• 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
    " +
    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
    " +
    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

    
    
    




     

  • 相关阅读:
    TMainMenu 类[三] 手动建立菜单(5) : 给菜单项添加事件
    TMainMenu 类[二] 成员列表
    TMainMenu 类[三] 手动建立菜单(4) : 添加分割线与隐藏多余的分割线
    初学 Delphi 嵌入汇编[30] 寄存器表
    TMainMenu 类[三] 手动建立菜单(6) : 更换菜单
    TMainMenu 类[三] 手动建立菜单(7) : 指定快捷键
    关于网络编程(服务端)的一些笔记 roen的专栏 博客频道 CSDN.NET
    关于 多进程epoll 与 “惊群”问题
    乱谈服务器编程 MrDB 博客园
    再谈select, iocp, epoll,kqueue及各种I/O复用机制 Shallway 博客频道 CSDN.NET
  • 原文地址:https://www.cnblogs.com/enjoyjava/p/12089485.html
Copyright © 2020-2023  润新知