<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SpringBoot 整合 Shiro 實現動態(tài)權限加載更新+ Session 共享 + 單點登錄

          共 24494字,需瀏覽 49分鐘

           ·

          2020-11-07 08:06

          點擊上方藍色“小哈學Java”,選擇“設為星標

          回復“資源”獲取獨家整理的學習資料!

          來源: juejin.im/post/5d087d605188256de9779e64

          • 一。說明
          • 二。項目環(huán)境
          • 二。編寫項目基礎類
          • 三。編寫Shiro核心類
          • 四。實現權限控制
          • 五.POSTMAN測試
          • 六。項目源碼

          一。說明

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

          二。項目環(huán)境

          MyBatis-Plus版本:3.1.0

          SpringBoot版本:2.1.5

          JDK版本:1.8

          Shiro版本:1.4

          Shiro-redis插件版本:3.1.0

          數據表(SQL文件在項目中):數據庫中測試號的密碼進行了加密,密碼皆為123456

          數據表名中文表名備注說明
          sys_user系統(tǒng)用戶表基礎表
          sys_menu權限表基礎表
          sys_role角色表基礎表
          sys_role_menu角色與權限關系表中間表
          sys_user_role用戶與角色關系表中間表

          Maven依賴如下:

          <dependencies>
          ????????<dependency>
          ????????????<groupId>org.springframework.bootgroupId>
          ????????????<artifactId>spring-boot-starter-webartifactId>
          ????????dependency>
          ????????<dependency>
          ????????????<groupId>mysqlgroupId>
          ????????????<artifactId>mysql-connector-javaartifactId>
          ????????????<scope>runtimescope>
          ????????dependency>
          ????????
          ????????<dependency>
          ????????????<groupId>org.springframework.bootgroupId>
          ????????????<artifactId>spring-boot-starter-aopartifactId>
          ????????dependency>
          ????????
          ????????<dependency>
          ????????????<groupId>org.projectlombokgroupId>
          ????????????<artifactId>lombokartifactId>
          ????????????<optional>trueoptional>
          ????????dependency>
          ????????
          ????????<dependency>
          ????????????<groupId>org.springframework.bootgroupId>
          ????????????<artifactId>spring-boot-starter-data-redis-reactiveartifactId>
          ????????dependency>
          ????????
          ????????<dependency>
          ????????????<groupId>com.baomidougroupId>
          ????????????<artifactId>mybatis-plus-boot-starterartifactId>
          ????????????<version>3.1.0version>
          ????????dependency>
          ????????
          ????????<dependency>
          ????????????<groupId>com.alibabagroupId>
          ????????????<artifactId>druidartifactId>
          ????????????<version>1.1.6version>
          ????????dependency>
          ????????
          ????????<dependency>
          ????????????<groupId>org.apache.shirogroupId>
          ????????????<artifactId>shiro-springartifactId>
          ????????????<version>1.4.0version>
          ????????dependency>
          ????????
          ????????<dependency>
          ????????????<groupId>org.crazycakegroupId>
          ????????????<artifactId>shiro-redisartifactId>
          ????????????<version>3.1.0version>
          ????????dependency>
          ????????
          ????????<dependency>
          ????????????<groupId>org.apache.commonsgroupId>
          ????????????<artifactId>commons-lang3artifactId>
          ????????????<version>3.5version>
          ????????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:"全局唯一ID?UUID";
          ??????id-type:?auto
          ??????#字段策略?IGNORED:"忽略判斷"??NOT_NULL:"非?NULL?判斷")??NOT_EMPTY:"非空判斷"
          ??????field-strategy:?NOT_EMPTY
          ??????#數據庫類型
          ??????db-type:?MYSQL
          ??configuration:
          ????#?是否開啟自動駝峰命名規(guī)則映射:從數據庫列名到Java屬性駝峰命名的類似映射
          ????map-underscore-to-camel-case:?true
          ????#?返回map時true:當查詢數據為空時字段返回為null,false:不加這個查詢數據為空時,字段將被隱藏
          ????call-setters-on-nulls:?true
          ????#?這個配置會將執(zhí)行的sql打印出來,在開發(fā)或測試的時候可以用
          ????log-impl:?org.apache.ibatis.logging.stdout.StdOutImpl

          二。編寫項目基礎類

          用戶實體,Dao,Service等在這里省略,請參考源碼

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

          /**
          ?*?@Description?自定義異常
          ?*?@Author?Sans
          ?*?@CreateTime?2019/6/15?22:56
          ?*/

          @ControllerAdvice
          public?class?MyShiroException?{
          ????/**
          ?????*?處理Shiro權限攔截異常
          ?????*?如果返回JSON數據格式請加上?@ResponseBody注解
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/15?13:35
          ?????*?@Return?Map?返回結果集
          ?????*/
          ????@ResponseBody
          ????@ExceptionHandler(value?=?AuthorizationException.class)
          ????public?Map<String,Object>?defaultErrorHandler()
          {
          ????????Map?map?=?new?HashMap<>();
          ????????map.put("403","權限不足");
          ????????return?map;
          ????}
          }

          創(chuàng)建SHA256Util加密工具

          /**
          ?*?@Description?Sha-256加密工具
          ?*?@Author?Sans
          ?*?@CreateTime?2019/6/12?9:27
          ?*/

          public?class?SHA256Util?{
          ????/**??私有構造器?**/
          ????private?SHA256Util(){};
          ????/**??加密算法?**/
          ????public?final?static?String?HASH_ALGORITHM_NAME?=?"SHA-256";
          ????/**??循環(huán)次數?**/
          ????public?final?static?int?HASH_ITERATIONS?=?15;
          ????/**??執(zhí)行加密-采用SHA256和鹽值加密?**/
          ????public?static?String?sha256(String?password,?String?salt)?{
          ????????return?new?SimpleHash(HASH_ALGORITHM_NAME,?password,?salt,?HASH_ITERATIONS).toString();
          ????}
          }

          創(chuàng)建彈簧工具

          /**
          ?*?@Description?Spring上下文工具類
          ?*?@Author?Sans
          ?*?@CreateTime?2019/6/17?13:40
          ?*/

          @Component
          public?class?SpringUtil?implements?ApplicationContextAware?{
          ????private?static?ApplicationContext?context;
          ????/**
          ?????*?Spring在bean初始化后會判斷是不是ApplicationContextAware的子類
          ?????*?如果該類是,setApplicationContext()方法,會將容器中ApplicationContext作為參數傳入進去
          ?????*?@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?{

          ????/**?私有構造器?**/
          ????private?ShiroUtils(){}

          ????private?static?RedisSessionDAO?redisSessionDAO?=?SpringUtil.getBean(RedisSessionDAO.class);

          ????/**
          ?????*?獲取當前用戶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();
          ????}

          ????/**
          ????*?獲取當前用戶信息
          ????*?@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,找到該用戶名稱對應的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,在訪問受限接口時會重新授權
          ????????DefaultWebSecurityManager?securityManager?=?(DefaultWebSecurityManager)?SecurityUtils.getSecurityManager();
          ????????Authenticator?authc?=?securityManager.getAuthenticator();
          ????????((LogoutAware)?authc).onLogout((SimplePrincipalCollection)?attribute);
          ????}
          }

          創(chuàng)建Shiro的SessionId生成器

          /**
          ?*?@Description?自定義SessionId生成器
          ?*?@Author?Sans
          ?*?@CreateTime?2019/6/11?11:48
          ?*/

          public?class?ShiroSessionIdGenerator?implements?SessionIdGenerator?{
          ????/**
          ?????*?實現SessionId生成
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/11?11:54
          ?????*/

          ????@Override
          ????public?Serializable?generateId(Session?session)?{
          ????????Serializable?sessionId?=?new?JavaUuidSessionIdGenerator().generateId(session);
          ????????return?String.format("login_token_%s",?sessionId);
          ????}
          }

          三。編寫Shiro核心類

          創(chuàng)建Realm用于授權和認證

          /**
          ?*?@Description?Shiro權限匹配和賬號密碼匹配
          ?*?@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;
          ????/**
          ?????*?授權權限
          ?????*?用戶進行權限驗證時候Shiro會去緩存中找,如果查不到數據,會執(zhí)行這個方法去查權限,并放入緩存中
          ?????*?@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();
          ????????//這里可以進行授權和處理
          ????????Set?rolesSet?=?new?HashSet<>();
          ????????Set?permsSet?=?new?HashSet<>();
          ????????//查詢角色和權限(這里根據業(yè)務自行查詢)
          ????????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());
          ????????????}
          ????????}
          ????????//將查到的權限和角色分別傳入authorizationInfo中
          ????????authorizationInfo.setStringPermissions(permsSet);
          ????????authorizationInfo.setRoles(rolesSet);
          ????????return?authorizationInfo;
          ????}

          ????/**
          ?????*?身份認證
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/12?12:36
          ?????*/

          ????@Override
          ????protected?AuthenticationInfo?doGetAuthenticationInfo(AuthenticationToken?authenticationToken)?throws?AuthenticationException?{
          ????????//獲取用戶的輸入的賬號.
          ????????String?username?=?(String)?authenticationToken.getPrincipal();
          ????????//通過username從數據庫中查找?User對象,如果找到進行驗證
          ????????//實際項目中,這里可以根據實際情況做緩存,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重復執(zhí)行該方法
          ????????SysUserEntity?user?=?sysUserService.selectUserByName(username);
          ????????//判斷賬號是否存在
          ????????if?(user?==?null)?{
          ????????????throw?new?AuthenticationException();
          ????????}
          ????????//判斷賬號是否被凍結
          ????????if?(user.getState()==null||user.getState().equals("PROHIBIT")){
          ????????????throw?new?LockedAccountException();
          ????????}
          ????????//進行驗證
          ????????SimpleAuthenticationInfo?authenticationInfo?=?new?SimpleAuthenticationInfo(
          ????????????????user,??????????????????????????????????//用戶名
          ????????????????user.getPassword(),????????????????????//密碼
          ????????????????ByteSource.Util.bytes(user.getSalt()),?//設置鹽值
          ????????????????getName()
          ????????);
          ????????//驗證成功開始踢人(清除緩存和Session)
          ????????ShiroUtils.deleteCache(username,true);
          ????????return?authenticationInfo;
          ????}
          }

          創(chuàng)建SessionManager類

          /**
          ?*?@Description?自定義獲取Token
          ?*?@Author?Sans
          ?*?@CreateTime?2019/6/13?8:34
          ?*/

          public?class?ShiroSessionManager?extends?DefaultWebSessionManager?{
          ????//定義常量
          ????private?static?final?String?AUTHORIZATION?=?"Authorization";
          ????private?static?final?String?REFERENCED_SESSION_ID_SOURCE?=?"Stateless?request";
          ????//重寫構造器
          ????public?ShiroSessionManager()?{
          ????????super();
          ????????this.setDeleteInvalidSessions(true);
          ????}
          ????/**
          ?????*?重寫方法實現從請求頭獲取Token便于接口統(tǒng)一
          ?????*?每次請求進來,Shiro會去從請求頭找Authorization這個key對應的Value(Token)
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/13?8:47
          ?????*/

          ????@Override
          ????public?Serializable?getSessionId(ServletRequest?request,?ServletResponse?response)?{
          ????????String?token?=?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);
          ????????????return?token;
          ????????}?else?{
          ????????????//?這里禁用掉Cookie獲取方式
          ????????????//?按默認規(guī)則從Cookie取Token
          ????????????//?return?super.getSessionId(request,?response);
          ????????????return?null;
          ????????}
          ????}
          }


          創(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基礎配置
          ?????*?@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<>();
          ????????//?注意過濾器配置順序不能顛倒
          ????????//?配置過濾:不會被攔截的鏈接
          ????????filterChainDefinitionMap.put("/static/**",?"anon");
          ????????filterChainDefinitionMap.put("/userLogin/**",?"anon");
          ????????filterChainDefinitionMap.put("/**",?"authc");
          ????????//?配置shiro默認登錄界面地址,前后端分離中登錄界面跳轉應由前端路由控制,后臺僅返回json數據
          ????????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實現
          ????????securityManager.setCacheManager(cacheManager());
          ????????//?自定義Realm驗證
          ????????securityManager.setRealm(shiroRealm());
          ????????return?securityManager;
          ????}

          ????/**
          ?????*?身份驗證器
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/12?10:37
          ?????*/

          ????@Bean
          ????public?ShiroRealm?shiroRealm()?{
          ????????ShiroRealm?shiroRealm?=?new?ShiroRealm();
          ????????shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
          ????????return?shiroRealm;
          ????}

          ????/**
          ?????*?憑證匹配器
          ?????*?將密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理,在這里做匹配配置
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/12?10:48
          ?????*/

          ????@Bean
          ????public?HashedCredentialsMatcher?hashedCredentialsMatcher()?{
          ????????HashedCredentialsMatcher?shaCredentialsMatcher?=?new?HashedCredentialsMatcher();
          ????????//?散列算法:這里使用SHA256算法;
          ????????shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);
          ????????//?散列的次數,比如散列兩次,相當于?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存儲權限和角色標識
          ?????*?@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里面的實體類必須有個id標識
          ????????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;
          ????}
          }


          四。實現權限控制

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

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


          創(chuàng)建UserRoleController角色攔截測試類

          /**
          ?*?@Description?角色測試
          ?*?@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;

          ????/**
          ?????*?管理員角色測試接口
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/19?10:38
          ?????*?@Return?Map?返回結果
          ?????*/

          ????@RequestMapping("/getAdminInfo")
          ????@RequiresRoles("ADMIN")
          ????public?Map?getAdminInfo(){
          ????????Map?map?=?new?HashMap<>();
          ????????map.put("code",200);
          ????????map.put("msg","這里是只有管理員角色能訪問的接口");
          ????????return?map;
          ????}

          ????/**
          ?????*?用戶角色測試接口
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/19?10:38
          ?????*?@Return?Map?返回結果
          ?????*/

          ????@RequestMapping("/getUserInfo")
          ????@RequiresRoles("USER")
          ????public?Map?getUserInfo(){
          ????????Map?map?=?new?HashMap<>();
          ????????map.put("code",200);
          ????????map.put("msg","這里是只有用戶角色能訪問的接口");
          ????????return?map;
          ????}

          ????/**
          ?????*?角色測試接口
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/19?10:38
          ?????*?@Return?Map?返回結果
          ?????*/

          ????@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;
          ????}

          ????/**
          ?????*?登出(測試登出)
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/19?10:38
          ?????*?@Return?Map?返回結果
          ?????*/

          ????@RequestMapping("/getLogout")
          ????@RequiresUser
          ????public?Map?getLogout(){
          ????????ShiroUtils.logout();
          ????????Map?map?=?new?HashMap<>();
          ????????map.put("code",200);
          ????????map.put("msg","登出");
          ????????return?map;
          ????}
          }


          創(chuàng)建UserMenuController權限攔截測試類

          /**
          ?*?@Description?權限測試
          ?*?@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?返回結果
          ?????*/

          ????@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?返回結果
          ?????*/

          ????@RequestMapping("/getRoleInfoList")
          ????@RequiresPermissions("sys:role:info")
          ????public?Map?getRoleInfoList(){
          ????????Map?map?=?new?HashMap<>();
          ????????List?sysRoleEntityList?=?sysRoleService.list();
          ????????map.put("sysRoleEntityList",sysRoleEntityList);
          ????????return?map;
          ????}

          ????/**
          ?????*?獲取權限信息集合
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/19?10:38
          ?????*?@Return?Map?返回結果
          ?????*/

          ????@RequestMapping("/getMenuInfoList")
          ????@RequiresPermissions("sys:menu:info")
          ????public?Map?getMenuInfoList(){
          ????????Map?map?=?new?HashMap<>();
          ????????List?sysMenuEntityList?=?sysMenuService.list();
          ????????map.put("sysMenuEntityList",sysMenuEntityList);
          ????????return?map;
          ????}

          ????/**
          ?????*?獲取所有數據
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/19?10:38
          ?????*?@Return?Map?返回結果
          ?????*/

          ????@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;
          ????}

          ????/**
          ?????*?添加管理員角色權限(測試動態(tài)權限更新)
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/19?10:39
          ?????*?@Param??username?用戶ID
          ?????*?@Return?Map?返回結果
          ?????*/

          ????@RequestMapping("/addMenu")
          ????public?Map?addMenu(){
          ????????//添加管理員角色權限
          ????????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","權限添加成功");
          ????????return?map;
          ????}
          }

          創(chuàng)建UserLoginController登錄類

          /**
          ?*?@Description?用戶登錄
          ?*?@Author?Sans
          ?*?@CreateTime?2019/6/17?15:21
          ?*/

          @RestController
          @RequestMapping("/userLogin")
          public?class?UserLoginController?{

          ????/**
          ?????*?登錄
          ?????*?@Author?Sans
          ?????*?@CreateTime?2019/6/20?9:21
          ?????*/

          ????@RequestMapping("/login")
          ????public?Map?login(@RequestBody?SysUserEntity?sysUserEntity){
          ????????Map?map?=?new?HashMap<>();
          ????????//進行身份驗證
          ????????try{
          ????????????//驗證身份和登錄
          ????????????Subject?subject?=?SecurityUtils.getSubject();
          ????????????UsernamePasswordToken?token?=?new?UsernamePasswordToken(sysUserEntity.getUsername(),?sysUserEntity.getPassword());
          ????????????//驗證成功進行登錄操作
          ????????????subject.login(token);
          ????????}catch?(IncorrectCredentialsException?e)?{
          ????????????map.put("code",500);
          ????????????map.put("msg","用戶不存在或者密碼錯誤");
          ????????????return?map;
          ????????}?catch?(LockedAccountException?e)?{
          ????????????map.put("code",500);
          ????????????map.put("msg","登錄失敗,該用戶已被凍結");
          ????????????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測試

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

          當第一次訪問接口后我們可以看到緩存中已經有權限數據了,在次訪問接口的時候,Shiro會直接去緩存中拿取權限,注意訪問接口時候要設置請求頭。

          ADMIN這個號現在沒有sys:info:all這個權限的,所以無法訪問getInfoAll接口,我們要動態(tài)分配權限后,要清掉緩存,在訪問接口時候,Shiro會去重新執(zhí)行授權方法,之后再次把權限和角色數據放入緩存中

          訪問添加權限測試接口,因為是測試,我把增加權限的用戶ADMIN寫死在里面了,權限添加后,調用工具類清掉緩存,我們可以發(fā)現,Redis中已經沒有緩存了

          首次訪問getInfoAll接口,因為緩存中沒有數據,Shiro會重新授權查詢權限,攔截通過

          六。項目源碼

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

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

          END


          有熱門推薦?

          1.?Mybatis 使用的 9 種設計模式,真是太有用了

          2.?突然就懵了!面試官問我:線程池中多余的線程是如何回收的?

          3.?星巴克不使用兩階段提交

          4.?扛住 100 億次請求?我們來試一試

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

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

          文章有幫助的話,在看,轉發(fā)吧。

          謝謝支持喲 (*^__^*)

          瀏覽 64
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    0930AV电影 | 人人草人人玩 | 翔田千里精品视频 | 亚洲熟逼| 国产亲子乱A片免费视频 |