shiro框架學習-10-shiro整合springboot案例


1. 技術選型:

  前后端分離的權限檢驗 + SpringBoot2.x + Mysql + Mybatis + Shiro + Redis + IDEA + JDK8

2. RBAC權限控制設計

  • 用戶
  • 角色(一個用戶可以具有多種角色,一個角色可以被多個用戶使用,用戶與角色為多對多關系)
  • 權限(一個角色可以擁有多種權限,一個權限可以歸屬到多種角色,也是N:N關系)

  想要獲取一個用戶所能訪問的資源,根據用戶查詢用戶角色,根據用戶角色查詢權限,理清了這個關系,就可以創建數據庫了:

CREATE TABLE `permission` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(128) DEFAULT NULL COMMENT '名稱',
  `url` varchar(128) DEFAULT NULL COMMENT '接口路徑',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
INSERT INTO `permission` VALUES (1,'video_update','/api/video/update'),(2,'video_delete','/api/video/delete'),(3,'video_add','/api/video/add'),(4,'order_list','/api/order/list'),(5,'user_list','/api/user/list');

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(128) DEFAULT NULL COMMENT '用戶名',
  `password` varchar(256) DEFAULT NULL COMMENT '密碼',
  `create_time` datetime DEFAULT NULL,
  `salt` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES (1,'二當家小D','4280d89a5a03f812751f504cc10ee8a5',NULL,NULL),(2,'大當家','123456789',NULL,NULL),(3,'jack','d022646351048ac0ba397d12dfafa304',NULL,NULL);

CREATE TABLE `role_permission` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `role_id` int(11) DEFAULT NULL,
  `permission_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
INSERT INTO `role_permission` VALUES (1,3,1),(2,3,2),(3,3,3),(4,2,1),(5,2,2),(6,2,3),(7,2,4);


CREATE TABLE `role` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(128) DEFAULT NULL COMMENT '名稱',
  `description` varchar(64) DEFAULT NULL COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `role` VALUES (1,'admin','普通管理員'),(2,'root','超級管理員'),(3,'editor','審核人員');


CREATE TABLE `user_role` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `role_id` int(11) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `remarks` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
INSERT INTO `user_role` VALUES (1,3,1,'二當家小D是editor'),(2,1,3,'jack是admin'),(3,2,3,'jack是root'),(4,3,3,'jack是editor'),(5,1,2,'大當家是admin'),(6,1,1,'二當家小D是root');

3.新建springboot項目,添加如下項目依賴:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <!-- 下面這個runtime需要去掉!!! -->
            <!--    <scope>runtime</scope>-->
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.6</version>
        </dependency>

        <!-- spring整合shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>

        <!-- 熱部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- shiro+redis緩存插件 -->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.1.0</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

4. application.properties配置數據庫連接

#數據庫配置 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/xdclass_shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&userSSL=false spring.datasource.username=root spring.datasource.password=lchadmin #使用druid數據源 #spring.datasource.type=com.alibaba.druid.pool.DruidDataSource #開啟控制台打印sql mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl #開啟數據庫字段下划線自動轉Java駝峰命名 mybatis.configuration.map-underscore-to-camel-case=true

5. 用戶-角色-權限 多對多關聯查詢SQL

* 第一步 查詢用戶對應的角色映射關系
select * from user u left join user_role ur on u.id=ur.user_id where u.id=3

* 第二步 查詢用戶對應的角色信息
select * from user u left join user_role ur on u.id=ur.user_id left join role r on ur.role_id = r.id where u.id=3
* 第三步 查詢角色和權限的關系
select * from user u
left join user_role ur on u.id=ur.user_id
left join role r on ur.role_id = r.id
left join role_permission rp on r.id=rp.role_id
where u.id=1
* 第四步 查詢角色對應的權限信息(某個用戶具備的角色和權限集合)
select * from user u
left join user_role ur on u.id=ur.user_id
left join role r on ur.role_id = r.id
left join role_permission rp on r.id=rp.role_id
left join permission p on rp.permission_id=p.id
where u.id=1

6. POJO定義

數據庫查詢接口:

 
         
         
        

 

 1 package net.xdclass.xdclassshiro.dao;
 2 
 3 import net.xdclass.xdclassshiro.domain.User;
 4 import org.apache.ibatis.annotations.Param;
 5 import org.apache.ibatis.annotations.Select;
 6 
 7 import javax.websocket.server.ServerEndpoint;
 8 
 9 public interface UserMapper {
10 
11     @Select("select * from user where username= #{username}")
12     User findByUserName(@Param("username") String username);
13 
14     @Select("select * from user where id = #{userId}")
15     User findById(@Param("userId") int id);
16 
17     @Select("select * from user where username=#{username} and password=#{pwd}")
18     User findByUsernameAndPwd(@Param("username") String userName,@Param("pwd")String pwd);
19 
20 }
UserMapper

 

 1 package net.xdclass.xdclassshiro.dao;
 2 
 3 import net.xdclass.xdclassshiro.domain.Role;
 4 import org.apache.ibatis.annotations.*;
 5 import org.apache.ibatis.mapping.FetchType;
 6 
 7 import java.util.List;
 8 
 9 public interface RoleMapper {
10 
11     @Select("select ur.role_id id,r.name name,r.description description from role r\n" +
12             "left join user_role ur  on r.id = ur.role_id where ur.user_id = #{userId}")
13     @Results(
14             value={
15                     @Result(id=true,property = "id",column="id"),
16                     @Result(property = "name",column="name"),
17                     @Result(property = "description",column="description"),
18                     @Result(property = "permissionList",column="id",
19                     many = @Many(select = "net.xdclass.xdclassshiro.dao.PermissionMapper.findPermissionListByRoleId",fetchType = FetchType.DEFAULT))
20             }
21     )
22     List<Role> findRoleListByUserId(@Param("userId") int userId);
23 }
RoleMapper
 1 package net.xdclass.xdclassshiro.dao;
 2 
 3 import net.xdclass.xdclassshiro.domain.Permission;
 4 import net.xdclass.xdclassshiro.domain.Role;
 5 import org.apache.ibatis.annotations.Param;
 6 import org.apache.ibatis.annotations.Select;
 7 
 8 import java.util.List;
 9 
10 public interface PermissionMapper {
11 
12     @Select("select p.id id,p.name name,p.url url from permission p left join role_permission rp on rp.permission_id = p.id\n" +
13             "where rp.role_id= #{roleId}")
14     List<Permission> findPermissionListByRoleId(@Param("roleId") int roleId);
15 
16 }
PermissionMapper

主啟動類添加mapper包掃描

package net.xdclass.xdclassshiro;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
//掃描mapper包
@MapperScan("net.xdclass.xdclassshiro.dao")
public class XdclassShiroApplication {

    public static void main(String[] args) {
        SpringApplication.run(XdclassShiroApplication.class, args);
    }

}

Service接口及實現:

 1 package net.xdclass.xdclassshiro.service;
 2 
 3 import net.xdclass.xdclassshiro.domain.User;
 4 import org.apache.ibatis.annotations.Select;
 5 
 6 public interface UserService {
 7     /**
 8      * 獲取用戶全部信息,包含角色權限
 9      * @param username
10      * @return
11      */
12     User findAllUserInfoByUsername(String username);
13 
14     /**
15      * 獲取用戶基本信息
16      * @param userId
17      * @return
18      */
19     User findSimpleUserInfoById(int userId);
20 
21     /**
22      * 根據用戶名查找用戶
23      * @param username
24      * @return
25      */
26     User findSimpleUserInfoByUsername(String username);
27 }
UserService
 1 package net.xdclass.xdclassshiro.service.impl;
 2 
 3 import net.xdclass.xdclassshiro.dao.RoleMapper;
 4 import net.xdclass.xdclassshiro.dao.UserMapper;
 5 import net.xdclass.xdclassshiro.domain.Role;
 6 import net.xdclass.xdclassshiro.domain.User;
 7 import net.xdclass.xdclassshiro.service.UserService;
 8 import org.springframework.beans.factory.annotation.Autowired;
 9 import org.springframework.stereotype.Service;
10 
11 import java.util.List;
12 
13 @Service
14 public class UserServiceImpl implements UserService {
15 
16     @Autowired
17     private UserMapper userMapper;
18     @Autowired
19     private RoleMapper roleMapper;
20 
21     @Override
22     public User findAllUserInfoByUsername(String username) {
23         User user = userMapper.findByUserName(username);
24         List<Role> roleList = roleMapper.findRoleListByUserId(user.getId());
25         user.setRoleList(roleList);
26         return user;
27     }
28 
29     @Override
30     public User findSimpleUserInfoById(int userId) {
31 
32         return userMapper.findById(userId);
33     }
34 
35     @Override
36     public User findSimpleUserInfoByUsername(String username) {
37         return userMapper.findByUserName(username);
38     }
39 }
UserServiceImpl

自定義realm

 1 package net.xdclass.xdclassshiro.config;  2 
 3 import net.xdclass.xdclassshiro.domain.Permission;  4 import net.xdclass.xdclassshiro.domain.Role;  5 import net.xdclass.xdclassshiro.domain.User;  6 import net.xdclass.xdclassshiro.service.UserService;  7 import org.apache.commons.lang3.StringUtils;  8 import org.apache.shiro.authc.AuthenticationException;  9 import org.apache.shiro.authc.AuthenticationInfo; 10 import org.apache.shiro.authc.AuthenticationToken; 11 import org.apache.shiro.authc.SimpleAuthenticationInfo; 12 import org.apache.shiro.authz.AuthorizationInfo; 13 import org.apache.shiro.authz.SimpleAuthorizationInfo; 14 import org.apache.shiro.realm.AuthorizingRealm; 15 import org.apache.shiro.subject.PrincipalCollection; 16 import org.springframework.beans.factory.annotation.Autowired; 17 
18 import java.util.ArrayList; 19 import java.util.Collections; 20 import java.util.List; 21 
22 /**
23  * 自定義realm,繼承AuthorizingRealm,重寫認證,授權的方法 24  */
25 public class CustomRealm extends AuthorizingRealm { 26  @Autowired 27     private UserService userService; 28 
29     /**
30  * 進行授權校驗 31  * @param principalCollection 32  * @return
33      */
34  @Override 35     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 36         User principal = (User) principalCollection.getPrimaryPrincipal(); 37         User user = userService.findAllUserInfoByUsername(principal.getUsername()); 38         List<String> roleList = new ArrayList<>(); 39         List<String> permissionList = new ArrayList<>(); 40         List<Role> userRoleList = user.getRoleList(); 41        // 把用戶的角色,權限放到對應的list中
42        if(userRoleList.size() >0 && userRoleList.get(0) != null){ 43             for (Role role : userRoleList){ 44  roleList.add(role.getName()); 45                 for (Permission p: role.getPermissionList()){ 46                     if(null != p.getName()){ 47  permissionList.add(p.getName()); 48  } 49  } 50  } 51  } 52         
53         SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); 54 simpleAuthorizationInfo.addRoles(roleList); 55 simpleAuthorizationInfo.addStringPermissions(permissionList); 56 // 返回simpleAuthorizationInfo對象,交給shiro框架去做權限校驗 57 return simpleAuthorizationInfo; 58  } 59 
60     /**
61  * 自定義shiro認證 62  * @param authenticationToken 63  * @return
64  * @throws AuthenticationException 65      */
66  @Override 67     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { 68         //從token中獲取用戶信息,token代表用戶輸入
69         String username = (String) authenticationToken.getPrincipal(); 70         User user = userService.findAllUserInfoByUsername(username); 71 
72         String pwd = user.getPassword(); 73         if(StringUtils.isBlank(pwd)){ 74 return null; 75 } 76 //org.apache.shiro.authc.SimpleAuthenticationInfo.SimpleAuthenticationInfo(java.lang.Object, java.lang.Object, java.lang.String) 77 // return new SimpleAuthenticationInfo(username,pwd,this.getClass().getName()); 78         // 使用shiro-reids插件,該插件默認會把SimpleAuthenticationInfo的第一個參數作為redis的key來使用,這里需要把username改為userc才能保證key的唯一性
79         return new SimpleAuthenticationInfo(user,pwd,this.getClass().getName()); 80  } 81 }

shiro配置類:

 1 package net.xdclass.xdclassshiro.config;  2 
 3 import org.apache.shiro.authc.credential.HashedCredentialsMatcher;  4 import org.apache.shiro.mgt.SecurityManager;  5 import org.apache.shiro.session.mgt.SessionManager;  6 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;  7 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;  8 import org.crazycake.shiro.RedisCacheManager;  9 import org.crazycake.shiro.RedisManager;  10 import org.crazycake.shiro.RedisSessionDAO;  11 import org.springframework.context.annotation.Bean;  12 import org.springframework.context.annotation.Configuration;  13 
 14 import javax.servlet.Filter;  15 import java.util.LinkedHashMap;  16 import java.util.Map;  17 
 18 /**
 19  * ShiroFilterFactoryBean配置,要加@Configuration注解  20  *  21  */
 22 @Configuration  23 public class ShiroConfig {  24 
 25 
 26     /**
 27  * @param securityManager  28  * @return
 29  * @Bean是一個方法級別上的注解,主要用在@Configuration注解的類里,也可以用在@Component注解的類里。添加的bean的id為方法名 等同於xml配置中的<bean id = " shiroFilter " class = " org.apache.shiro.spring.web.ShiroFilterFactoryBean " />  30      */
 31  @Bean  32     public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {  33         System.out.println("ShiroFilterFactoryBean.shiroFilter");  34         ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();  35         // 設置securityManager
 36  shiroFilterFactoryBean.setSecurityManager(securityManager);  37         // 如果訪問配置的這個接口時用戶還未登錄,則調用該接口登錄
 38         shiroFilterFactoryBean.setLoginUrl("/pub/need_login");  39         // 登錄成功后重定向url 前后端分離的,沒有這個調用
 40         shiroFilterFactoryBean.setSuccessUrl("/");  41         // 沒有頁面訪問權限(登錄了,未授權),調用該接口
 42         shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit");  43 
 44         // 設置自定義Filter
 45         Map<String, Filter> filterMap = new LinkedHashMap<>(); 46 filterMap.put("roleOrFilter", new CustomRolesAuthorizationFilter()); 47 // 綁定 48 shiroFilterFactoryBean.setFilters(filterMap);  49 
 50         // 注意,這里必須使用有序的LinkedHashMap,過濾器鏈是從上往下順序執行,一般將/**放在最后,否則部分路徑無法攔截
 51         Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();  52         // 登出過濾器
 53        filterChainDefinitionMap.put("/logout", "logout");  54         // 匿名訪問過濾器(游客訪問)
 55         filterChainDefinitionMap.put("/pub/**", "anon");  56         // 登陸用戶訪問
 57         filterChainDefinitionMap.put("/authc/**", "authc");  58         // 設置自定義Filter,只要是管理員角色或者root角色就能訪問
 59         filterChainDefinitionMap.put("/admin/**", "roleOrFilter[admin,root]");  60         // 配置roles[admin,root]表示必須同時具備admin和root角色才能訪問,實際應用不會這么用,使用自定義filter  61 // filterChainDefinitionMap.put("/admin/**", "roles[admin,root]");  62         // 有編輯權限才可以訪問 /video/update是數據庫配置的權限路徑
 63         filterChainDefinitionMap.put("/video/update", "perms[video_update]");  64         // authc: 定義的url必須通過認證才可以訪問 anon: url可以匿名訪問
 65         filterChainDefinitionMap.put("/**", "authc");  66          // 這里沒設置,過濾器不生效!!!
 67  shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);  68         return shiroFilterFactoryBean;  69  }  70 
 71  @Bean  72     public SecurityManager securityManager() {  73         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();  74         // 錯誤寫法:securityManager.setRealm(new CustomRealm());  75         // 不是前后端分離項目,這里可以不用設置sessionmanager
 76  securityManager.setSessionManager(sessionManager());  77        // 使用自定義的cacheManager  78  securityManager.setCacheManager(cacheManager());  79         // 必須通過spring實例化CustomRealm的實例之后,這里再通過customRealm()進行注入
 80  securityManager.setRealm(customRealm());  81         return securityManager;  82  }  83 
 84     /**
 85  * 注入realm  86  * @return
 87      */
 88  @Bean  89     public CustomRealm customRealm(){  90         CustomRealm customRealm = new CustomRealm();  91         // 設置密碼驗證器 使用明文密碼進行登錄測試時,需要將這里注釋掉,或者在數據庫存儲new SimpleHash("md5",pwd,null,2)加密過的密碼  92         //數據庫修改二當家小D的密碼為4280d89a5a03f812751f504cc10ee8a5,這里密碼驗證注釋放開,使用明文密碼訪問http://localhost:8080/pub/login,能夠登錄成功
 93  customRealm.setCredentialsMatcher(hashedCredentialsMatcher());  94         return customRealm;  95  }  96 
 97     /**
 98  * 設置自定義sessionManager  99  * @return
100      */
101  @Bean 102     public SessionManager sessionManager(){ 103         CustomSessionManager sessionManager = new CustomSessionManager(); 104         // 設置session過期時間,不設置默認是30分鍾,單位ms
105         sessionManager.setGlobalSessionTimeout(200000); 106         // 設置session持久化到redis中,這樣服務器重啟后,用戶還可以通過之前的session進行操作
107  sessionManager.setSessionDAO(redisSessionDAO()); 108         return sessionManager; 109  } 110 
111     /**
112  * 密碼加解密規則 113  * @return
114      */
115  @Bean 116     public HashedCredentialsMatcher hashedCredentialsMatcher(){ 117         HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); 118         // 設置散列算法,進行加解密
119         credentialsMatcher.setHashAlgorithmName("md5"); 120         // 設置散列迭代次數,2 表示 hash之后再次hash
121         credentialsMatcher.setHashIterations(2); 122         return credentialsMatcher; 123  } 124 
125 
126     /**
127  * 配置redisManager,使用redis來存數session數據 128  * @return
129      */
130     public RedisManager getRedisManager(){ 131 RedisManager redisManager = new RedisManager(); 132 // redisManager.setHost("192.168.5.112"); 133 redisManager.setHost("192.168.0.114"); //單機redis地址 134 redisManager.setPort(8007); //單機redis端口 135 return redisManager; 136  } 137 
138     /**
139  * 配置緩存管理器具體實現類,然后添加到securityManager里面 140  * @return
141      */
142     public RedisCacheManager cacheManager(){ 143 RedisCacheManager redisCacheManager = new RedisCacheManager(); 144  redisCacheManager.setRedisManager(getRedisManager()); 145 // 設置緩存過期時間,單位秒 146 redisCacheManager.setExpire(1800); 147 return redisCacheManager; 148  } 149 
150     /**
151  * 1.通過shiro redis插件自定義session持久化 152  * 2.在shiro的sessionManager中配置session持久化 153  * @return
154      */
155     public RedisSessionDAO redisSessionDAO(){ 156         RedisSessionDAO redisSessionDao = new RedisSessionDAO(); 157  redisSessionDao.setRedisManager(getRedisManager()); 158         // session持久化的過期時間,單位s,如果不設置,默認使用session的過期時間,如果設置了,則使用這里設置的過期時間
159         redisSessionDao.setExpire(1800); 160 
161         //設置自定義sessionId生成
162         redisSessionDao.setSessionIdGenerator(new CustomSessionIdGenerator()); 163         return redisSessionDao; 164  } 165 }

shiro配置類中使用到的自定義的內容:

(1)前后端分離情況下自定義SessionManager

 1 package net.xdclass.xdclassshiro.config;
 2 
 3 import org.apache.commons.lang3.StringUtils;
 4 import org.apache.shiro.session.mgt.SessionKey;
 5 import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
 6 import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
 7 import org.apache.shiro.web.util.WebUtils;
 8 
 9 import javax.servlet.ServletRequest;
10 import javax.servlet.ServletResponse;
11 import java.io.Serializable;
12 
13 /**
14  * 自定義sessionManager
15  * 繼承DefaultWebSessionManager並重寫該類的getSessionId(javax.servlet.ServletRequest, javax.servlet.ServletResponse)方法
16  * 意義:
17  * 前后端分離時,用戶訪問接口時,接口生成一個token保存到map中,
18  * 下次用戶訪問接口時帶上這個token,后端進行校驗
19  */
20 public class CustomSessionManager extends DefaultWebSessionManager {
21 
22     /**
23      * 請求頭中token的key名
24      */
25     private static  final String AUTHORIZATION = "token";
26 
27     public CustomSessionManager() {
28         super();
29     }
30 
31     @Override
32     protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
33         String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
34         if(StringUtils.isBlank(sessionId)){
35             return super.getSessionId(request,response);
36         } else {
37 //org.apache.shiro.web.session.mgt.DefaultWebSessionManager.getReferencedSessionId id不為null時的分支處理
38             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,  ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
39             // 校驗sessionId是否有效
40             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
41             // automatically mark it valid here,if it is invalid, the onUnknowSesson method
42             // below will be invoked and we`ll remove the attribute at that time.
43             // 標記當前sessionId有效
44             request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
45             return sessionId;
46         }
47 
48 
49     }
50 }

