1. 程式人生 > 實用技巧 >SpringBoot 整合 Shiro 實現動態許可權載入更新+ Session 共享 + 單點登入

SpringBoot 整合 Shiro 實現動態許可權載入更新+ Session 共享 + 單點登入

  • 一。說明
  • 二。專案環境
  • 二。編寫專案基礎類
  • 三。編寫Shiro核心類
  • 四。實現許可權控制
  • 五.POSTMAN測試
  • 六。專案原始碼

一。說明

Shiro是一個安全框架,專案中主要用它做認證,授權,加密,以及使用者的會話管理,雖然Shiro沒有SpringSecurity功能更豐富,但是它輕量,簡單,在專案中通常業務需求Shiro也能夠勝任。

二。專案環境

MyBatis-Plus版本:3.1.0

SpringBoot版本:2.1.5

JDK版本:1.8

Shiro版本:1.4

Shiro-redis外掛版本:3.1.0

資料表(SQL檔案在專案中):資料庫中測試號的密碼進行了加密,密碼皆為123456

資料表名中文表名備註說明
sys_user 系統使用者表 基礎表
sys_menu 許可權表 基礎表
sys_role 角色表 基礎表
sys_role_menu 角色與許可權關係表 中間表
sys_user_role 使用者與角色關係表 中間表

Maven依賴如下:

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--AOP依賴,一定要加,否則許可權攔截驗證不生效-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--lombok外掛-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!--mybatisPlus核心庫-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<!--引入阿里資料庫連線池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!--Shiro核心依賴-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--Shiro-redis外掛-->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
<!--StringUtilS工具-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
</dependencies>

配置如下:

#配置埠
server:
port:8764
spring:
#配置資料來源
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資料來源
redis:
host:localhost
port:6379
timeout:6000
password:123456
jedis:
pool:
max-active:1000#連線池最大連線數(使用負值表示沒有限制)
max-wait:-1#連線池最大阻塞等待時間(使用負值表示沒有限制)
max-idle:10#連線池中的最大空閒連線
min-idle:5#連線池中的最小空閒連線
#mybatis-plus相關配置
mybatis-plus:
#xml掃描,多個目錄用逗號或者分號分隔(告訴Mapper所對應的XML檔案位置)
mapper-locations:classpath:mapper/*.xml
#以下配置均有預設值,可以不設定
global-config:
db-config:
#主鍵型別AUTO:"資料庫ID自增"INPUT:"使用者輸入ID",ID_WORKER:"全域性唯一ID(數字型別唯一ID)",UUID:"全域性唯一IDUUID";
id-type:auto
#欄位策略IGNORED:"忽略判斷"NOT_NULL:"非NULL判斷")NOT_EMPTY:"非空判斷"
field-strategy:NOT_EMPTY
#資料庫型別
db-type:MYSQL
configuration:
#是否開啟自動駝峰命名規則對映:從資料庫列名到Java屬性駝峰命名的類似對映
map-underscore-to-camel-case:true
#返回map時true:當查詢資料為空時欄位返回為null,false:不加這個查詢資料為空時,欄位將被隱藏
call-setters-on-nulls:true
#這個配置會將執行的sql打印出來,在開發或測試的時候可以用
log-impl:org.apache.ibatis.logging.stdout.StdOutImpl

二。編寫專案基礎類

使用者實體,Dao,Service等在這裡省略,請參考原始碼

編寫異常類來處理Shiro許可權攔截異常

/**
*@Description自定義異常
*@AuthorSans
*@CreateTime2019/6/1522:56
*/
@ControllerAdvice
publicclassMyShiroException{
/**
*處理Shiro許可權攔截異常
*如果返回JSON資料格式請加上@ResponseBody註解
*@AuthorSans
*@CreateTime2019/6/1513:35
*@ReturnMap<Object>返回結果集
*/
@ResponseBody
@ExceptionHandler(value=AuthorizationException.class)
publicMap<String,Object>defaultErrorHandler(){
Map<String,Object>map=newHashMap<>();
map.put("403","許可權不足");
returnmap;
}
}

