最近在學習Springboot,在這個過程中遇到了很多之前都沒有技術知識,學習了一陣子,稍微總結一些。
---- Shiro框架
shiro框架,是一個相對比較簡便的安全框架,它可以干凈利落地處理身份驗證、授權、企業會話管理和加密。
在此引入網上眾多資料,歸總shiro大致框架如下:
- Subject: 主體,代表了當前“用戶”,這個用戶不一定是一個具體的人,與當前應用交互的任何東西都是Subject,如網絡爬蟲,機器人等;即一個抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會委托給SecurityManager;可以把Subject認為是一個門面;SecurityManager才是實際的執行者
- Shiro SecurityManager: 管理所有Subject,SecurityManager 是 Shiro 架構的核心,配合內部安全組件共同組成安全傘.
- Realm: 用於進行權限信息的驗證,我們自己實現。Realm 本質上是一個特定的安全 DAO:它封裝與數據源連接的細節,得到Shiro 所需的相關的數據。在配置 Shiro 的時候,你必須指定至少一個Realm 來實現認證(authentication)和/或授權(authorization)
在主要開發上,我將Shiro的實際應用歸結成兩大塊,其一是關於shiro的一些配置信息,這里稱為ShiroConfig文件,由於Apache Shiro 核心通過 Filter 來實現,既然是使用 Filter 一般也就能猜到,是通過 URL 規則來進行過濾和權限校驗,所以我們需要定義一系列關於 URL 的規則和訪問權限,以及一些Shiro自身具備的一些Bean工具。其二是關於我們自己需要定義的Realm類,這里稱為ShiroRealm文件,繼承AuthorizingRealm 抽象類,重載 doGetAuthenticationInfo(),重寫獲取用戶信息的方法,重寫doGetAuthorizationInfo(),進行角色權限的配置。
---?ShiroConfig
package springbootshiro2.demo.configurer; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.codec.Base64; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.mgt.WebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; /** * 〈一句話功能簡述〉<br> * 〈〉 * * @author X450J * @create 2019/5/30 * @since 1.0.0 */ @Configuration public class ShiroConf { //注入shiro過濾器 @Bean("shiroFilterFactoryBean") public ShiroFilterFactoryBean shiroFiltr(WebSecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //設置管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login"); // 如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 shiroFilterFactoryBean.setSuccessUrl("/index");// 登錄成功后要跳轉的鏈接 shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");//設置無權限跳轉頁面 Map<String,String> chains = new LinkedHashMap<>(); chains.put("/logout","logout"); chains.put("/login", "anon");//anon表示可以匿名訪問 chains.put("/login/qq", "anon");//anon表示可以匿名訪問 chains.put("/authorize/qq", "anon");//anon表示可以匿名訪問 //對PermissionAction.class 中的url進行權限控制 chains.put("/user", "roles[user]");//需要user角色才可以訪問 chains.put("/user/per", "perms[user:query]");//需要user角色才可以訪問 chains.put("/admin", "roles[admin]");//需要admin角色才可以訪問 //chains.put("/**", "authc");//表示需要認證,才能訪問 chains.put("/**", "user");//表示需要認證或記a住我都能訪問 shiroFilterFactoryBean.setFilterChainDefinitionMap(chains); return shiroFilterFactoryBean; } //安全管理器 @Bean public WebSecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //設置realm securityManager.setRealm(shiroRealm()); securityManager.setCacheManager(ehCacheManager()); securityManager.setRememberMeManager(rememberMeManager()); return securityManager; } //會話管理器 @Bean public SessionManager sessionManager(){ DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionIdUrlRewritingEnabled(true); sessionManager.setGlobalSessionTimeout(1 * 60 * 60 * 1000); sessionManager.setDeleteInvalidSessions(true); sessionManager.setSessionIdCookie(rememberMeCookie()); return sessionManager; } //Realm,里面需要自己實現認證和授權業務 @Bean public ShiroRealm shiroRealm() { ShiroRealm shiroRealm = new ShiroRealm(); shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return shiroRealm; } //緩存管理 @Bean public EhCacheManager ehCacheManager(){ EhCacheManager ehCacheManager = new EhCacheManager(); ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml"); return ehCacheManager; } //密碼管理 @Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); credentialsMatcher.setHashAlgorithmName("md5"); credentialsMatcher.setHashIterations(2); //加密兩次 credentialsMatcher.setStoredCredentialsHexEncoded(true); //啟用十六進制 return credentialsMatcher; } //cookie管理 @Bean public SimpleCookie rememberMeCookie() { SimpleCookie cookie = new SimpleCookie("rememberMe"); cookie.setHttpOnly(true); cookie.setMaxAge(1 * 60 * 60); return cookie; } //管理shiro的生命周期 @Bean(name = "lifecycleBeanPostProcessor") public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } //記住我 @Bean public CookieRememberMeManager rememberMeManager() { CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); //不是所有的base64編碼都可以用,長度過大過小都不行 cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag==")); return cookieRememberMeManager; } //開啟shiro注解權限控制 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor attributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); attributeSourceAdvisor.setSecurityManager(securityManager); return attributeSourcteAdvisor; } }
- LifecycleBeanPostProcessor:它管理Shiro的生命周期。
- ShiroFilterFactoryBean:采用了工廠設計模式,負責引入SecurityManager、登錄的頁面url設置、登錄后要跳轉的url設置、未授權的頁面的url設置、攔截器鏈的設置。
- SecurityManager:Shiro的核心,認證器、緩存器、Realm數據源都在這里配置注入
- HashedCredentialsMatcher:Shiro自帶密碼器,這里用了MD5加密算法,並且還將其注入到自定義的ShiroRealm之中。
- CacheManager: 緩存管理器,這里引入了Ehcache框架來實現緩存,接着再一起注入安全管理器(SecurityManager)
- AuthorizationAttributeSourceAdvisor: 配置生效后,可以開啟shiro的注解權限控制
- CookieRememberMeManager: 顧名思義,負責實現RememberMe功能的,使得瀏覽器請求頁面時可以通過cookies判斷用戶而無需登錄
- SessionManager: 關於session會話的一些管理
---?ShiroRealm
package springbootshiro2.demo.configurer; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import springbootshiro2.demo.model.User; /** * 〈一句話功能簡述〉<br> * 〈自定義Realm〉 * * @author X450J * @create 2019/5/30 * @since 1.0.0 */ public class ShiroRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //給當前角色授權 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); User user = (User) principalCollection.getPrimaryPrincipal(); //在這里 實際開發應該從數據庫進行獲取 if (user.getUsername().equals("xslde")){ //設置該用戶擁有user角色 authorizationInfo.addRole("user"); //設置該用戶擁有query權限 authorizationInfo.addStringPermission("user:query"); } if(user.getUsername().equals("admin")){ //admin用戶擁有admin、user角色 authorizationInfo.addRole("admin"); authorizationInfo.addRole("user"); //設置該用戶擁有query權限 authorizationInfo.addStringPermission("user:query"); } return authorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //進行用戶信息獲取 String username = (String)token.getPrincipal(); //開發中,這里都是去數據庫查詢 if (!"xslde".equals(username)&&!"test".equals(username)&&!"xslde.com".equals(username)&&!"admin".equals(username)){ throw new UnknownAccountException("用戶不存在!"); } User user = null; if ("xslde".equals(username)){ user = new User(); user.setUsername("xslde"); user.setPassword("0caf568dbf30f5c33a13c56b869259fc"); user.setSalt("abcd"); user.setAvailable(1); } if ("admin".equals(username)){ user = new User(); user.setUsername("admin"); user.setPassword("0caf568dbf30f5c33a13c56b869259fc"); user.setSalt("abcd"); user.setAvailable(1); } if ("test".equals(username)){ user = new User(); user.setUsername("test"); user.setPassword("0caf568dbf30f5c33a13c56b869259fc"); user.setSalt("abcd"); user.setAvailable(0); } if (user.getAvailable()!=1){ throw new LockedAccountException("賬戶已被鎖定"); } return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName()); } //生成一個加鹽密碼 public static void main(String[] args){ //加密類型 String hashAlgorithmName = "md5"; //迭代次數 Integer count = 2; String password = "123456"; String salt = "abcd"; String s = new SimpleHash(hashAlgorithmName,password,salt,count).toHex(); System.out.println(s); } }
小結兩點:第一是理清數據之間關系,即一個用戶可以擁有多個角色,而一個角色可以具備多種權限
第二是理解關於鹽的事情,因為這里用的密碼雖然已經通過MD5算法進行加密,但是假如別人是知道這個系統用的是這個加密的算法,那么很容易就通過對應的密碼方式進 行破解,所以引入鹽的概念,即生活中一道相同的菜在不同人的手里所下的鹽的量都會有所區別,借助這個道理,因為我們上面已經在Realm中配置過加密工具了,所以這里不加鹽值的話會默認使用"MD5"算法(上面配置加密工具時設置)進行密碼加密,如果加了鹽值,在算法加密之后會再進行鹽值加密過程。
之后在controller層中,進行如下的配置
package springbootshiro2.demo.action; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import java.util.Map; /** * 〈一句話功能簡述〉<br> * 〈〉 * * @author X450J * @create 2019/5/30 * @since 1.0.0 */ @Controller public class PermissionAction { @GetMapping("/unauthorized") public String unauthorized(){ return "unauthorized.html"; } //擁有user角色才可訪問 @GetMapping("/user") public String user(Map<String,String> code){ code.put("msg","擁有user角色"); return "user.html"; } //擁有user角色才可訪問和user:query權限才可訪問 @GetMapping("/user/per") public String userPer(Map<String,String> mode){ mode.put("msg","擁有user角色和user:query權限"); return "user.html"; } //擁有admin角色才可訪問 @GetMapping("/admin") public String admin(Map<String,String> mode){ mode.put("msg","擁有admin角色"); return "admin.html"; } //通過注解控制授權 @RequiresRoles({"admin"}) @GetMapping("/abc") public String abc(Map<String,String> mode){ mode.put("msg","擁有admin角色,並且是通過注解控制"); return "admin.html"; } //通過注解控制權限 @RequiresPermissions({"user:query"}) @GetMapping("/abcd") public String abcd(Map<String,String> mode){ mode.put("msg","擁有user:query權限,並且是通過注解控制"); return "user.html"; } }