(2) 自定義Shiro Filter過濾器,  配置不同的角色,驗證自定義過濾器是否有效

 1 package net.xdclass.xdclassshiro.config;
 2 
 3 import org.apache.shiro.subject.Subject;
 4 import org.apache.shiro.util.CollectionUtils;
 5 import org.apache.shiro.web.filter.authz.AuthorizationFilter;
 6 
 7 import javax.servlet.ServletRequest;
 8 import javax.servlet.ServletResponse;
 9 import java.util.Set;
10 
11 /**
12  * 1.自定義filter
13  * 2. 把自定義filter加入到ShiroConfig中去
14  */
15 public class CustomRolesAuthorizationFilter extends AuthorizationFilter {
16     @Override
17     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
18         Subject subject = this.getSubject(request, response);
19         String[] rolesArray = (String[]) ((String[]) mappedValue);
20         if (rolesArray != null && rolesArray.length != 0) {
21             Set<String> roles = CollectionUtils.asSet(rolesArray);
22             /*RolesAuthorizationFilter 中是return subject.hasAllRoles(roles),同時具備所有角色才能夠訪問
23             這里改為遍歷角色 ,當前subject是roles中的任意一個,則有權限訪問
24             * */
25             for (String role : roles) {
26                 if (subject.hasRole(role)) {
27                     return true;
28                 }
29             }
30             // return subject.hasAllRoles(roles);
31         }
32         // 沒有角色限制,可以直接訪問
33         return true;
34     }
35 }

(3)Redis整合CacheManager ,見shiroConfig類 78行, 130-136行,142-148行

shiro+redis緩存插件依賴:

 <dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>3.1.0</version>
</dependency>

(4)Redis整合SessionManager 管理Session會話

為什么session也要持久化?
-——應用保活,重啟應用,用戶無感知,可以繼續以原先的狀態繼續訪問:
如果不做session持久化,假如一個已經登錄的用戶正在做新增操作,在還沒有提交之前,后台服務器進行了重啟,然后用戶編輯完數據進行提交,這時服務器因為重啟,
之前的session丟失,后台檢測到用戶成了未登錄狀態,就跳轉到用戶登錄頁面,這樣的用戶體驗很不友好,因此session需要進行持久化

怎么做session持久化:參考shiroconfig類的配置,需要注意的是,DO對象需要實現序列化接口 Serializable 

 //配置session持久化
     customSessionManager.setSessionDAO(redisSessionDAO());       
       /**
       * 自定義session持久化
       * @return*/
      public RedisSessionDAO redisSessionDAO(){
          RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
          redisSessionDAO.setRedisManager(getRedisManager());
          return redisSessionDAO;
      }

(5)自定義sessionId

 1 package net.xdclass.xdclassshiro.config;
 2 
 3 
 4 import org.apache.shiro.session.Session;
 5 import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
 6 
 7 import java.io.Serializable;
 8 import java.util.UUID;
 9 
