shiro是輕量級框架,比SpringSecurity簡單,下面是我springboot整合shiro的經驗。
(因為今天沒有時間調試前後端分離的shiro配置,這裡僅僅是shiro和springboot的簡單整合)
shiro功能
- Subject:主體,一般指用戶。
- SecurityManager:安全管理器,管理所有Subject,可以配合內部安全組件。
- Realms:用於進行權限信息的驗證。
項目整合Shiro
<code> org.apache.shiro shiro-spring 1.4.0 /<code>
項目結構圖
UserInfo(用戶類)
<code>@Data public class UserInfo { private int userId; private String username; private String password; private Set roles; }/<code>
Role(角色類)
<code>@Data public class Role { private Long id; private String description; private String name; private Integer status; /** * 角色對應權限集合 */ private Set permissions; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description == null ? null : description.trim(); } public String getName() { return name; } public void setName(String name) { this.name = name == null ? null : name.trim(); } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } }/<code>
Permission.java(權限類):
<code>public class Permission { private Long id; private String description; private String name; private Long parentId; private Integer version; private Integer weight; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description == null ? null : description.trim(); } public String getName() { return name; } public void setName(String name) { this.name = name == null ? null : name.trim(); } public Long getParentId() { return parentId; } public void setParentId(Long parentId) { this.parentId = parentId; } public Integer getVersion() { return version; } public void setVersion(Integer version) { this.version = version; } public Integer getWeight() { return weight; } public void setWeight(Integer weight) { this.weight = weight; } }/<code>
LoginServiceImpl(登錄服務類--查詢用戶)
<code> @Service public class LoginServiceImpl implements LoginService { @Override public UserInfo getUser(String getMapByName) { // 查詢用戶信息, 此處就是shiro每次查詢用戶的接口 return getMapByName(getMapByName); } /** * 模擬數據庫查詢 * @param userName * @return */ private UserInfo getMapByName(String userName){ //共添加兩個用戶,兩個用戶都是admin一個角色, //wsl有query和add權限,zhangsan只有一個query權限 Permission permissions1 = new Permission(); permissions1.setId(1L); permissions1.setDescription("user:list"); Set permissionsSet = new HashSet<>(); permissionsSet.add(permissions1); Role role = new Role(); role.setId(1L); role.setName("admin"); role.setPermissions(permissionsSet); Set roleSet = new HashSet<>(); roleSet.add(role); UserInfo user = new UserInfo(); user.setUserId(1); user.setUsername("admin"); // 賬號 user.setPassword("admin"); // 密碼 目前還是明文保存 user.setRoles(roleSet); Map map = new HashMap<>(); map.put(user.getUsername(), user); return map.get(userName); } } /<code>
自定義Realm用於查詢用戶的角色和權限信息並保存到權限管理器:
UserRealm.java
<code>import com.whf.blog.bean.UserInfo; import com.whf.blog.pojo.Permission; import com.whf.blog.service.LoginService; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; public class UserRealm extends AuthorizingRealm { @Autowired private LoginService loginService; // 授權查詢回調函數, 進行鑑權但緩存中無用戶的授權信息時調用.SecurityUtils.getSubject().isPermitted 執行 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //獲取用戶名 String name = (String) principalCollection.getPrimaryPrincipal(); //查詢用戶信息 UserInfo user = loginService.getUser(name); //添加角色和權限 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //添加角色 //添加權限 user.getRoles().forEach(role -> { simpleAuthorizationInfo.addRole(role.getName()); for (Permission permissions : role.getPermissions()) { simpleAuthorizationInfo.addStringPermission(permissions.getName()); } }); return simpleAuthorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // subject.login(usernamePasswordToken); 此時方法會走到這裡,如果用戶發現賬戶密碼又問題可以斷點此處查看原因 if (authenticationToken.getPrincipal() == null) { return null; } //獲取用戶信息 String name = authenticationToken.getPrincipal().toString(); UserInfo user = loginService.getUser(name); if (user == null) { //這裡返回後會報出對應異常 return null; } else { //這裡驗證authenticationToken和simpleAuthenticationInfo的信息 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword().toString(), getName()); return simpleAuthenticationInfo; } } }/<code>
ShiroConfig(配置類)
<code> import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; @Configuration public class shiroConfig { @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator(); defaultAAP.setProxyTargetClass(true); return defaultAAP; } //自己的驗證方式 @Bean public UserRealm myShiroRealm() { UserRealm customRealm = new UserRealm(); return customRealm; } //權限管理, @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } //Filter工廠,設置對應的過濾條件和跳轉條件 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map map = new HashMap<>(); //登出 map.put("/logout", "logout"); //對所有用戶認證 map.put("/**", "authc"); // //登錄 登錄跳過驗證 shiroFilterFactoryBean.setLoginUrl("/login"); // //首頁 // shiroFilterFactoryBean.setSuccessUrl("/index"); // //錯誤頁面,認證不通過跳轉 // shiroFilterFactoryBean.setUnauthorizedUrl("/error"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } } /<code>
LoginController.(外部訪問)
<code> @RequestMapping(value = "/login",method = RequestMethod.POST) @ResponseBody public Result login(@RequestBody UserInfo user) { Result result = new Result(); //添加用戶認證信息 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken( user.getUsername(), user.getPassword() ); try { //進行驗證,這裡可以捕獲異常,然後返回對應信息 subject.login(usernamePasswordToken); result.setCode(200); result.setUserId("1"); // subject.checkRole("admin"); // subject.checkPermissions("query", "add"); } catch (AuthenticationException e) { e.printStackTrace(); result.setCode(400); } catch (AuthorizationException e) { e.printStackTrace(); result.setCode(400); } return result; }/<code>
註解驗證角色和權限的話無法捕捉異常,添加一個異常攔截器
MyExceptionHandler
<code> import com.whf.blog.resp.Result; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authz.AuthorizationException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; @ControllerAdvice @Slf4j public class MyExceptionHandler { @ExceptionHandler @ResponseBody public Result ErrorHandler(AuthorizationException e) { log.error("權限驗證失敗!", e); Result result = new Result(); result.setCode(400); result.setMessage( "權限驗證失敗!"); return result; } } /<code>