SpringBoot集成Shiro進(jìn)行權(quán)限控制和管理的示例
shiro
apache shiro 是一個(gè)輕量級(jí)的身份驗(yàn)證與授權(quán)框架,與spring security 相比較,簡(jiǎn)單易用,靈活性高,springboot本身是提供了對(duì)security的支持,畢竟是自家的東西。springboot暫時(shí)沒(méi)有集成shiro,這得自己配。
1 . 添加依賴(lài)
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.5</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.5</version> </dependency>
2 . 編寫(xiě)Shiro配置類(lèi)
package com.xbz.web.system.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.session.mgt.eis.SessionDAO;
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.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
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 org.springframework.context.annotation.DependsOn;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* shiro配置類(lèi)
* ApacheShiro核心通過(guò)Filter來(lái)實(shí)現(xiàn)權(quán)限控制和攔截 , 就好像SpringMVC通過(guò)DispachServlet來(lái)主控制請(qǐng)求分發(fā)一樣 .
* 既然是使用Filter , 即是通過(guò)URL規(guī)則來(lái)進(jìn)行過(guò)濾和權(quán)限校驗(yàn) , 所以我們需要定義一系列關(guān)于URL的規(guī)則和訪問(wèn)權(quán)限
*/
@Configuration
public class ShiroConfiguration {
/**
* DefaultAdvisorAutoProxyCreator , Spring的一個(gè)bean , 由Advisor決定對(duì)哪些類(lèi)的方法進(jìn)行AOP代理 .
*/
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
/**
* ShiroFilterFactoryBean : 為了生成ShiroFilter , 處理攔截資源文件問(wèn)題 .
* 它主要保持了三項(xiàng)數(shù)據(jù) , securityManager , filters , filterChainDefinitionManager .
* 注意 : 單獨(dú)一個(gè)ShiroFilterFactoryBean配置是或報(bào)錯(cuò)的 , 因?yàn)樵诔跏蓟疭hiroFilterFactoryBean的時(shí)候需要注入:SecurityManager
*
* FilterChain定義說(shuō)明
* 1 . 一個(gè)URL可以配置多個(gè)Filter , 使用逗號(hào)分隔
* 2 . 當(dāng)設(shè)置多個(gè)過(guò)濾器時(shí) , 全部驗(yàn)證通過(guò) , 才視為通過(guò)
* 3 . 部分過(guò)濾器可指定參數(shù) , 如perms , roles
*
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
shiroFilterFactoryBean.setLoginUrl("/login");//不設(shè)置默認(rèn)找web工程根目錄下的login.jsp頁(yè)面
shiroFilterFactoryBean.setSuccessUrl("/index");//登錄成功之后要跳轉(zhuǎn)的連接
shiroFilterFactoryBean.setUnauthorizedUrl("/403");//未授權(quán)跳轉(zhuǎn)頁(yè)面
/* //自定義攔截器 , 多個(gè)filter的設(shè)置 */
// Map<String, Filter> filters = new LinkedHashMap<>();
// LogoutFilter logoutFilter = new LogoutFilter();//限制同一帳號(hào)同時(shí)在線的個(gè)數(shù)?;騿吸c(diǎn)登錄等
// logoutFilter.setRedirectUrl("/login");
// filters.put("logout",null);
// shiroFilterFactoryBean.setFilters(filters);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//filterChainDefinitionManager必須是LinkedHashMap因?yàn)樗仨毐WC有序
filterChainDefinitionMap.put("/css/**", "anon");//靜態(tài)資源不要求權(quán)限 , 若有其他目錄下文件(如js,img等)也依此設(shè)置
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/login", "anon");//配置不需要權(quán)限訪問(wèn)的部分url
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/user/**", "authc,roles[ROLE_USER]");//用戶(hù)為ROLE_USER 角色可以訪問(wèn) . 由用戶(hù)角色控制用戶(hù)行為 .
filterChainDefinitionMap.put("/events/**", "authc,roles[ROLE_ADMIN]");
// filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");// 這里為了測(cè)試 , 固定寫(xiě)死的值 , 也可以從數(shù)據(jù)庫(kù)或其他配置中讀取 , 此處是用權(quán)限控制
filterChainDefinitionMap.put("/**", "authc");//需要登錄訪問(wèn)的資源 , 一般將/**放在最下邊
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//region Cookie及Session
// ==================== Cookie及Session管理 begin ====================
private static final String COOKIE_NAME = "rememberMe";
/** cookie對(duì)象管理 */
public SimpleCookie rememberMeCookie(){
//這個(gè)參數(shù)是cookie的名稱(chēng),對(duì)應(yīng)前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie(COOKIE_NAME);
simpleCookie.setMaxAge(604800);//記住我cookie生效時(shí)間7天 ,單位秒
return simpleCookie;
}
/** cookie管理對(duì)象 : 記住我功能 */
public CookieRememberMeManager rememberMeManager(){
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));//rememberMe cookie加密的密鑰 建議每個(gè)項(xiàng)目都不一樣 默認(rèn)AES算法 密鑰長(zhǎng)度(128 256 512 位)
return cookieRememberMeManager;
}
@Bean
SessionDAO sessionDAO() {
return new MemorySessionDAO();
}
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
Collection<SessionListener> listeners = new ArrayList<>();
listeners.add(new BDSessionListener());
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionDAO(sessionDAO());
return sessionManager;
}
// ==================== Cookie及Session管理 end ====================
//endregion
/**
* SecurityManager : 核心安全事務(wù)管理器 , 權(quán)限管理
* 這個(gè)類(lèi)組合了登陸 , 登出 , 權(quán)限 , session的處理 . 是個(gè)比較重要的類(lèi) .
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
securityManager.setCacheManager(ehCacheManager());////用戶(hù)授權(quán)/認(rèn)證信息Cache, 采用EhCache 緩存
// 自定義session管理 使用redis
securityManager.setSessionManager(sessionManager());
//注入記住我管理器;
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* ShiroRealm , 這是個(gè)自定義的認(rèn)證類(lèi) , 繼承自AuthorizingRealm ,
* 負(fù)責(zé)用戶(hù)的認(rèn)證和權(quán)限的處理 , 可以參考JdbcRealm的實(shí)現(xiàn) .
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public ShiroRealm shiroRealm(CredentialsMatcher matcher) {
ShiroRealm realm = new ShiroRealm();
realm.setCredentialsMatcher(matcher);//密碼校驗(yàn)實(shí)現(xiàn)
return realm;
}
/**
* EhCacheManager , 緩存管理
* 用戶(hù)登陸成功后 , 把用戶(hù)信息和權(quán)限信息緩存起來(lái) , 然后每次用戶(hù)請(qǐng)求時(shí) , 放入用戶(hù)的session中 , 如果不設(shè)置這個(gè)bean , 每個(gè)請(qǐng)求都會(huì)查詢(xún)一次數(shù)據(jù)庫(kù) .
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public EhCacheManager ehCacheManager() {
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:config/ehcache.xml");//配置文件路徑
return em;
}
/**
* LifecycleBeanPostProcessor , 這是個(gè)DestructionAwareBeanPostProcessor的子類(lèi) ,
* 負(fù)責(zé)org.apache.shiro.util.Initializable類(lèi)型bean的生命周期的 , 初始化和銷(xiāo)毀 .
* 主要是AuthorizingRealm類(lèi)的子類(lèi) , 以及EhCacheManager類(lèi) .
*/
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* HashedCredentialsMatcher , 這個(gè)類(lèi)是為了對(duì)密碼進(jìn)行編碼的 ,
* 防止密碼在數(shù)據(jù)庫(kù)里明碼保存 , 當(dāng)然在登陸認(rèn)證的時(shí)候 ,
* 這個(gè)類(lèi)也負(fù)責(zé)對(duì)form里輸入的密碼進(jìn)行編碼
* 處理認(rèn)證匹配處理器:如果自定義需要實(shí)現(xiàn)繼承HashedCredentialsMatcher
*/
@Bean(name = "hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");//指定加密方式方式,也可以在這里加入緩存,當(dāng)用戶(hù)超過(guò)五次登陸錯(cuò)誤就鎖定該用戶(hù)禁止不斷嘗試登陸
credentialsMatcher.setHashIterations(2);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
/**
* AuthorizationAttributeSourceAdvisor , shiro里實(shí)現(xiàn)的Advisor類(lèi) ,
* 內(nèi)部使用AopAllianceAnnotationsAuthorizingMethodInterceptor來(lái)攔截用以下注解的方法 .
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager());
return advisor;
}
//thymeleaf模板引擎和shiro整合時(shí)使用
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
3 . 自定義Realm驗(yàn)證類(lèi)
package com.yiyun.web.system.config;
import com.yiyun.dao.master.UserDao;
import com.yiyun.domain.UserDO;
import com.yiyun.web.common.utils.ShiroUtils;
import com.yiyun.web.system.service.MenuService;
import org.apache.shiro.SecurityUtils;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.*;
/**
* 獲取用戶(hù)的角色和權(quán)限信息
*/
public class ShiroRealm extends AuthorizingRealm {
// 一般這里都寫(xiě)的是servic
@Autowired
private UserDao userMapper;
@Autowired
private MenuService menuService;
/**
* 登錄認(rèn)證 一般情況下 , 登錄成功之后就給當(dāng)前用戶(hù)進(jìn)行權(quán)限賦予了
* 根據(jù)用戶(hù)的權(quán)限信息做授權(quán)判斷,這一步是以doGetAuthenticationInfo為基礎(chǔ)的,只有在有用戶(hù)信息后才能根據(jù)用戶(hù)的角色和授權(quán)信息做判斷是否給用戶(hù)授權(quán),因此這里的Roles和Permissions是用戶(hù)的兩個(gè)重點(diǎn)判斷依據(jù)
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
UserDo user = userMapper.findByName(token.getUsername());//查出是否有此用戶(hù)
if(user != null){
// 若存在,將此用戶(hù)存放到登錄認(rèn)證info中,無(wú)需自己做密碼對(duì)比,Shiro會(huì)為我們進(jìn)行密碼對(duì)比校驗(yàn)
List<URole> rlist = uRoleDao.findRoleByUid(user.getId());//獲取用戶(hù)角色
List<UPermission> plist = uPermissionDao.findPermissionByUid(user.getId());//獲取用戶(hù)權(quán)限
List<String> roleStrlist=new ArrayList<String>();////用戶(hù)的角色集合
List<String> perminsStrlist=new ArrayList<String>();//用戶(hù)的權(quán)限集合
for (URole role : rlist) {
roleStrlist.add(role.getName());
}
for (UPermission uPermission : plist) {
perminsStrlist.add(uPermission.getName());
}
user.setRoleStrlist(roleStrlist);
user.setPerminsStrlist(perminsStrlist);
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("user", user);//成功則放入session
// 若存在,將此用戶(hù)存放到登錄認(rèn)證info中,無(wú)需自己做密碼對(duì)比,Shiro會(huì)為我們進(jìn)行密碼對(duì)比校驗(yàn)
return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
}
return null;
}
/**
* 權(quán)限認(rèn)證
* 獲取用戶(hù)的權(quán)限信息,這是為下一步的授權(quán)做判斷,獲取當(dāng)前用戶(hù)的角色和這些角色所擁有的權(quán)限信息
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//獲取當(dāng)前登錄輸入的用戶(hù)名,等價(jià)于(String) principalCollection.fromRealm(getName()).iterator().next();
// String loginName = (String) super.getAvailablePrincipal(principalCollection);
UserDo user = (UserDo) principalCollection.getPrimaryPrincipal();
// //到數(shù)據(jù)庫(kù)查是否有此對(duì)象
// User user = null;// 實(shí)際項(xiàng)目中,這里可以根據(jù)實(shí)際情況做緩存,如果不做,Shiro自己也是有時(shí)間間隔機(jī)制,2分鐘內(nèi)不會(huì)重復(fù)執(zhí)行該方法
// user = userMapper.findByName(loginName);
if (user != null) {
//權(quán)限信息對(duì)象info,用來(lái)存放查出的用戶(hù)的所有的角色(role)及權(quán)限(permission)
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//用戶(hù)的角色集合
info.addRoles(user.getRoleStrlist());
//用戶(hù)的權(quán)限集合
info.addStringPermissions(user.getPerminsStrlist());
return info;
}
// 返回null的話,就會(huì)導(dǎo)致任何用戶(hù)訪問(wèn)被攔截的請(qǐng)求時(shí),都會(huì)自動(dòng)跳轉(zhuǎn)到unauthorizedUrl指定的地址
return null;
}
}
4 . 最后看一下ehcache的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="java.io.tmpdir/Tmp_EhCache" />
<!--
name:緩存名稱(chēng)。
maxElementsInMemory:緩存最大數(shù)目
maxElementsOnDisk:硬盤(pán)最大緩存?zhèn)€數(shù)。
eternal:對(duì)象是否永久有效,一但設(shè)置了,timeout將不起作用。
overflowToDisk:是否保存到磁盤(pán),當(dāng)系統(tǒng)當(dāng)機(jī)時(shí)
timeToIdleSeconds:設(shè)置對(duì)象在失效前的允許閑置時(shí)間(單位:秒)。僅當(dāng)eternal=false對(duì)象不是永久有效時(shí)使用,可選屬性,默認(rèn)值是0,也就是可閑置時(shí)間無(wú)窮大。
timeToLiveSeconds:設(shè)置對(duì)象在失效前允許存活時(shí)間(單位:秒)。最大時(shí)間介于創(chuàng)建時(shí)間和失效時(shí)間之間。僅當(dāng)eternal=false對(duì)象不是永久有效時(shí)使用,默認(rèn)是0.,也就是對(duì)象存活時(shí)間無(wú)窮大。
diskPersistent:是否緩存虛擬機(jī)重啟期數(shù)據(jù) Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:這個(gè)參數(shù)設(shè)置DiskStore(磁盤(pán)緩存)的緩存區(qū)大小。默認(rèn)是30MB。每個(gè)Cache都應(yīng)該有自己的一個(gè)緩沖區(qū)。
diskExpiryThreadIntervalSeconds:磁盤(pán)失效線程運(yùn)行時(shí)間間隔,默認(rèn)是120秒。
memoryStoreEvictionPolicy:當(dāng)達(dá)到maxElementsInMemory限制時(shí),Ehcache將會(huì)根據(jù)指定的策略去清理內(nèi)存。默認(rèn)策略是LRU(最近最少使用)。你可以設(shè)置為FIFO(先進(jìn)先出)或是LFU(較少使用)。
clearOnFlush:內(nèi)存數(shù)量最大時(shí)是否清除。
memoryStoreEvictionPolicy:
Ehcache的三種清空策略;
FIFO,first in first out,這個(gè)是大家最熟的,先進(jìn)先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點(diǎn)就是講一直以來(lái)最少被使用的。如上面所講,緩存的元素有一個(gè)hit屬性,hit值最小的將會(huì)被清出緩存。
LRU,Least Recently Used,最近最少使用的,緩存的元素有一個(gè)時(shí)間戳,當(dāng)緩存容量滿(mǎn)了,而又需要騰出地方來(lái)緩存新的元素的時(shí)候,那么現(xiàn)有緩存元素中時(shí)間戳離當(dāng)前時(shí)間最遠(yuǎn)的元素將被清出緩存。
-->
<defaultCache eternal="false" maxElementsInMemory="1000"
overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0"
timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU" />
<!-- 登錄記錄緩存鎖定10分鐘 -->
<cache name="passwordRetryCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
</ehcache>
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
java實(shí)現(xiàn)二維碼掃碼授權(quán)登陸
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)二維碼掃碼授權(quán)登陸,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
因Spring AOP導(dǎo)致@Autowired依賴(lài)注入失敗的解決方法
這篇文章主要給大家介紹了因Spring AOP導(dǎo)致@Autowired依賴(lài)注入失敗的解決方法,文中通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-07-07
Apache?Hudi異步Clustering部署操作的掌握
這篇文章主要介紹了Apache?Hudi異步Clustering部署操作的掌握,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-03-03
Java ThreadLocal 線程安全問(wèn)題解決方案
這篇文章主要介紹了Java ThreadLocal 線程安全問(wèn)題解決方案的相關(guān)資料,需要的朋友可以參考下2016-09-09
淺析Java中靜態(tài)代理和動(dòng)態(tài)代理的應(yīng)用與區(qū)別
代理模式在我們生活中很常見(jiàn),而Java中常用的兩個(gè)的代理模式就是動(dòng)態(tài)代理與靜態(tài)代理,這篇文章主要為大家介紹了二者的應(yīng)用與區(qū)別,需要的可以參考下2023-08-08
Java實(shí)現(xiàn)用戶(hù)不可重復(fù)登錄功能
這篇文章主要介紹了Java實(shí)現(xiàn)用戶(hù)不可重復(fù)登錄功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2016-12-12