10 /**
11  *  自定義sessionId
12  */
13 public class CustomSessionIdGenerator implements SessionIdGenerator {
14     @Override
15     public Serializable generateId(Session session) {
16         return UUID.randomUUID().toString().replaceAll("-", "");
17     }
18 }

接口開發:

(1)游客訪問接口:

package net.xdclass.xdclassshiro.controller;

import net.xdclass.xdclassshiro.domain.JsonData;
import net.xdclass.xdclassshiro.domain.UserQuery;
import net.xdclass.xdclassshiro.service.UserService;
import org.apache.commons.collections.MapUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.ToLongBiFunction;

@RestController
@RequestMapping("/pub")
public class PublicController {

    @Autowired
    private UserService userService;

    //http://localhost:8080/pub/find_user_info?username=jack
    @GetMapping("/find_user_info")
    public Object queryUserInfo(@RequestParam("username") String username) {
        return userService.findAllUserInfoByUsername(username);
    }

    @RequestMapping("/need_login")
    public JsonData needLogin(){
        return JsonData.buildError("請登錄",-2);
    }

// shiroconfig配置類里面配置的路徑
    @RequestMapping("/not_permit")
    public JsonData noPermit(){
        return JsonData.buildError("沒有權限訪問",-3);
    }

    //首頁,任何角色的都可以訪問
    @RequestMapping("/index")
    public JsonData index(){
        List<String> videoList = new ArrayList<>();
        videoList.add("Mysql零基礎入門到實戰 數據庫教程");
        videoList.add("Redis高並發高可用集群百萬級秒殺實戰");
        videoList.add("Zookeeper+Dubbo視頻教程 微服務教程分布式教程");
        videoList.add("2019年新版本RocketMQ4.X教程消息隊列教程");
        videoList.add("微服務SpringCloud+Docker入門到高級實戰");
        return JsonData.buildSuccess(videoList);
    }


    /**
     * 登錄接口
     * @param userQuery
     * @param request
     * @param response
     * @return
     */
    @PostMapping("/login")
    public JsonData login(@RequestBody UserQuery userQuery, HttpServletRequest request,
                          HttpServletResponse response){
        Map<String,Object> info = new HashMap<>();
        try {
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken(userQuery.getName(),userQuery.getPwd());
            subject.login(token);

            info.put("msg", "登錄成功");
            info.put("session_id",subject.getSession().getId());
            return JsonData.buildSuccess(info);
        } catch (Exception e){
            e.printStackTrace();
            return JsonData.buildError("登錄失敗");
        }
    }
}

(2)admin角色才能訪問的接口

package net.xdclass.xdclassshiro.controller;

import net.xdclass.xdclassshiro.domain.JsonData;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 *  只有admin管理員角色才能訪問的接口
 */
@RestController
@RequestMapping("admin")
public class AdminController {

    /**
     *  視頻播放列表接口
     * @return
     */
    @PostMapping("/video/order")
    public JsonData findPlayRecord(){
        Map<String ,String> recordMap = new HashMap<>();
        recordMap.put("SpringBoot入門到高級實戰","300元");
        recordMap.put("Cloud微服務入門到高級實戰","100元");
        recordMap.put("分布式緩存Redis","90元");
        return JsonData.buildSuccess(recordMap);
    }
}

