Apache Shiro是Java的一個(gè)安全框架。相比于Spring Security,功能沒(méi)那么強(qiáng)大抽米,但是簡(jiǎn)單許多,下面我們了解的是在SpringBoot中集成Shiro框架理肺。
一.Shiro框架介紹:
本次我pom.xml用的Shiro版本是
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
1.認(rèn)證與授權(quán)相關(guān)基本概念
兩個(gè)基本的概念
安全實(shí)體:系統(tǒng)需要保護(hù)的具體對(duì)象數(shù)據(jù)
權(quán)限:系統(tǒng)相關(guān)的功能操作摄闸,例如基本的CRUD
Authentication(認(rèn)證):身份認(rèn)證/登錄,驗(yàn)證用戶(hù)是不是擁有相應(yīng)的身份-即登錄妹萨;
Authorization(授權(quán)):授權(quán)年枕,即權(quán)限驗(yàn)證,驗(yàn)證某個(gè)已認(rèn)證的用戶(hù)是否擁有某個(gè)權(quán)限乎完;即判斷用戶(hù)是否能做事情熏兄,常見(jiàn)的如:驗(yàn)證某個(gè)用戶(hù)是否擁有某個(gè)角色。或者細(xì)粒度的驗(yàn)證某個(gè)用戶(hù)對(duì)某個(gè)資源是否具有某個(gè)權(quán)限摩桶;
Session Manager:會(huì)話(huà)管理桥状,即用戶(hù)登錄后就是一次會(huì)話(huà),在沒(méi)有退出之前硝清,它的所有信息都在會(huì)話(huà)中辅斟;會(huì)話(huà)可以是普通JavaSE環(huán)境的,也可以是如Web環(huán)境的芦拿;
Cryptography:加密士飒,保護(hù)數(shù)據(jù)的安全性,如密碼加密存儲(chǔ)到數(shù)據(jù)庫(kù)蔗崎,而不是明文存儲(chǔ)酵幕;
Web Support:Web支持,可以非常容易的集成到Web環(huán)境缓苛;
Caching:緩存芳撒,比如用戶(hù)登錄后,其用戶(hù)信息他嫡、擁有的角色/權(quán)限不必每次去查番官,這樣可以提高效率;
Concurrency:shiro支持多線(xiàn)程應(yīng)用的并發(fā)驗(yàn)證钢属,即如在一個(gè)線(xiàn)程中開(kāi)啟另一個(gè)線(xiàn)程徘熔,能把權(quán)限自動(dòng)傳播過(guò)去;
Testing:提供測(cè)試支持淆党;
Run As:允許一個(gè)用戶(hù)假裝為另一個(gè)用戶(hù)(如果他們?cè)试S)的身份進(jìn)行訪(fǎng)問(wèn)酷师;
Remember Me:記住我,這個(gè)是非常常見(jiàn)的功能染乌,即一次登錄后山孔,下次再來(lái)的話(huà)不用登錄了。
二.核心組件核心類(lèi)
1.Subject:當(dāng)前用戶(hù)荷憋,SUbject可以是一個(gè)人台颠,也可以是第三方服務(wù),與當(dāng)前應(yīng)用交互的任何東西都是Subject勒庄,如網(wǎng)絡(luò)爬蟲(chóng)串前,機(jī)器人等;即一個(gè)抽象概念实蔽;所有Subject都綁定到SecurityManager荡碾,與Subject的所有交互都會(huì)委托給SecurityManager;可以把Subject認(rèn)為是一個(gè)門(mén)面局装;SecurityManager才是實(shí)際的執(zhí)行者坛吁;
2.SecurityManager:安全管理器劳殖,管理所有Subject,可以配合內(nèi)部安全組件拨脉,你可以把它看成DispatcherServlet前端控制器哆姻。
3.principals:身份,即主體的標(biāo)識(shí)屬性女坑,可以是任何東西填具,如用戶(hù)名、郵箱等匆骗,唯一即可劳景。一個(gè)主體可以有多個(gè)principals,但只有一個(gè)Primary principals碉就,一般是用戶(hù)名/密碼/手機(jī)號(hào)盟广。
4.credentials:證明/憑證,即只有主體知道的安全值瓮钥,如密碼/數(shù)字證書(shū)等筋量。
最常見(jiàn)的principals和credentials組合就是用戶(hù)名/密碼了。
5.Realms:用于進(jìn)行權(quán)限信息的驗(yàn)證碉熄,需要自己實(shí)現(xiàn)桨武。
6.Realm 本質(zhì)上是一個(gè)特定的安全 DAO:它封裝與數(shù)據(jù)源連接的細(xì)節(jié),得到Shiro 所需的相關(guān)的數(shù)據(jù)锈津。
域呀酸,Shiro從從Realm獲取安全數(shù)據(jù)(如用戶(hù)、角色琼梆、權(quán)限)性誉,就是說(shuō)SecurityManager要驗(yàn)證用戶(hù)身份,那么它需要從Realm獲取相應(yīng)的用戶(hù)進(jìn)行比較以確定用戶(hù)身份是否合法茎杂;也需要從Realm得到用戶(hù)相應(yīng)的角色/權(quán)限進(jìn)行驗(yàn)證用戶(hù)是否能進(jìn)行操作错览;可以把Realm看成DataSource,即安全數(shù)據(jù)源煌往。
在配置 Shiro 的時(shí)候倾哺,你必須指定至少一個(gè)Realm 來(lái)實(shí)現(xiàn)認(rèn)證(authentication)和/或授權(quán)(authorization)。
我們需要實(shí)現(xiàn)Realms的Authentication 和 Authorization刽脖。其中 Authentication 是用來(lái)驗(yàn)證用戶(hù)身份悼粮,Authorization 是授權(quán)訪(fǎng)問(wèn)控制,用于對(duì)用戶(hù)進(jìn)行的操作授權(quán)曾棕,證明該用戶(hù)是否允許進(jìn)行當(dāng)前操作,如訪(fǎng)問(wèn)某個(gè)鏈接菜循,某個(gè)資源文件等翘地。
7.SimpleHash,可以通過(guò)特定算法(比如md5)配合鹽值salt,對(duì)密碼進(jìn)行多次加密衙耕。
三昧穿、Shiro配置
1.SpringBoot集成Shiro一般通過(guò)java代碼配合@Configuration和@Bean配置。
2.Shiro的核心是通過(guò)Filter實(shí)現(xiàn)的橙喘,Shiro中的Filter是通過(guò)URL規(guī)則來(lái)進(jìn)行過(guò)濾和權(quán)限校驗(yàn)时鸵,因此我們需要定義一些關(guān)于URL的規(guī)則喝訪(fǎng)問(wèn)權(quán)限。
3.SpringBoot集成Shiro厅瞎,我們需要寫(xiě)來(lái)給你個(gè)類(lèi)饰潜,ShiroConfiguration-用來(lái)配置Shiro,注入各種Bean(包括過(guò)濾器(shiroFilter)和簸、安全事務(wù)管理器(SecurityManager)彭雾、密碼憑證(CredentialsMatcher)、aop注解支持(authorizationAttributeSourceAdvisor)等等) 和繼承了AuthorizingRealm的Realm類(lèi)锁保,包括登陸認(rèn)證(doGetAuthenticationInfo)薯酝、授權(quán)認(rèn)證(doGetAuthorizationInfo)。
四.代碼實(shí)現(xiàn)
ShiroConfiguration類(lèi):
package com.example.demo.config;
import cn.licoy.wdog.core.config.mybatis.UserRealm;
import cn.licoy.wdog.core.config.shiro.CredentialsMatcher;
import org.apache.log4j.Logger;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
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;
@Configuration
public class ShiroConfiguration {
private Logger logger=Logger.getLogger(ShiroConfiguration.class);
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
//設(shè)置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//默認(rèn)跳轉(zhuǎn)到登陸頁(yè)面
shiroFilterFactoryBean.setLoginUrl("/login");
//登陸成功后的頁(yè)面
shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
//自定義過(guò)濾器
Map<String,Filter> filterMap=new LinkedHashMap<>();
shiroFilterFactoryBean.setFilters(filterMap);
//權(quán)限控制map
Map<String,String> filterChainDefinitionMap=new LinkedHashMap<>();
// 配置不會(huì)被攔截的鏈接 順序判斷
filterChainDefinitionMap.put("/static/**", "anon");
//配置退出 過(guò)濾器,其中的具體的退出代碼Shiro已經(jīng)替我們實(shí)現(xiàn)了
filterChainDefinitionMap.put("/logout", "logout");
// //:這是一個(gè)坑呢爽柒,一不小心代碼就不好使了;
// //
// filterChainDefinitionMap.put("/**", "anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 核心的安全事務(wù)管理器
* 設(shè)置realm吴菠、cacheManager等
* @return
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager ();
//設(shè)置realm
securityManager.setRealm( myShiroRealm( ) );
securityManager.setRememberMeManager(rememberMeManager());
securityManager.setCacheManager( ehCacheManager() );
return securityManager;
}
/**
* 身份認(rèn)證Realm,此處的注入不可以缺少浩村。否則會(huì)在UserRealm中注入對(duì)象會(huì)報(bào)空指針.
* @return
*/
@Bean
public UserRealm myShiroRealm( ){
UserRealm myShiroRealm = new UserRealm();
myShiroRealm.setCredentialsMatcher( hashedCredentialsMatcher() );
return myShiroRealm;
}
/**
* 配置自定義的密碼比較器
* @return
*/
@Bean
public CredentialsMatcher credentialsMatcher(){
return new CredentialsMatcher();
}
/**
* 哈希密碼比較器做葵。在myShiroRealm中作用參數(shù)使用
* 登陸時(shí)會(huì)比較用戶(hù)輸入的密碼,跟數(shù)據(jù)庫(kù)密碼配合鹽值salt解密后是否一致穴亏。
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:這里使用md5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次數(shù)蜂挪,比如散列兩次,相當(dāng)于 md5( md5(""));
return hashedCredentialsMatcher;
}
/**
* shiro緩存管理器;
* 需要注入對(duì)應(yīng)的其它的實(shí)體類(lèi)中: 安全管理器:securityManager
* 可見(jiàn)securityManager是整個(gè)shiro的核心嗓化;
* @return
*/
@Bean
public EhCacheManager ehCacheManager(){
logger.info("------------->ShiroConfiguration.getEhCacheManager()執(zhí)行");
EhCacheManager cacheManager=new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return cacheManager;
}
/**
* 記住我管理器
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager=new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
//rememberMe cookie加密的密鑰 默認(rèn)AES算法
// cookieRememberMeManager.setCipherKey();
return cookieRememberMeManager;
}
/**
* cookie對(duì)象
* @return
*/
@Bean
public Cookie rememberMeCookie() {
SimpleCookie simpleCookie=new SimpleCookie("rememberMe");
//記住我cookie生效時(shí)間棠涮,單位秒
simpleCookie.setMaxAge(3600);
return simpleCookie;
}
/**
* 開(kāi)啟shiro aop注解支持.
* 使用代理方式;所以需要開(kāi)啟代碼支持;否則@RequiresRoles等注解無(wú)法生效
* @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();
}
/**
* 自動(dòng)創(chuàng)建代理
* @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
}
自定義UserRealm-(MyRealm)類(lèi):
package cn.licoy.wdog.core.config.mybatis;
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.HashSet;
import java.util.Set;
public class UserRealm extends AuthorizingRealm {
@Resource(name = "userServiceImpl")
private UserSerevice userService;
private Logger logger=Logger.getLogger(UserRealm.class);
/**
* 提供用戶(hù)信息,返回權(quán)限信息
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("---------------------------->授權(quán)認(rèn)證:");
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 ) );
//將擁有角色的所有權(quán)限放進(jìn)Set里面刺覆,也就是求Set集合的并集
pemissionIdSet.addAll( userService.findPermissionIdByRoleId( roleId ));
}
for(int permissionId : pemissionIdSet) {
String permission= userService.findPermissionById( permissionId ).getPermission() ;
pemissionSet.add( permission );
}
// 將角色名稱(chēng)提供給授權(quán)info
authorizationInfo.setRoles( roleSet );
// 將權(quán)限名稱(chēng)提供給info
authorizationInfo.setStringPermissions(pemissionSet);
return authorizationInfo;
}
/**
* 提供帳戶(hù)信息严肪,返回認(rèn)證信息
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("---------------------------->登陸驗(yàn)證:");
String userName=(String)authenticationToken.getPrincipal();
User user=userService.findUserByName(userName);
if(user==null) {
//用戶(hù)不存在就拋出異常
throw new UnknownAccountException();
}
//State.LOCKED="2" 用戶(hù)被鎖定狀態(tài),自定義
if( State.LOCKED.equals( user.getState() ) ) {
//用戶(hù)被鎖定就拋異常
throw new LockedAccountException();
}
//密碼可以通過(guò)SimpleHash加密谦屑,然后保存進(jìn)數(shù)據(jù)庫(kù)驳糯。
//此處是獲取數(shù)據(jù)庫(kù)內(nèi)的賬號(hào)、密碼氢橙、鹽值酝枢,保存到登陸信息info中
SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(user.getUserName(),
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()) ,
getName()); //realm name
return authenticationInfo;
}
}
更具體的代碼,參見(jiàn)碼云:
https://gitee.com/lufff1458/Shiro-Demo
可以參考watchdog整個(gè)框架中的shiro部分:
https://gitee.com/licoy/watchdog-framework/tree/master/src/main/java/cn/licoy/wdog/core/config
參考博客:
http://www.ityouknow.com/springboot/2017/06/26/springboot-shiro.html