建立SHA256Util加密工具

/**
*@DescriptionSha-256加密工具
*@AuthorSans
*@CreateTime2019/6/129:27
*/
publicclassSHA256Util{
/**私有構造器**/
privateSHA256Util(){};
/**加密演算法**/
publicfinalstaticStringHASH_ALGORITHM_NAME="SHA-256";
/**迴圈次數**/
publicfinalstaticintHASH_ITERATIONS=15;
/**執行加密-採用SHA256和鹽值加密**/
publicstaticStringsha256(Stringpassword,Stringsalt){
returnnewSimpleHash(HASH_ALGORITHM_NAME,password,salt,HASH_ITERATIONS).toString();
}
}

建立彈簧工具

/**
*@DescriptionSpring上下文工具類
*@AuthorSans
*@CreateTime2019/6/1713:40
*/
@Component
publicclassSpringUtilimplementsApplicationContextAware{
privatestaticApplicationContextcontext;
/**
*Spring在bean初始化後會判斷是不是ApplicationContextAware的子類
*如果該類是,setApplicationContext()方法,會將容器中ApplicationContext作為引數傳入進去
*@AuthorSans
*@CreateTime2019/6/1716:58
*/
@Override
publicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{
context=applicationContext;
}
/**
*通過Name返回指定的Bean
*@AuthorSans
*@CreateTime2019/6/1716:03
*/
publicstatic<T>TgetBean(Class<T>beanClass){
returncontext.getBean(beanClass);
}
}

建立Shiro工具

/**
*@DescriptionShiro工具類
*@AuthorSans
*@CreateTime2019/6/1516:11
*/
publicclassShiroUtils{

/**私有構造器**/
privateShiroUtils(){}

privatestaticRedisSessionDAOredisSessionDAO=SpringUtil.getBean(RedisSessionDAO.class);

/**
*獲取當前使用者Session
*@AuthorSans
*@CreateTime2019/6/1717:03
*@ReturnSysUserEntity使用者資訊
*/
publicstaticSessiongetSession(){
returnSecurityUtils.getSubject().getSession();
}

/**
*使用者登出
*@AuthorSans
*@CreateTime2019/6/1717:23
*/
publicstaticvoidlogout(){
SecurityUtils.getSubject().logout();
}

/**
*獲取當前使用者資訊
*@AuthorSans
*@CreateTime2019/6/1717:03
*@ReturnSysUserEntity使用者資訊
*/
publicstaticSysUserEntitygetUserInfo(){
return(SysUserEntity)SecurityUtils.getSubject().getPrincipal();
}

/**
*刪除使用者快取資訊
*@AuthorSans
*@CreateTime2019/6/1713:57
*@Paramusername使用者名稱稱
*@ParamisRemoveSession是否刪除Session
*@Returnvoid
*/
publicstaticvoiddeleteCache(Stringusername,booleanisRemoveSession){
//從快取中獲取Session
Sessionsession=null;
Collection<Session>sessions=redisSessionDAO.getActiveSessions();
SysUserEntitysysUserEntity;
Objectattribute=null;
for(SessionsessionInfo:sessions){
//遍歷Session,找到該使用者名稱稱對應的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;
break;
}
}
if(session==null||attribute==null){
return;
}
//刪除session
if(isRemoveSession){
redisSessionDAO.delete(session);
}
//刪除Cache,在訪問受限介面時會重新授權
DefaultWebSecurityManagersecurityManager=(DefaultWebSecurityManager)SecurityUtils.getSecurityManager();
Authenticatorauthc=securityManager.getAuthenticator();
((LogoutAware)authc).onLogout((SimplePrincipalCollection)attribute);
}
}

建立Shiro的SessionId生成器

/**
*@Description自定義SessionId生成器
*@AuthorSans
*@CreateTime2019/6/1111:48
*/
publicclassShiroSessionIdGeneratorimplementsSessionIdGenerator{
/**
*實現SessionId生成
*@AuthorSans
*@CreateTime2019/6/1111:54
*/
@Override
publicSerializablegenerateId(Sessionsession){
SerializablesessionId=newJavaUuidSessionIdGenerator().generateId(session);
returnString.format("login_token_%s",sessionId);
}
}

三。編寫Shiro核心類

建立Realm用於授權和認證

/**
*@DescriptionShiro許可權匹配和賬號密碼匹配
*@AuthorSans
*@CreateTime2019/6/1511:27
*/
publicclassShiroRealmextendsAuthorizingRealm{
@Autowired
privateSysUserServicesysUserService;
@Autowired
privateSysRoleServicesysRoleService;
@Autowired
privateSysMenuServicesysMenuService;
/**
*授權許可權
*使用者進行許可權驗證時候Shiro會去快取中找,如果查不到資料,會執行這個方法去查許可權,並放入快取中
*@AuthorSans
*@CreateTime2019/6/1211:44
*/
@Override
protectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipalCollection){
SimpleAuthorizationInfoauthorizationInfo=newSimpleAuthorizationInfo();
SysUserEntitysysUserEntity=(SysUserEntity)principalCollection.getPrimaryPrincipal();
//獲取使用者ID
LonguserId=sysUserEntity.getUserId();
//這裡可以進行授權和處理
Set<String>rolesSet=newHashSet<>();
Set<String>permsSet=newHashSet<>();
//查詢角色和許可權(這裡根據業務自行查詢)
List<SysRoleEntity>sysRoleEntityList=sysRoleService.selectSysRoleByUserId(userId);
for(SysRoleEntitysysRoleEntity:sysRoleEntityList){
rolesSet.add(sysRoleEntity.getRoleName());
List<SysMenuEntity>sysMenuEntityList=sysMenuService.selectSysMenuByRoleId(sysRoleEntity.getRoleId());
for(SysMenuEntitysysMenuEntity:sysMenuEntityList){
permsSet.add(sysMenuEntity.getPerms());
}
}
//將查到的許可權和角色分別傳入authorizationInfo中
authorizationInfo.setStringPermissions(permsSet);
authorizationInfo.setRoles(rolesSet);
returnauthorizationInfo;
}

/**
*身份認證
*@AuthorSans
*@CreateTime2019/6/1212:36
*/
@Override
protectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokenauthenticationToken)throwsAuthenticationException{
//獲取使用者的輸入的賬號.
Stringusername=(String)authenticationToken.getPrincipal();
//通過username從資料庫中查詢User物件,如果找到進行驗證
//實際專案中,這裡可以根據實際情況做快取,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重複執行該方法
SysUserEntityuser=sysUserService.selectUserByName(username);
//判斷賬號是否存在
if(user==null){
thrownewAuthenticationException();
}
//判斷賬號是否被凍結
if(user.getState()==null||user.getState().equals("PROHIBIT")){
thrownewLockedAccountException();
}
//進行驗證
SimpleAuthenticationInfoauthenticationInfo=newSimpleAuthenticationInfo(
user,//使用者名稱
user.getPassword(),//密碼
ByteSource.Util.bytes(user.getSalt()),//設定鹽值
getName()
);
//驗證成功開始踢人(清除快取和Session)
ShiroUtils.deleteCache(username,true);
returnauthenticationInfo;
}
}

建立SessionManager類

/**
*@Description自定義獲取Token
*@AuthorSans
*@CreateTime2019/6/138:34
*/
publicclassShiroSessionManagerextendsDefaultWebSessionManager{
//定義常量
privatestaticfinalStringAUTHORIZATION="Authorization";
privatestaticfinalStringREFERENCED_SESSION_ID_SOURCE="Statelessrequest";
//重寫構造器
publicShiroSessionManager(){
super();
this.setDeleteInvalidSessions(true);
}
/**
*重寫方法實現從請求頭獲取Token便於介面統一
*每次請求進來,Shiro會去從請求頭找Authorization這個key對應的Value(Token)
*@AuthorSans
*@CreateTime2019/6/138:47
*/
@Override
publicSerializablegetSessionId(ServletRequestrequest,ServletResponseresponse){
Stringtoken=WebUtils.toHttp(request).getHeader(AUTHORIZATION);
//如果請求頭中存在token則從請求頭中獲取token
if(!StringUtils.isEmpty(token)){
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,token);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TRUE);
returntoken;
}else{
//這裡禁用掉Cookie獲取方式
//按預設規則從Cookie取Token
//returnsuper.getSessionId(request,response);
returnnull;
}
}
}