(3)只有登錄后才能訪問到的接口

 1 package net.xdclass.xdclassshiro.controller;
 2 
 3 import net.xdclass.xdclassshiro.domain.JsonData;
 4 import org.springframework.web.bind.annotation.PostMapping;
 5 import org.springframework.web.bind.annotation.RequestMapping;
 6 import org.springframework.web.bind.annotation.RestController;
 7 
 8 import java.util.HashMap;
 9 import java.util.Map;
10 
11 /**
12  *  登錄才能訪問的接口,使用@RestController,返回json數據到前端
13  */
14 @RestController
15 @RequestMapping("authc")
16 public class OrderController {
17 
18     /**
19      *  視頻播放列表接口
20      * @return
21      */
22     @PostMapping("/video/play_record")
23     public JsonData findPlayRecord(){
24         Map<String ,String> recordMap = new HashMap<>();
25         recordMap.put("SpringBoot入門到高級實戰","第8章第1集");
26         recordMap.put("Cloud微服務入門到高級實戰","第4章第10集");
27         recordMap.put("分布式緩存Redis","第10章第3集");
28         return JsonData.buildSuccess(recordMap);
29     }
30 }

(4)具有指定資源訪問權限(video/update)的用戶才能訪問的接口

 1 package net.xdclass.xdclassshiro.controller;
 2 
 3 import net.xdclass.xdclassshiro.domain.JsonData;
 4 import org.springframework.web.bind.annotation.PostMapping;
 5 import org.springframework.web.bind.annotation.RequestMapping;
 6 import org.springframework.web.bind.annotation.RestController;
 7 
 8 import java.util.HashMap;
 9 import java.util.Map;
10 
11 /**
12  *  只有具有video/update權限才能訪問的接口
13  *  select u.username,u.password,ur.remarks,r.name,r.description,rp.role_id,p.name from user u
14  * left join user_role ur on u.id = ur.user_id
15  * left join role r on r.id = ur.role_id
16  * left join role_permission rp on rp.role_id = r.id
17  * left join permission p on p.id = rp.permission_id
18  * where  p.id = 1
19  * 查詢出來的用戶才能訪問該接口
20  */
21 @RestController
22 @RequestMapping("video")
23 public class VideoController {
24     @PostMapping("/update")
25     public JsonData findPlayRecord(){
26         return JsonData.buildSuccess("video更新成功");
27     }
28 }

 代碼地址:https://github.com/liuch0228/shiro-learn.git   

參考資料:

 Shiro權限管理框架詳解

Shiro權限控制+整合shiro  https://blog.csdn.net/qq_43652509/article/details/88074832

 

 

 
        

 





 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM