Shiro主要用來進行權限管理。簡單的介紹如下:
一、概念
Shiro是一個安全框架,可以進行角色、權限管理。
Shiro主要功能如下:
Authentication(認證):用戶身份識別,通常被稱為用戶“登錄”
Authorization(授權):訪問控制。比如某個用戶是否具有某個操作的使用權限。
Session Management(會話管理):特定於用戶的會話管理,甚至在非web 或 EJB 應用程序。
Cryptography(加密):在對數據源使用加密算法加密的同時,保證易於使用。
二、主要的類
1.Subject:當前用戶,Subject可以是一個人,也可以是第三方服務
2.SecurityManager:管理所有Subject,可以配合內部安全組件。
3.principals:身份,即主體的標識屬性,可以是任何東西,如用戶名、郵箱等,唯一即可。一個主體可以有多個principals,但只有一個Primary principals,一般是用戶名/密碼/手機號。
4.credentials:證明/憑證,即只有主體知道的安全值,如密碼/數字證書等。
最常見的principals和credentials組合就是用戶名/密碼了。
5.Realms:用於進行權限信息的驗證,需要自己實現。
6.Realm 本質上是一個特定的安全 DAO:它封裝與數據源連接的細節,得到Shiro 所需的相關的數據。
在配置 Shiro 的時候,你必須指定至少一個Realm 來實現認證(authentication)和/或授權(authorization)。
我們需要實現Realms的Authentication 和 Authorization。其中 Authentication 是用來驗證用戶身份,Authorization 是授權訪問控制,用於對用戶進行的操作授權,證明該用戶是否允許進行當前操作,如訪問某個鏈接,某個資源文件等。
7.SimpleHash,可以通過特定算法(比如md5)配合鹽值salt,對密碼進行多次加密。
三、Shiro配置
1.Spring集成Shiro一般通過xml配置,SpringBoot集成Shiro一般通過java代碼配合@Configuration和@Bean配置。
2.Shiro的核心通過過濾器Filter實現。Shiro中的Filter是通過URL規則來進行過濾和權限校驗,所以我們需要定義一系列關於URL的規則和訪問權限。
3.SpringBoot集成Shiro,我們需要寫的主要是兩個類,ShiroConfiguration類,還有繼承了AuthorizingRealm的Realm類
ShiroConfiguration類,用來配置Shiro,注入各種Bean。
包括過濾器(shiroFilter)、安全事務管理器(SecurityManager)、密碼憑證(CredentialsMatcher)、aop注解支持(authorizationAttributeSourceAdvisor)等等
Realm類,包括登陸認證(doGetAuthenticationInfo)、授權認證(doGetAuthorizationInfo)
四、具體示例如下:
ShiroConfiguration.java如下:
package com.example.demo.config; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.SecurityManager; 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.servlet.Cookie; import org.apache.shiro.web.servlet.SimpleCookie; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import javax.servlet.Filter; import java.util.LinkedHashMap; import java.util.Map; /** * Created by lenovo on 三月 */ @Configuration public class ShiroConfiguration { @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean(); //設置安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); //默認跳轉到登陸頁面 shiroFilterFactoryBean.setLoginUrl("/login"); //登陸成功后的頁面 shiroFilterFactoryBean.setSuccessUrl("/index"); shiroFilterFactoryBean.setUnauthorizedUrl("/403"); //自定義過濾器 Map<String,Filter> filterMap=new LinkedHashMap<>(); shiroFilterFactoryBean.setFilters(filterMap); //權限控制map Map<String,String> filterChainDefinitionMap=new LinkedHashMap<>(); // 配置不會被攔截的鏈接 順序判斷 filterChainDefinitionMap.put("/static/**", "anon"); //配置退出 過濾器,其中的具體的退出代碼Shiro已經替我們實現了 filterChainDefinitionMap.put("/logout", "logout"); // //<!-- 過濾鏈定義,從上向下順序執行,一般將/**放在最為下邊 -->:這是一個坑呢,一不小心代碼就不好使了; // //<!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問--> // filterChainDefinitionMap.put("/**", "anon"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 核心的安全事務管理器 * @return */ @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager (); //設置realm securityManager.setRealm( myShiroRealm( ) ); securityManager.setRememberMeManager(rememberMeManager()); return securityManager; } /** * 身份認證Realm,此處的注入不可以缺少。否則會在UserRealm中注入對象會報空指針. * @return */ @Bean public UserRealm myShiroRealm( ){ UserRealm myShiroRealm = new UserRealm(); myShiroRealm.setCredentialsMatcher( hashedCredentialsMatcher() ); return myShiroRealm; } /** * 哈希密碼比較器。在myShiroRealm中作用參數使用 * 登陸時會比較用戶輸入的密碼,跟數據庫密碼配合鹽值salt解密后是否一致。 * @return */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:這里使用md5算法; hashedCredentialsMatcher.setHashIterations(2);//散列的次數,比如散列兩次,相當於 md5( md5("")); return hashedCredentialsMatcher; } // //注入緩存 // @Bean // public EhCacheManager ehCacheManager(){ // System.out.println("ShiroConfiguration.getEhCacheManager()執行"); // EhCacheManager cacheManager=new EhCacheManager(); // cacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml"); // return cacheManager; // } /** * 開啟shiro aop注解支持. * 使用代理方式;所以需要開啟代碼支持;否則@RequiresRoles等注解無法生效 * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * Shiro生命周期處理器 * @return */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){ return new LifecycleBeanPostProcessor(); } /** * 自動創建代理 * @return */ @Bean @DependsOn({"lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } }
Realm類如下:
package com.example.demo.config; import com.example.demo.pojo.SysPermission; import com.example.demo.pojo.SysUserRole; import com.example.demo.pojo.User; import com.example.demo.service.UserSerevice; import com.example.demo.utils.State; import org.apache.log4j.Logger; import org.apache.shiro.authc.*; 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.ByteSource; import javax.annotation.Resource; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.apache.coyote.http11.Constants.a; /** * Created by lenovo on 三月 */ public class UserRealm extends AuthorizingRealm { @Resource(name = "userServiceImp") private UserSerevice userService; private Logger logger=Logger.getLogger(UserRealm.class); /** * 提供用戶信息,返回權限信息 * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info("---------------------------->授權認證:"); SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo(); String userName=(String) principals.getPrimaryPrincipal(); String userId=userService.findUserIdByName(userName); Set<SysUserRole> roleIdSet=userService.findRoleIdByUid( Integer.parseInt(userId) ); Set<String> roleSet=new HashSet<>(); Set<Integer> pemissionIdSet=new HashSet<>(); Set<String> pemissionSet=new HashSet<>(); for(SysUserRole roleInfo : roleIdSet) { int roleId=roleInfo.getRoleId(); roleSet.add( userService.findRoleByRoleId( roleId ) ); //將擁有角色的所有權限放進Set里面,也就是求Set集合的並集 //由於我這邊的數據表設計得不太好,所以提取set集合比較麻煩 pemissionIdSet.addAll( userService.findPermissionIdByRoleId( roleId )); } for(int permissionId : pemissionIdSet) { String permission= userService.findPermissionById( permissionId ).getPermission() ; pemissionSet.add( permission ); } // 將角色名稱組成的Set提供給授權info authorizationInfo.setRoles( roleSet ); // 將權限名稱組成的Set提供給info authorizationInfo.setStringPermissions(pemissionSet); return authorizationInfo; } /** * 提供帳戶信息,返回認證信息 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { logger.info("---------------------------->登陸驗證:"); String userName=(String)authenticationToken.getPrincipal(); User user=userService.findUserByName(userName); if(user==null) { //用戶不存在就拋出異常 throw new UnknownAccountException(); } if( State.LOCKED.equals( user.getState() ) ) { //用戶被鎖定就拋異常 throw new LockedAccountException(); } //密碼可以通過SimpleHash加密,然后保存進數據庫。 //此處是獲取數據庫內的賬號、密碼、鹽值,保存到登陸信息info中 SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()) , getName()); //realm name return authenticationInfo; } }
更具體的代碼,參見GitHub:
https://github.com/firefoxer1992/SpringBootProject
參考博客:
http://www.ityouknow.com/springboot/2017/06/26/springboot-shiro.html
鹽值及密碼的加密,可以參考博客: