SpringBoot 整合Shiro實(shí)現(xiàn)動(dòng)態(tài)權(quán)限加載更新+Session共享+單點(diǎn)登錄
武哥領(lǐng)讀:
有源碼的教程,不會(huì)的同學(xué)下載源碼,根據(jù)教程學(xué)一下哈~
作者:Sans_
juejin.im/post/5d087d605188256de9779e64
一. 說明
Shiro 是一個(gè)安全框架, 項(xiàng)目中主要用它做認(rèn)證, 授權(quán), 加密, 以及用戶的會(huì)話管理, 雖然 Shiro 沒有 SpringSecurity 功能更豐富, 但是它輕量, 簡(jiǎn)單, 在項(xiàng)目中通常業(yè)務(wù)需求 Shiro 也都能勝任.
二. 項(xiàng)目環(huán)境
MyBatis-Plus 版本: 3.1.0
SpringBoot 版本: 2.1.5
JDK 版本: 1.8
Shiro 版本: 1.4
Shiro-redis 插件版本: 3.1.0
數(shù)據(jù)表 (SQL 文件在項(xiàng)目中): 數(shù)據(jù)庫(kù)中測(cè)試號(hào)的密碼進(jìn)行了加密, 密碼皆為 123456

Maven 依賴如下:
????????
????????????org.springframework.boot
????????????spring-boot-starter-web
????????
????????
????????????mysql
????????????mysql-connector-java
????????????runtime
????????
????????
????????
????????????org.springframework.boot
????????????spring-boot-starter-aop
????????
????????
????????
????????????org.projectlombok
????????????lombok
????????????true
????????
????????
????????
????????????org.springframework.boot
????????????spring-boot-starter-data-redis-reactive
????????
????????
????????
????????????com.baomidou
????????????mybatis-plus-boot-starter
????????????3.1.0
????????
????????
????????
????????????com.alibaba
????????????druid
????????????1.1.6
????????
????????
????????
????????????org.apache.shiro
????????????shiro-spring
????????????1.4.0
????????
????????
????????
????????????org.crazycake
????????????shiro-redis
????????????3.1.0
????????
????????
????????
????????????org.apache.commons
????????????commons-lang3
????????????3.5
????????
配置如下:
#?配置端口
server:
??port:?8764
spring:
??#?配置數(shù)據(jù)源
??datasource:
????driver-class-name:?com.mysql.cj.jdbc.Driver
????url:?jdbc:mysql://localhost:3306/my_shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
????username:?root
????password:?root
????type:?com.alibaba.druid.pool.DruidDataSource
??#?Redis數(shù)據(jù)源
??redis:
????host:?localhost
????port:?6379
????timeout:?6000
????password:?123456
????jedis:
??????pool:
????????max-active:?1000??#?連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
????????max-wait:?-1??????#?連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制)
????????max-idle:?10??????#?連接池中的最大空閑連接
????????min-idle:?5???????#?連接池中的最小空閑連接
#?mybatis-plus相關(guān)配置
mybatis-plus:
??#?xml掃描,多個(gè)目錄用逗號(hào)或者分號(hào)分隔(告訴?Mapper?所對(duì)應(yīng)的?XML?文件位置)
??mapper-locations:?classpath:mapper/*.xml
??#?以下配置均有默認(rèn)值,可以不設(shè)置
??global-config:
????db-config:
??????#主鍵類型?AUTO:"數(shù)據(jù)庫(kù)ID自增"?INPUT:"用戶輸入ID",ID_WORKER:"全局唯一ID?(數(shù)字類型唯一ID)",?UUID:"全局唯一ID?UUID";
??????id-type:?auto
??????#字段策略?IGNORED:"忽略判斷"??NOT_NULL:"非?NULL?判斷")??NOT_EMPTY:"非空判斷"
??????field-strategy:?NOT_EMPTY
??????#數(shù)據(jù)庫(kù)類型
??????db-type:?MYSQL
??configuration:
????#?是否開啟自動(dòng)駝峰命名規(guī)則映射:從數(shù)據(jù)庫(kù)列名到Java屬性駝峰命名的類似映射
????map-underscore-to-camel-case:?true
????#?如果查詢結(jié)果中包含空值的列,則?MyBatis?在映射的時(shí)候,不會(huì)映射這個(gè)字段
????call-setters-on-nulls:?true
????#?這個(gè)配置會(huì)將執(zhí)行的sql打印出來,在開發(fā)或測(cè)試的時(shí)候可以用
????log-impl:?org.apache.ibatis.logging.stdout.StdOutImpl
二. 編寫項(xiàng)目基礎(chǔ)類
用戶實(shí)體, Dao,Service 等在這里省略, 請(qǐng)參考源碼
編寫 Exception 類來處理 Shiro 權(quán)限攔截異常
創(chuàng)建 SHA256Util 加密工具
創(chuàng)建 Spring 工具
/**
?*?@Description?Spring上下文工具類
?*?@Author?Sans
?*?@CreateTime?2019/6/17?13:40
?*/
@Component
public?class?SpringUtil?implements?ApplicationContextAware?{
????private?static?ApplicationContext?context;
????/**
?????*?Spring在bean初始化后會(huì)判斷是不是ApplicationContextAware的子類
?????*?如果該類是,setApplicationContext()方法,會(huì)將容器中ApplicationContext作為參數(shù)傳入進(jìn)去
?????*?@Author?Sans
?????*?@CreateTime?2019/6/17?16:58
?????*/
????@Override
????public?void?setApplicationContext(ApplicationContext?applicationContext)?throws?BeansException?{
????????context?=?applicationContext;
????}
????/**
?????*?通過Name返回指定的Bean
?????*?@Author?Sans
?????*?@CreateTime?2019/6/17?16:03
?????*/
????public?static??T?getBean(Class?beanClass) ?{
????????return?context.getBean(beanClass);
????}
}
創(chuàng)建 Shiro 工具
/**
?*?@Description?Shiro工具類
?*?@Author?Sans
?*?@CreateTime?2019/6/15?16:11
?*/
public?class?ShiroUtils?{
????/**?私有構(gòu)造器?**/
????private?ShiroUtils(){}
????private?static?RedisSessionDAO?redisSessionDAO?=?SpringUtil.getBean(RedisSessionDAO.class);
????/**
?????*?獲取當(dāng)前用戶Session
?????*?@Author?Sans
?????*?@CreateTime?2019/6/17?17:03
?????*?@Return?SysUserEntity?用戶信息
?????*/
????public?static?Session?getSession()?{
????????return?SecurityUtils.getSubject().getSession();
????}
????/**
?????*?用戶登出
?????*?@Author?Sans
?????*?@CreateTime?2019/6/17?17:23
?????*/
????public?static?void?logout()?{
????????SecurityUtils.getSubject().logout();
????}
????/**
????*?獲取當(dāng)前用戶信息
????*?@Author?Sans
????*?@CreateTime?2019/6/17?17:03
????*?@Return?SysUserEntity?用戶信息
????*/
????public?static?SysUserEntity?getUserInfo()?{
??????return?(SysUserEntity)?SecurityUtils.getSubject().getPrincipal();
????}
????/**
?????*?刪除用戶緩存信息
?????*?@Author?Sans
?????*?@CreateTime?2019/6/17?13:57
?????*?@Param??username??用戶名稱
?????*?@Param??isRemoveSession?是否刪除Session
?????*?@Return?void
?????*/
????public?static?void?deleteCache(String?username,?boolean?isRemoveSession){
????????//從緩存中獲取Session
????????Session?session?=?null;
????????Collection?sessions?=?redisSessionDAO.getActiveSessions();
????????SysUserEntity?sysUserEntity;
????????Object?attribute?=?null;
????????for(Session?sessionInfo?:?sessions){
????????????//遍歷Session,找到該用戶名稱對(duì)應(yīng)的Session
????????????attribute?=?sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
????????????if?(attribute?==?null)?{
????????????????continue;
????????????}
????????????sysUserEntity?=?(SysUserEntity)?((SimplePrincipalCollection)?attribute).getPrimaryPrincipal();
????????????if?(sysUserEntity?==?null)?{
????????????????continue;
????????????}
????????????if?(Objects.equals(sysUserEntity.getUsername(),?username))?{
????????????????session=sessionInfo;
????????????}
????????}
????????if?(session?==?null||attribute?==?null)?{
????????????return;
????????}
????????//刪除session
????????if?(isRemoveSession)?{
????????????redisSessionDAO.delete(session);
????????}
????????//刪除Cache,在訪問受限接口時(shí)會(huì)重新授權(quán)
????????DefaultWebSecurityManager?securityManager?=?(DefaultWebSecurityManager)?SecurityUtils.getSecurityManager();
????????Authenticator?authc?=?securityManager.getAuthenticator();
????????((LogoutAware)?authc).onLogout((SimplePrincipalCollection)?attribute);
????}
}
創(chuàng)建 Shiro 的 SessionId 生成器
三. 編寫 Shiro 核心類
創(chuàng)建 Realm 用于授權(quán)和認(rèn)證
/**
?*?@Description?Shiro權(quán)限匹配和賬號(hào)密碼匹配
?*?@Author?Sans
?*?@CreateTime?2019/6/15?11:27
?*/
public?class?ShiroRealm?extends?AuthorizingRealm?{
????@Autowired
????private?SysUserService?sysUserService;
????@Autowired
????private?SysRoleService?sysRoleService;
????@Autowired
????private?SysMenuService?sysMenuService;
????/**
?????*?授權(quán)權(quán)限
?????*?用戶進(jìn)行權(quán)限驗(yàn)證時(shí)候Shiro會(huì)去緩存中找,如果查不到數(shù)據(jù),會(huì)執(zhí)行這個(gè)方法去查權(quán)限,并放入緩存中
?????*?@Author?Sans
?????*?@CreateTime?2019/6/12?11:44
?????*/
????@Override
????protected?AuthorizationInfo?doGetAuthorizationInfo(PrincipalCollection?principalCollection)?{
????????SimpleAuthorizationInfo?authorizationInfo?=?new?SimpleAuthorizationInfo();
????????SysUserEntity?sysUserEntity?=?(SysUserEntity)?principalCollection.getPrimaryPrincipal();
????????//獲取用戶ID
????????Long?userId?=sysUserEntity.getUserId();
????????//這里可以進(jìn)行授權(quán)和處理
????????Set?rolesSet?=?new?HashSet<>();
????????Set?permsSet?=?new?HashSet<>();
????????//查詢角色和權(quán)限(這里根據(jù)業(yè)務(wù)自行查詢)
????????List?sysRoleEntityList?=?sysRoleService.selectSysRoleByUserId(userId);
????????for?(SysRoleEntity?sysRoleEntity:sysRoleEntityList)?{
????????????rolesSet.add(sysRoleEntity.getRoleName());
????????????List?sysMenuEntityList?=?sysMenuService.selectSysMenuByRoleId(sysRoleEntity.getRoleId());
????????????for?(SysMenuEntity?sysMenuEntity?:sysMenuEntityList)?{
????????????????permsSet.add(sysMenuEntity.getPerms());
????????????}
????????}
????????//將查到的權(quán)限和角色分別傳入authorizationInfo中
????????authorizationInfo.setStringPermissions(permsSet);
????????authorizationInfo.setRoles(rolesSet);
????????return?authorizationInfo;
????}
????/**
?????*?身份認(rèn)證
?????*?@Author?Sans
?????*?@CreateTime?2019/6/12?12:36
?????*/
????@Override
????protected?AuthenticationInfo?doGetAuthenticationInfo(AuthenticationToken?authenticationToken)?throws?AuthenticationException?{
????????//獲取用戶的輸入的賬號(hào).
????????String?username?=?(String)?authenticationToken.getPrincipal();
????????//通過username從數(shù)據(jù)庫(kù)中查找?User對(duì)象,如果找到進(jìn)行驗(yàn)證
????????//實(shí)際項(xiàng)目中,這里可以根據(jù)實(shí)際情況做緩存,如果不做,Shiro自己也是有時(shí)間間隔機(jī)制,2分鐘內(nèi)不會(huì)重復(fù)執(zhí)行該方法
????????SysUserEntity?user?=?sysUserService.selectUserByName(username);
????????//判斷賬號(hào)是否存在
????????if?(user?==?null)?{
????????????throw?new?AuthenticationException();
????????}
????????//判斷賬號(hào)是否被凍結(jié)
????????if?(user.getState()==null||user.getState().equals("PROHIBIT")){
????????????throw?new?LockedAccountException();
????????}
????????//進(jìn)行驗(yàn)證
????????SimpleAuthenticationInfo?authenticationInfo?=?new?SimpleAuthenticationInfo(
????????????????user,??????????????????????????????????//用戶名
????????????????user.getPassword(),????????????????????//密碼
????????????????ByteSource.Util.bytes(user.getSalt()),?//設(shè)置鹽值
????????????????getName()
????????);
????????//驗(yàn)證成功開始踢人(清除緩存和Session)
????????ShiroUtils.deleteCache(username,true);
????????return?authenticationInfo;
????}
}
創(chuàng)建 SessionManager 類
創(chuàng)建 ShiroConfig 配置類
/**
?*?@Description?Shiro配置類
?*?@Author?Sans
?*?@CreateTime?2019/6/10?17:42
?*/
@Configuration
public?class?ShiroConfig?{
????private?final?String?CACHE_KEY?=?"shiro:cache:";
????private?final?String?SESSION_KEY?=?"shiro:session:";
????private?final?int?EXPIRE?=?1800;
????//Redis配置
????@Value("${spring.redis.host}")
????private?String?host;
????@Value("${spring.redis.port}")
????private?int?port;
????@Value("${spring.redis.timeout}")
????private?int?timeout;
????@Value("${spring.redis.password}")
????private?String?password;
????/**
?????*?開啟Shiro-aop注解支持
?????*?@Attention?使用代理方式所以需要開啟代碼支持
?????*?@Author?Sans
?????*?@CreateTime?2019/6/12?8:38
?????*/
????@Bean
????public?AuthorizationAttributeSourceAdvisor?authorizationAttributeSourceAdvisor(SecurityManager?securityManager)?{
????????AuthorizationAttributeSourceAdvisor?authorizationAttributeSourceAdvisor?=?new?AuthorizationAttributeSourceAdvisor();
????????authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
????????return?authorizationAttributeSourceAdvisor;
????}
????/**
?????*?Shiro基礎(chǔ)配置
?????*?@Author?Sans
?????*?@CreateTime?2019/6/12?8:42
?????*/
????@Bean
????public?ShiroFilterFactoryBean?shiroFilterFactory(SecurityManager?securityManager){
????????ShiroFilterFactoryBean?shiroFilterFactoryBean?=?new?ShiroFilterFactoryBean();
????????shiroFilterFactoryBean.setSecurityManager(securityManager);
????????Map?filterChainDefinitionMap?=?new?LinkedHashMap<>();
????????//?注意過濾器配置順序不能顛倒
????????//?配置過濾:不會(huì)被攔截的鏈接
????????filterChainDefinitionMap.put("/static/**",?"anon");
????????filterChainDefinitionMap.put("/userLogin/**",?"anon");
????????filterChainDefinitionMap.put("/**",?"authc");
????????//?配置shiro默認(rèn)登錄界面地址,前后端分離中登錄界面跳轉(zhuǎn)應(yīng)由前端路由控制,后臺(tái)僅返回json數(shù)據(jù)
????????shiroFilterFactoryBean.setLoginUrl("/userLogin/unauth");
????????shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
????????return?shiroFilterFactoryBean;
????}
????/**
?????*?安全管理器
?????*?@Author?Sans
?????*?@CreateTime?2019/6/12?10:34
?????*/
????@Bean
????public?SecurityManager?securityManager()?{
????????DefaultWebSecurityManager?securityManager?=?new?DefaultWebSecurityManager();
????????//?自定義Ssession管理
????????securityManager.setSessionManager(sessionManager());
????????//?自定義Cache實(shí)現(xiàn)
????????securityManager.setCacheManager(cacheManager());
????????//?自定義Realm驗(yàn)證
????????securityManager.setRealm(shiroRealm());
????????return?securityManager;
????}
????/**
?????*?身份驗(yàn)證器
?????*?@Author?Sans
?????*?@CreateTime?2019/6/12?10:37
?????*/
????@Bean
????public?ShiroRealm?shiroRealm()?{
????????ShiroRealm?shiroRealm?=?new?ShiroRealm();
????????shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
????????return?shiroRealm;
????}
????/**
?????*?憑證匹配器
?????*?將密碼校驗(yàn)交給Shiro的SimpleAuthenticationInfo進(jìn)行處理,在這里做匹配配置
?????*?@Author?Sans
?????*?@CreateTime?2019/6/12?10:48
?????*/
????@Bean
????public?HashedCredentialsMatcher?hashedCredentialsMatcher()?{
????????HashedCredentialsMatcher?shaCredentialsMatcher?=?new?HashedCredentialsMatcher();
????????//?散列算法:這里使用SHA256算法;
????????shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);
????????//?散列的次數(shù),比如散列兩次,相當(dāng)于?md5(md5(""));
????????shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);
????????return?shaCredentialsMatcher;
????}
????/**
?????*?配置Redis管理器
?????*?@Attention?使用的是shiro-redis開源插件
?????*?@Author?Sans
?????*?@CreateTime?2019/6/12?11:06
?????*/
????@Bean
????public?RedisManager?redisManager()?{
????????RedisManager?redisManager?=?new?RedisManager();
????????redisManager.setHost(host);
????????redisManager.setPort(port);
????????redisManager.setTimeout(timeout);
????????redisManager.setPassword(password);
????????return?redisManager;
????}
????/**
?????*?配置Cache管理器
?????*?用于往Redis存儲(chǔ)權(quán)限和角色標(biāo)識(shí)
?????*?@Attention?使用的是shiro-redis開源插件
?????*?@Author?Sans
?????*?@CreateTime?2019/6/12?12:37
?????*/
????@Bean
????public?RedisCacheManager?cacheManager()?{
????????RedisCacheManager?redisCacheManager?=?new?RedisCacheManager();
????????redisCacheManager.setRedisManager(redisManager());
????????redisCacheManager.setKeyPrefix(CACHE_KEY);
????????//?配置緩存的話要求放在session里面的實(shí)體類必須有個(gè)id標(biāo)識(shí)
????????redisCacheManager.setPrincipalIdFieldName("userId");
????????return?redisCacheManager;
????}
????/**
?????*?SessionID生成器
?????*?@Author?Sans
?????*?@CreateTime?2019/6/12?13:12
?????*/
????@Bean
????public?ShiroSessionIdGenerator?sessionIdGenerator(){
????????return?new?ShiroSessionIdGenerator();
????}
????/**
?????*?配置RedisSessionDAO
?????*?@Attention?使用的是shiro-redis開源插件
?????*?@Author?Sans
?????*?@CreateTime?2019/6/12?13:44
?????*/
????@Bean
????public?RedisSessionDAO?redisSessionDAO()?{
????????RedisSessionDAO?redisSessionDAO?=?new?RedisSessionDAO();
????????redisSessionDAO.setRedisManager(redisManager());
????????redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
????????redisSessionDAO.setKeyPrefix(SESSION_KEY);
????????redisSessionDAO.setExpire(expire);
????????return?redisSessionDAO;
????}
????/**
?????*?配置Session管理器
?????*?@Author?Sans
?????*?@CreateTime?2019/6/12?14:25
?????*/
????@Bean
????public?SessionManager?sessionManager()?{
????????ShiroSessionManager?shiroSessionManager?=?new?ShiroSessionManager();
????????shiroSessionManager.setSessionDAO(redisSessionDAO());
????????return?shiroSessionManager;
????}
}
四. 實(shí)現(xiàn)權(quán)限控制
Shiro 可以用代碼或者注解來控制權(quán)限, 通常我們使用注解控制, 不僅簡(jiǎn)單方便, 而且更加靈活. Shiro 注解一共有五個(gè):
一般情況下我們?cè)陧?xiàng)目中做權(quán)限控制, 使用最多的是 RequiresPermissions 和 RequiresRoles, 允許存在多個(gè)角色和權(quán)限, 默認(rèn)邏輯是 AND, 也就是同時(shí)擁有這些才可以訪問方法, 可以在注解中以參數(shù)的形式設(shè)置成 OR
示例

使用順序: Shiro 注解是存在順序的, 當(dāng)多個(gè)注解在一個(gè)方法上的時(shí)候, 會(huì)逐個(gè)檢查, 知道全部通過為止, 默認(rèn)攔截順序是: RequiresRoles->RequiresPermissions->RequiresAuthentication->
RequiresUser->RequiresGuest
示例

創(chuàng)建 UserRoleController 角色攔截測(cè)試類
/**
?*?@Description?角色測(cè)試
?*?@Author?Sans
?*?@CreateTime?2019/6/19?11:38
?*/
@RestController
@RequestMapping("/role")
public?class?UserRoleController?{
????@Autowired
????private?SysUserService?sysUserService;
????@Autowired
????private?SysRoleService?sysRoleService;
????@Autowired
????private?SysMenuService?sysMenuService;
????@Autowired
????private?SysRoleMenuService?sysRoleMenuService;
????/**
?????*?管理員角色測(cè)試接口
?????*?@Author?Sans
?????*?@CreateTime?2019/6/19?10:38
?????*?@Return?Map?返回結(jié)果
?????*/
????@RequestMapping("/getAdminInfo")
????@RequiresRoles("ADMIN")
????public?Map?getAdminInfo(){
????????Map?map?=?new?HashMap<>();
????????map.put("code",200);
????????map.put("msg","這里是只有管理員角色能訪問的接口");
????????return?map;
????}
????/**
?????*?用戶角色測(cè)試接口
?????*?@Author?Sans
?????*?@CreateTime?2019/6/19?10:38
?????*?@Return?Map?返回結(jié)果
?????*/
????@RequestMapping("/getUserInfo")
????@RequiresRoles("USER")
????public?Map?getUserInfo(){
????????Map?map?=?new?HashMap<>();
????????map.put("code",200);
????????map.put("msg","這里是只有用戶角色能訪問的接口");
????????return?map;
????}
????/**
?????*?角色測(cè)試接口
?????*?@Author?Sans
?????*?@CreateTime?2019/6/19?10:38
?????*?@Return?Map?返回結(jié)果
?????*/
????@RequestMapping("/getRoleInfo")
????@RequiresRoles(value={"ADMIN","USER"},logical?=?Logical.OR)
????@RequiresUser
????public?Map?getRoleInfo(){
????????Map?map?=?new?HashMap<>();
????????map.put("code",200);
????????map.put("msg","這里是只要有ADMIN或者USER角色能訪問的接口");
????????return?map;
????}
????/**
?????*?登出(測(cè)試登出)
?????*?@Author?Sans
?????*?@CreateTime?2019/6/19?10:38
?????*?@Return?Map?返回結(jié)果
?????*/
????@RequestMapping("/getLogout")
????@RequiresUser
????public?Map?getLogout(){
????????ShiroUtils.logout();
????????Map?map?=?new?HashMap<>();
????????map.put("code",200);
????????map.put("msg","登出");
????????return?map;
????}
}
創(chuàng)建 UserMenuController 權(quán)限攔截測(cè)試類
/**
?*?@Description?權(quán)限測(cè)試
?*?@Author?Sans
?*?@CreateTime?2019/6/19?11:38
?*/
@RestController
@RequestMapping("/menu")
public?class?UserMenuController?{
????@Autowired
????private?SysUserService?sysUserService;
????@Autowired
????private?SysRoleService?sysRoleService;
????@Autowired
????private?SysMenuService?sysMenuService;
????@Autowired
????private?SysRoleMenuService?sysRoleMenuService;
????/**
?????*?獲取用戶信息集合
?????*?@Author?Sans
?????*?@CreateTime?2019/6/19?10:36
?????*?@Return?Map?返回結(jié)果
?????*/
????@RequestMapping("/getUserInfoList")
????@RequiresPermissions("sys:user:info")
????public?Map?getUserInfoList(){
????????Map?map?=?new?HashMap<>();
????????List?sysUserEntityList?=?sysUserService.list();
????????map.put("sysUserEntityList",sysUserEntityList);
????????return?map;
????}
????/**
?????*?獲取角色信息集合
?????*?@Author?Sans
?????*?@CreateTime?2019/6/19?10:37
?????*?@Return?Map?返回結(jié)果
?????*/
????@RequestMapping("/getRoleInfoList")
????@RequiresPermissions("sys:role:info")
????public?Map?getRoleInfoList(){
????????Map?map?=?new?HashMap<>();
????????List?sysRoleEntityList?=?sysRoleService.list();
????????map.put("sysRoleEntityList",sysRoleEntityList);
????????return?map;
????}
????/**
?????*?獲取權(quán)限信息集合
?????*?@Author?Sans
?????*?@CreateTime?2019/6/19?10:38
?????*?@Return?Map?返回結(jié)果
?????*/
????@RequestMapping("/getMenuInfoList")
????@RequiresPermissions("sys:menu:info")
????public?Map?getMenuInfoList(){
????????Map?map?=?new?HashMap<>();
????????List?sysMenuEntityList?=?sysMenuService.list();
????????map.put("sysMenuEntityList",sysMenuEntityList);
????????return?map;
????}
????/**
?????*?獲取所有數(shù)據(jù)
?????*?@Author?Sans
?????*?@CreateTime?2019/6/19?10:38
?????*?@Return?Map?返回結(jié)果
?????*/
????@RequestMapping("/getInfoAll")
????@RequiresPermissions("sys:info:all")
????public?Map?getInfoAll(){
????????Map?map?=?new?HashMap<>();
????????List?sysUserEntityList?=?sysUserService.list();
????????map.put("sysUserEntityList",sysUserEntityList);
????????List?sysRoleEntityList?=?sysRoleService.list();
????????map.put("sysRoleEntityList",sysRoleEntityList);
????????List?sysMenuEntityList?=?sysMenuService.list();
????????map.put("sysMenuEntityList",sysMenuEntityList);
????????return?map;
????}
????/**
?????*?添加管理員角色權(quán)限(測(cè)試動(dòng)態(tài)權(quán)限更新)
?????*?@Author?Sans
?????*?@CreateTime?2019/6/19?10:39
?????*?@Param??username?用戶ID
?????*?@Return?Map?返回結(jié)果
?????*/
????@RequestMapping("/addMenu")
????public?Map?addMenu(){
????????//添加管理員角色權(quán)限
????????SysRoleMenuEntity?sysRoleMenuEntity?=?new?SysRoleMenuEntity();
????????sysRoleMenuEntity.setMenuId(4L);
????????sysRoleMenuEntity.setRoleId(1L);
????????sysRoleMenuService.save(sysRoleMenuEntity);
????????//清除緩存
????????String?username?=?"admin";
????????ShiroUtils.deleteCache(username,false);
????????Map?map?=?new?HashMap<>();
????????map.put("code",200);
????????map.put("msg","權(quán)限添加成功");
????????return?map;
????}
}
創(chuàng)建 UserLoginController 登錄類
/**
?*?@Description?用戶登錄
?*?@Author?Sans
?*?@CreateTime?2019/6/17?15:21
?*/
@RestController
@RequestMapping("/userLogin")
public?class?UserLoginController?{
????@Autowired
????private?SysUserService?sysUserService;
????/**
?????*?登錄
?????*?@Author?Sans
?????*?@CreateTime?2019/6/20?9:21
?????*/
????@RequestMapping("/login")
????public?Map?login(@RequestBody?SysUserEntity?sysUserEntity){
????????Map?map?=?new?HashMap<>();
????????//進(jìn)行身份驗(yàn)證
????????try{
????????????//驗(yàn)證身份和登陸
????????????Subject?subject?=?SecurityUtils.getSubject();
????????????UsernamePasswordToken?token?=?new?UsernamePasswordToken(sysUserEntity.getUsername(),?sysUserEntity.getPassword());
????????????//驗(yàn)證成功進(jìn)行登錄操作
????????????subject.login(token);
????????}catch?(IncorrectCredentialsException?e)?{
????????????map.put("code",500);
????????????map.put("msg","用戶不存在或者密碼錯(cuò)誤");
????????????return?map;
????????}?catch?(LockedAccountException?e)?{
????????????map.put("code",500);
????????????map.put("msg","登錄失敗,該用戶已被凍結(jié)");
????????????return?map;
????????}?catch?(AuthenticationException?e)?{
????????????map.put("code",500);
????????????map.put("msg","該用戶不存在");
????????????return?map;
????????}?catch?(Exception?e)?{
????????????map.put("code",500);
????????????map.put("msg","未知異常");
????????????return?map;
????????}
????????map.put("code",0);
????????map.put("msg","登錄成功");
????????map.put("token",ShiroUtils.getSession().getId().toString());
????????return?map;
????}
????/**
?????*?未登錄
?????*?@Author?Sans
?????*?@CreateTime?2019/6/20?9:22
?????*/
????@RequestMapping("/unauth")
????public?Map?unauth(){
????????Map?map?=?new?HashMap<>();
????????map.put("code",500);
????????map.put("msg","未登錄");
????????return?map;
????}
}
五. POSTMAN 測(cè)試
登錄成功后會(huì)返回 TOKEN, 因?yàn)槭菃吸c(diǎn)登錄, 再次登陸的話會(huì)返回新的 TOKEN, 之前 Redis 的 TOKEN 就會(huì)失效了
當(dāng)?shù)谝淮卧L問接口后我們可以看到緩存中已經(jīng)有權(quán)限數(shù)據(jù)了, 在次訪問接口的時(shí)候, Shiro 會(huì)直接去緩存中拿取權(quán)限, 注意訪問接口時(shí)候要設(shè)置請(qǐng)求頭.
ADMIN 這個(gè)號(hào)現(xiàn)在沒有 sys:info:all 這個(gè)權(quán)限的, 所以無法訪問 getInfoAll 接口, 我們要?jiǎng)討B(tài)分配權(quán)限后, 要清掉緩存, 在訪問接口時(shí)候, Shiro 會(huì)去重新執(zhí)行授權(quán)方法, 之后再次把權(quán)限和角色數(shù)據(jù)放入緩存中
訪問添加權(quán)限測(cè)試接口, 因?yàn)槭菧y(cè)試, 我把增加權(quán)限的用戶 ADMIN 寫死在里面了, 權(quán)限添加后, 調(diào)用工具類清掉緩存, 我們可以發(fā)現(xiàn), Redis 中已經(jīng)沒有緩存了
再次訪問 getInfoAll 接口, 因?yàn)榫彺嬷袥]有數(shù)據(jù), Shiro 會(huì)重新授權(quán)查詢權(quán)限, 攔截通過
六. 項(xiàng)目源碼
https://gitee.com/liselotte/spring-boot-shiro-demo
https://github.com/xuyulong2017/my-java-demo
-END- 我是武哥,最后給大家免費(fèi)分享我寫的 10 萬字 Spring Boot 學(xué)習(xí)筆記(帶完整目錄)以及對(duì)應(yīng)的源碼。這是我之前在 CSDN 開的一門課,所以筆記非常詳細(xì)完整,我準(zhǔn)備將資料分享出來給大家免費(fèi)學(xué)習(xí),相信大家看完一定會(huì)有所收獲(下面有下載方式)。
可以看出,我當(dāng)時(shí)備課非常詳細(xì),目錄非常完整,讀者可以手把手跟著筆記,結(jié)合源代碼來學(xué)習(xí)?,F(xiàn)在免費(fèi)分享出來,有需要的讀者可以下載學(xué)習(xí),就在我下面的公眾號(hào)Java禿頭哥里回復(fù):筆記,就行。
如有文章對(duì)你有幫助,
在看和轉(zhuǎn)發(fā)是對(duì)我最大的支持!
關(guān)注Java禿頭哥
只有禿頭才能更強(qiáng)
點(diǎn)贊是最大的支持?