建立ShiroConfig配置類

/**
*@DescriptionShiro配置類
*@AuthorSans
*@CreateTime2019/6/1017:42
*/
@Configuration
publicclassShiroConfig{

privatefinalStringCACHE_KEY="shiro:cache:";
privatefinalStringSESSION_KEY="shiro:session:";
privatefinalintEXPIRE=1800;

//Redis配置
@Value("${spring.redis.host}")
privateStringhost;
@Value("${spring.redis.port}")
privateintport;
@Value("${spring.redis.timeout}")
privateinttimeout;
@Value("${spring.redis.password}")
privateStringpassword;

/**
*開啟Shiro-aop註解支援
*@Attention使用代理方式所以需要開啟程式碼支援
*@AuthorSans
*@CreateTime2019/6/128:38
*/
@Bean
publicAuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor(SecurityManagersecurityManager){
AuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor=newAuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
returnauthorizationAttributeSourceAdvisor;
}

/**
*Shiro基礎配置
*@AuthorSans
*@CreateTime2019/6/128:42
*/
@Bean
publicShiroFilterFactoryBeanshiroFilterFactory(SecurityManagersecurityManager){
ShiroFilterFactoryBeanshiroFilterFactoryBean=newShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String,String>filterChainDefinitionMap=newLinkedHashMap<>();
//注意過濾器配置順序不能顛倒
//配置過濾:不會被攔截的連結
filterChainDefinitionMap.put("/static/**","anon");
filterChainDefinitionMap.put("/userLogin/**","anon");
filterChainDefinitionMap.put("/**","authc");
//配置shiro預設登入介面地址,前後端分離中登入介面跳轉應由前端路由控制,後臺僅返回json資料
shiroFilterFactoryBean.setLoginUrl("/userLogin/unauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
returnshiroFilterFactoryBean;
}

/**
*安全管理器
*@AuthorSans
*@CreateTime2019/6/1210:34
*/
@Bean
publicSecurityManagersecurityManager(){
DefaultWebSecurityManagersecurityManager=newDefaultWebSecurityManager();
//自定義Ssession管理
securityManager.setSessionManager(sessionManager());
//自定義Cache實現
securityManager.setCacheManager(cacheManager());
//自定義Realm驗證
securityManager.setRealm(shiroRealm());
returnsecurityManager;
}

/**
*身份驗證器
*@AuthorSans
*@CreateTime2019/6/1210:37
*/
@Bean
publicShiroRealmshiroRealm(){
ShiroRealmshiroRealm=newShiroRealm();
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
returnshiroRealm;
}

/**
*憑證匹配器
*將密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理,在這裡做匹配配置
*@AuthorSans
*@CreateTime2019/6/1210:48
*/
@Bean
publicHashedCredentialsMatcherhashedCredentialsMatcher(){
HashedCredentialsMatchershaCredentialsMatcher=newHashedCredentialsMatcher();
//雜湊演算法:這裡使用SHA256演算法;
shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);
//雜湊的次數,比如雜湊兩次,相當於md5(md5(""));
shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);
returnshaCredentialsMatcher;
}

/**
*配置Redis管理器
*@Attention使用的是shiro-redis開源外掛
*@AuthorSans
*@CreateTime2019/6/1211:06
*/
@Bean
publicRedisManagerredisManager(){
RedisManagerredisManager=newRedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
returnredisManager;
}

/**
*配置Cache管理器
*用於往Redis儲存許可權和角色標識
*@Attention使用的是shiro-redis開源外掛
*@AuthorSans
*@CreateTime2019/6/1212:37
*/
@Bean
publicRedisCacheManagercacheManager(){
RedisCacheManagerredisCacheManager=newRedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setKeyPrefix(CACHE_KEY);
//配置快取的話要求放在session裡面的實體類必須有個id標識
redisCacheManager.setPrincipalIdFieldName("userId");
returnredisCacheManager;
}

/**
*SessionID生成器
*@AuthorSans
*@CreateTime2019/6/1213:12
*/
@Bean
publicShiroSessionIdGeneratorsessionIdGenerator(){
returnnewShiroSessionIdGenerator();
}

/**
*配置RedisSessionDAO
*@Attention使用的是shiro-redis開源外掛
*@AuthorSans
*@CreateTime2019/6/1213:44
*/
@Bean
publicRedisSessionDAOredisSessionDAO(){
RedisSessionDAOredisSessionDAO=newRedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
redisSessionDAO.setKeyPrefix(SESSION_KEY);
redisSessionDAO.setExpire(expire);
returnredisSessionDAO;
}

/**
*配置Session管理器
*@AuthorSans
*@CreateTime2019/6/1214:25
*/
@Bean
publicSessionManagersessionManager(){
ShiroSessionManagershiroSessionManager=newShiroSessionManager();
shiroSessionManager.setSessionDAO(redisSessionDAO());
returnshiroSessionManager;
}
}


四。實現許可權控制

Shiro可以使用程式碼或註解來控制權限,通常我們使用註解控制,既簡單方便,而且更加靈活。

註解名稱說明
需要認證 使用該註解標註的類,方法等在訪問時,當前主題必須在當前會話中已經過認證。
需要客人 使用該註解標註的類,方法等在訪問時,當前主題可以是“ gust”身份,不需要經過認證或者在原先的會話中存在記錄。
要求使用者 驗證使用者是否被記憶,有兩種含義:一種是成功登入的(subject.isAuthenticated()結果為true);另一種是被記憶的(subject.isRemembered()結果為true)。
需要許可權 當前Subject需要擁有某些特定的許可權時,才能執行被該註解註釋的方法。如果沒有許可權,則方法將不會執行引發AuthorizationException異常。
需要角色 當前Subject必須擁有所有指定的角色時,才能訪問被該註解標註的方法。如果沒有角色,則方法將不會執行引發丟擲AuthorizationException異常。

一般情況下我們在專案中做許可權控制,使用最多的是RequiresPermissions和RequiresRoles,允許存在多個角色和許可權,成為邏輯是AND,也就是同時擁有這些才可以訪問方法,可以在註解中以引數的形式設定成OR

示例
//擁有一個角色就可以訪問
@RequiresRoles(value={"ADMIN","USER"},logical=Logical.OR)
//擁有所有許可權才可以訪問
@RequiresPermissions(value={"sys:user:info","sys:role:info"},logical=Logical.AND)


使用順序:Shiro註解是存在順序的,當多個註解在一個方法上的時候,會逐個檢查,知道全部通過為止,交替攔截順序是:RequiresRoles-> RequiresPermissions-> RequiresAuthentication-> RequiresUser-> RequiresGuest

