Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、受權、密碼和會話管理。html
shiro有個核心組件,分別爲Subject、SecurityManager和Realmsjava
若是想要更加深刻的瞭解的shrio能夠參考:https://www.w3cschool.cn/shiro/co4m1if2.htmlgit
<!--shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.7.1</version> </dependency> <!--shiro整合thymeleaf--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> <!--shiro緩存--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.7.1</version> </dependency>
shiro默認是與jsp進行使用的,而這裏是shiro整合thymeleaf全部要導入shiro整合thymeleaf的jar包github
通常來講整合只須要完成兩個類的實現便可
一個是 ShiroConfig 一個是 CustomerRealm
若是須要添加shiro緩存而且不是自帶的緩存而是redis緩存還須要進行另外兩個類的編寫
一個是 RedisCache 一個是 RedisCacheManagerweb
未加shiro的緩存redis
package com.yuwen.config; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import com.yuwen.shiro.cache.RedisCacheManager; import com.yuwen.shiro.realm.CustomerRealm; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; @Configuration public class ShiroConfig { //讓頁面shiro標籤生效 @Bean public ShiroDialect shiroDialect(){ return new ShiroDialect(); } //一、建立shiroFilter 負責攔截全部請求 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); //給filter設置安全管理 factoryBean.setSecurityManager(defaultWebSecurityManager); //配置系統的受限資源 //配置系統公共資源 所有都能訪問的設置anon Map<String,String> map = new HashMap<>(); map.put("/main","authc");//請求這個資源須要認證和受權 authc表示須要認證後才能訪問 map.put("/admin","roles[admin]"); //表示admin角色才能訪問 roles[]表示須要什麼角色才能訪問 map.put("/manage","perms[user:*:*]"); //表示須要user:*:*權限才能訪問 perms[]表示須要什麼權限才能訪問 //訪問須要認證的頁面若是未登陸會跳轉到/login路由進行登錄 factoryBean.setLoginUrl("/login"); //訪問未受權頁面會自動跳轉到/unAuth路由 factoryBean.setUnauthorizedUrl("/unAuth"); factoryBean.setFilterChainDefinitionMap(map); return factoryBean; } //二、建立安全管理器 @Bean public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("getRealm") Realm realm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //給安全管理器設置 securityManager.setRealm(realm); return securityManager; } //三、建立自定義的realm @Bean public Realm getRealm(){ CustomerRealm customerRealm = new CustomerRealm(); //修改憑證校驗匹配器 HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); //設置加密算法爲md5 credentialsMatcher.setHashAlgorithmName("MD5"); //設置散列次數 credentialsMatcher.setHashIterations(1024); customerRealm.setCredentialsMatcher(credentialsMatcher); return customerRealm; } }
由於通常在數據庫中設置明文密碼不安全,全部我這裏對密碼進行了md5加密,個人加密方式爲:密碼 = 密碼+鹽+散列次數 然後進行MD5加密 因此這裏建立自定義的realm時須要進行設置匹配器這樣登陸時密碼才能匹配成功算法
package com.yuwen.shiro.realm; import com.yuwen.pojo.User; import com.yuwen.pojo.vo.ViewPerms; import com.yuwen.pojo.vo.ViewRole; import com.yuwen.service.UserService; import com.yuwen.shiro.salt.MyByteSource; 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.apache.shiro.util.CollectionUtils; import org.springframework.util.ObjectUtils; import javax.annotation.Resource; import java.util.List; //自定義realm public class CustomerRealm extends AuthorizingRealm { @Resource private UserService userService; //受權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //獲取身份信息 String primaryPrincipal = (String)principalCollection.getPrimaryPrincipal(); //根據主身份信息獲取角色 和 權限信息 List<ViewRole> roles = userService.findRolesByUsername(primaryPrincipal); if (!CollectionUtils.isEmpty(roles)){ SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); roles.forEach(viewRole -> { simpleAuthorizationInfo.addRole(viewRole.getName()); //權限信息 List<ViewPerms> perms = userService.findPermsByRoleId(viewRole.getName()); if (!CollectionUtils.isEmpty(perms)){ perms.forEach(viewPerms -> { simpleAuthorizationInfo.addStringPermission(viewPerms.getPName()); }); } }); return simpleAuthorizationInfo; } return null; } //認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //獲取登入的身份信息 String principal = (String) authenticationToken.getPrincipal(); User user = userService.findByUsername(principal); if (!ObjectUtils.isEmpty(user)){ //ByteSource.Util.bytes(user.getSalt()) 經過這個工具將鹽傳入 //若是身份認證驗證成功,返回一個AuthenticationInfo實現; return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),new MyByteSource(user.getSalt()),this.getName()); } return null; } }
在登陸時會自動調用這個身份驗證 在驗證時若是出錯,會報異常,我在controller層接收了異常並處理spring
controller層中登陸時的異常處理 數據庫
@PostMapping("/login") public String login(String username,String password){ //獲取主體對象 Subject subject = SecurityUtils.getSubject(); try { //自動調用CustomerRealm 類中的身份驗證方法 subject.login(new UsernamePasswordToken(username,password)); return "index"; }catch (UnknownAccountException e){ //接收異常並處理 e.printStackTrace(); model.addAttribute("msg","用戶名有誤,請從新登陸"); }catch (IncorrectCredentialsException e){//接收異常並處理 e.printStackTrace(); model.addAttribute("msg","密碼有誤,請從新登陸"); } return "login"; }
定義了shiro緩存,用戶登陸後,其用戶信息、擁有的角色 / 權限沒必要每次去查,這樣能夠提升效率apache
默認緩存的配置
在 ShiroConfig中 的 getRealm() 方法中開啓緩存管理
@Bean public Realm getRealm(){ CustomerRealm customerRealm = new CustomerRealm(); //開啓緩存管理 customerRealm.setCacheManager(new EhCacheManager()); //開啓全局緩存 customerRealm.setCachingEnabled(true); //開啓認證緩存 customerRealm.setAuthenticationCachingEnabled(true); customerRealm.setAuthenticationCacheName("authenticationCache"); //開啓權限緩存 customerRealm.setAuthorizationCachingEnabled(true); customerRealm.setAuthorizationCacheName("authorizationCache"); return customerRealm; }
與reids整合的緩存這裏就不說明了,放在源碼裏本身查看,源碼在下方
在這裏用標籤來判斷某些區域須要認證或什麼角色或者什麼權限才能訪問
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"> <title>首頁</title> <link rel="shortcut icon" href="#"> </head> <body> <h1>index</h1> <a href="/logout">退出</a> <div> <a href="/main">main</a> | <a href="/manage">manage</a> | <a href="/admin">admin</a> </div> <!--獲取認證信息--> 用戶:<span shiro:principal=""></span><hr> <!--認證處理--> <span shiro:authenticated=""><hr> 顯示認證經過內容 </span> <span shiro:notAuthenticated=""><hr> 沒有認證時 顯示 </span> <!--受權角色--> <span shiro:hasRole="admin"><hr> admin角色 顯示 </span> <span shiro:hasPermission="user:*:*"><hr> 具備用戶模塊的"user:*:*"權限 顯示 </span> </body> </html>