示例
//擁有ADMIN角色同時還要有sys:role:info許可權
@RequiresRoles(value={"ADMIN")
@RequiresPermissions("sys:role:info")


建立UserRoleController角色攔截測試類

/**
*@Description角色測試
*@AuthorSans
*@CreateTime2019/6/1911:38
*/
@RestController
@RequestMapping("/role")
publicclassUserRoleController{

@Autowired
privateSysUserServicesysUserService;
@Autowired
privateSysRoleServicesysRoleService;
@Autowired
privateSysMenuServicesysMenuService;
@Autowired
privateSysRoleMenuServicesysRoleMenuService;

/**
*管理員角色測試介面
*@AuthorSans
*@CreateTime2019/6/1910:38
*@ReturnMap<String,Object>返回結果
*/
@RequestMapping("/getAdminInfo")
@RequiresRoles("ADMIN")
publicMap<String,Object>getAdminInfo(){
Map<String,Object>map=newHashMap<>();
map.put("code",200);
map.put("msg","這裡是只有管理員角色能訪問的介面");
returnmap;
}

/**
*使用者角色測試介面
*@AuthorSans
*@CreateTime2019/6/1910:38
*@ReturnMap<String,Object>返回結果
*/
@RequestMapping("/getUserInfo")
@RequiresRoles("USER")
publicMap<String,Object>getUserInfo(){
Map<String,Object>map=newHashMap<>();
map.put("code",200);
map.put("msg","這裡是只有使用者角色能訪問的介面");
returnmap;
}

/**
*角色測試介面
*@AuthorSans
*@CreateTime2019/6/1910:38
*@ReturnMap<String,Object>返回結果
*/
@RequestMapping("/getRoleInfo")
@RequiresRoles(value={"ADMIN","USER"},logical=Logical.OR)
@RequiresUser
publicMap<String,Object>getRoleInfo(){
Map<String,Object>map=newHashMap<>();
map.put("code",200);
map.put("msg","這裡是只要有ADMIN或者USER角色能訪問的介面");
returnmap;
}

/**
*登出(測試登出)
*@AuthorSans
*@CreateTime2019/6/1910:38
*@ReturnMap<String,Object>返回結果
*/
@RequestMapping("/getLogout")
@RequiresUser
publicMap<String,Object>getLogout(){
ShiroUtils.logout();
Map<String,Object>map=newHashMap<>();
map.put("code",200);
map.put("msg","登出");
returnmap;
}
}


建立UserMenuController許可權攔截測試類

/**
*@Description許可權測試
*@AuthorSans
*@CreateTime2019/6/1911:38
*/
@RestController
@RequestMapping("/menu")
publicclassUserMenuController{

@Autowired
privateSysUserServicesysUserService;
@Autowired
privateSysRoleServicesysRoleService;
@Autowired
privateSysMenuServicesysMenuService;
@Autowired
privateSysRoleMenuServicesysRoleMenuService;

/**
*獲取使用者資訊集合
*@AuthorSans
*@CreateTime2019/6/1910:36
*@ReturnMap<String,Object>返回結果
*/
@RequestMapping("/getUserInfoList")
@RequiresPermissions("sys:user:info")
publicMap<String,Object>getUserInfoList(){
Map<String,Object>map=newHashMap<>();
List<SysUserEntity>sysUserEntityList=sysUserService.list();
map.put("sysUserEntityList",sysUserEntityList);
returnmap;
}

/**
*獲取角色資訊集合
*@AuthorSans
*@CreateTime2019/6/1910:37
*@ReturnMap<String,Object>返回結果
*/
@RequestMapping("/getRoleInfoList")
@RequiresPermissions("sys:role:info")
publicMap<String,Object>getRoleInfoList(){
Map<String,Object>map=newHashMap<>();
List<SysRoleEntity>sysRoleEntityList=sysRoleService.list();
map.put("sysRoleEntityList",sysRoleEntityList);
returnmap;
}

/**
*獲取許可權資訊集合
*@AuthorSans
*@CreateTime2019/6/1910:38
*@ReturnMap<String,Object>返回結果
*/
@RequestMapping("/getMenuInfoList")
@RequiresPermissions("sys:menu:info")
publicMap<String,Object>getMenuInfoList(){
Map<String,Object>map=newHashMap<>();
List<SysMenuEntity>sysMenuEntityList=sysMenuService.list();
map.put("sysMenuEntityList",sysMenuEntityList);
returnmap;
}

/**
*獲取所有資料
*@AuthorSans
*@CreateTime2019/6/1910:38
*@ReturnMap<String,Object>返回結果
*/
@RequestMapping("/getInfoAll")
@RequiresPermissions("sys:info:all")
publicMap<String,Object>getInfoAll(){
Map<String,Object>map=newHashMap<>();
List<SysUserEntity>sysUserEntityList=sysUserService.list();
map.put("sysUserEntityList",sysUserEntityList);
List<SysRoleEntity>sysRoleEntityList=sysRoleService.list();
map.put("sysRoleEntityList",sysRoleEntityList);
List<SysMenuEntity>sysMenuEntityList=sysMenuService.list();
map.put("sysMenuEntityList",sysMenuEntityList);
returnmap;
}

/**
*新增管理員角色許可權(測試動態許可權更新)
*@AuthorSans
*@CreateTime2019/6/1910:39
*@Paramusername使用者ID
*@ReturnMap<String,Object>返回結果
*/
@RequestMapping("/addMenu")
publicMap<String,Object>addMenu(){
//新增管理員角色許可權
SysRoleMenuEntitysysRoleMenuEntity=newSysRoleMenuEntity();
sysRoleMenuEntity.setMenuId(4L);
sysRoleMenuEntity.setRoleId(1L);
sysRoleMenuService.save(sysRoleMenuEntity);
//清除快取
Stringusername="admin";
ShiroUtils.deleteCache(username,false);
Map<String,Object>map=newHashMap<>();
map.put("code",200);
map.put("msg","許可權新增成功");
returnmap;
}
}

建立UserLoginController登入類

/**
*@Description使用者登入
*@AuthorSans
*@CreateTime2019/6/1715:21
*/
@RestController
@RequestMapping("/userLogin")
publicclassUserLoginController{

/**
*登入
*@AuthorSans
*@CreateTime2019/6/209:21
*/
@RequestMapping("/login")
publicMap<String,Object>login(@RequestBodySysUserEntitysysUserEntity){
Map<String,Object>map=newHashMap<>();
//進行身份驗證
try{
//驗證身份和登入
Subjectsubject=SecurityUtils.getSubject();
UsernamePasswordTokentoken=newUsernamePasswordToken(sysUserEntity.getUsername(),sysUserEntity.getPassword());
//驗證成功進行登入操作
subject.login(token);
}catch(IncorrectCredentialsExceptione){
map.put("code",500);
map.put("msg","使用者不存在或者密碼錯誤");
returnmap;
}catch(LockedAccountExceptione){
map.put("code",500);
map.put("msg","登入失敗,該使用者已被凍結");
returnmap;
}catch(AuthenticationExceptione){
map.put("code",500);
map.put("msg","該使用者不存在");
returnmap;
}catch(Exceptione){
map.put("code",500);
map.put("msg","未知異常");
returnmap;
}
map.put("code",0);
map.put("msg","登入成功");
map.put("token",ShiroUtils.getSession().getId().toString());
returnmap;
}
/**
*未登入
*@AuthorSans
*@CreateTime2019/6/209:22
*/
@RequestMapping("/unauth")
publicMap<String,Object>unauth(){
Map<String,Object>map=newHashMap<>();
map.put("code",500);
map.put("msg","未登入");
returnmap;
}
}

五.POSTMAN測試

登入成功後會返回TOKEN,因為是單點登入,再次登入的話會返回新的TOKEN,然後Redis的TOKEN就會失效了

當第一次訪問介面後我們可以看到快取中已經有許可權資料了,在次訪問介面的時候,Shiro會直接去快取中拿取許可權,注意訪問介面時候要設定請求頭。

ADMIN這個號現在沒有sys:info:all這個許可權的,所以無法訪問getInfoAll介面,我們要動態分配許可權後,要清掉快取,在訪問介面時候,Shiro會去重新執行授權方法,之後再次把許可權和角色資料放入快取中

訪問新增許可權測試介面,因為是測試,我把增加許可權的使用者ADMIN寫死在裡面了,許可權新增後,呼叫工具類清掉快取,我們可以發現,Redis中已經沒有快取了

首次訪問getInfoAll介面,因為快取中沒有資料,Shiro會重新授權查詢許可權,攔截通過

六。專案原始碼

編碼雲:https://gitee.com/liselotte/spring-boot-shiro-demo

GitHub:https://github.com/xuyulong2017/my-java-demo

END

小哈學Java昨天

最近面試BAT,整理一份面試資料Java面試BATJ通關手冊,覆蓋了Java核心技術、JVM、Java併發、SSM、微服務、資料庫、資料結構等等。

獲取方式:點“在看”,關注公眾號並回復Java領取,更多內容陸續奉上。