Spring Boot 2.x+Shiro+Redis

Updated on in 编程技术 with 0 views and 0 comments

一直想好好的学习一下安全方面的框架,自己对于这个方面的知识很欠缺,借助当前公司项目的机会,认真的研究了两天 Shiro 这个高度可定制的框架(虽然在 Spring Boot 中集成 Spring Security 更为方便,但是看了一天的相关资料,感觉还是 Shiro 更简单,以后再认真学习 Spring Security)。

项目数据库使用的是 MongoDB,稍后使用 MySQL 做一个集成,毕竟用到 MongoDB 的不多,其实都是大同小异,不过在 SpringBoot 中使用 MongoDB 特别方便。

1 准备

  • Spring Boot 2.x
  • Shiro
  • Redis
  • Maven 3.6
  • JDK 1.8
  • IDEA

2 Shiro 介绍

直达官网:http://shiro.apache.org

image.png

  • Shiro 中四大模块如图:
    • Authentication,身份证认证,一般就是登录
    • Authorization,授权,给用户分配角色或者访问某些资源的权限
    • Session Management, 用户的会话管理员,多数情况下是 Web session
    • Cryptography, 数据加解密,比如密码加解密等

Shiro 权限控制流程及相关概念(http://shiro.apache.org/architecture.html),其中:

  • Subject:主体,如用户或程序,主体去访问系统或者资源
  • SecurityManager:安全管理器,Subject 的认证和授权都需在安全管理器下进行
  • Authenticator:认证器,主要负责 Subject 的认证
  • Realm:数据域,Shiro 和安全数据的连接器,类似于 JDBC 连接数据库; 通过 realm 获取认证授权相关信息,在集成时这个部分需要重写 Shiro 指定的方法
  • Authorizer:授权器,主要负责 Subject 的授权, 控制 subject 拥有的角色或者权限
  • Cryptography:加解密,Shiro 中包含易于使用和理解的数据加解密方法,简化了很多复杂的 API
  • Cache Manager:缓存管理器,比如认证或授权信息,通过缓存进行管理,提高性能

3 集成 Shiro

3.1 数据库设计

image.png

3.2 创建项目

使用 IDEA 创建一个空的 Spring Boot 项目,勾选相关 Web 依赖和数据库依赖,这里把相关依赖全部罗列出来

 1    <properties>
 2        <java.version>1.8</java.version>
 3        <shiro-spring.version>1.5.1</shiro-spring.version>
 4        <shiro-redis.version>3.2.3</shiro-redis.version>
 5        <druid.version>1.1.20</druid.version>
 6        <fastjson.version>1.2.67</fastjson.version>
 7        <mybatis-plus.version>3.3.0</mybatis-plus.version>
 8    </properties>
 9
10    <dependencies>
11        <dependency>
12            <groupId>org.springframework.boot</groupId>
13            <artifactId>spring-boot-starter-web</artifactId>
14            <exclusions>
15                <exclusion>
16                    <groupId>org.springframework.boot</groupId>
17                    <artifactId>spring-boot-starter-json</artifactId>
18                </exclusion>
19            </exclusions>
20        </dependency>
21        <!-- FastJson -->
22        <dependency>
23            <groupId>com.alibaba</groupId>
24            <artifactId>fastjson</artifactId>
25            <version>${fastjson.version}</version>
26        </dependency>
27        <!--mysql-->
28        <dependency>
29            <groupId>mysql</groupId>
30            <artifactId>mysql-connector-java</artifactId>
31        </dependency>
32        <!-- druid数据库连接池 -->
33        <dependency>
34            <groupId>com.alibaba</groupId>
35            <artifactId>druid</artifactId>
36            <version>${druid.version}</version>
37        </dependency>
38        <!--mybatis-plus-->
39        <dependency>
40            <groupId>com.baomidou</groupId>
41            <artifactId>mybatis-plus-boot-starter</artifactId>
42            <version>${mybatis-plus.version}</version>
43        </dependency>
44        <!--整合shiro-->
45        <dependency>
46            <groupId>org.apache.shiro</groupId>
47            <artifactId>shiro-spring</artifactId>
48            <version>${shiro-spring.version}</version>
49        </dependency>
50        <!--整合shiro-redis缓存插件-->
51        <dependency>
52            <groupId>org.crazycake</groupId>
53            <artifactId>shiro-redis</artifactId>
54            <version>${shiro-redis.version}</version>
55            <exclusions>
56                <exclusion>
57                    <groupId>org.apache.shiro</groupId>
58                    <artifactId>shiro-core</artifactId>
59                </exclusion>
60            </exclusions>
61        </dependency>
62        <dependency>
63            <groupId>org.projectlombok</groupId>
64            <artifactId>lombok</artifactId>
65            <optional>true</optional>
66        </dependency>
67    </dependencies>

3.3 配置 application.yml 文件

配置数据库连接相关信息

 1spring:
 2  datasource:
 3    type: com.alibaba.druid.pool.DruidDataSource
 4    driver-class-name: com.mysql.cj.jdbc.Driver
 5    url: jdbc:mysql://localhost:3306/db_role?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
 6    username: db_role
 7    password: db_role
 8
 9  redis:
10    database: 1                     # Redis数据库索引(默认为0)
11    host: 192.168.213.146     # Redis服务器地址
12    port: 6379                      # Redis服务器连接端口
13    password: coctrl              # Redis服务器连接密码(默认为空)
14    timeout: 0                        # 连接超时时间(毫秒)
15    jedis:
16      pool:
17        max-active: 8
18        max-idle: 8
19        max-wait: -1ms
20        min-idle: 0
21
22# SQL打印
23mybatis-plus:
24  configuration:
25    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.4 集成 mybatis-plus

3.4.1 entity

实体类与数据库一一对应就行,可以配合 mybatis-plus 直接自动生成

3.4.2 mapper

  • UserMapper.java

只写了一个用于登录是通过用户名查对应信息的方法

1@Component
2public interface UserMapper extends BaseMapper<User> {
3     /**
4     * 根据用户名查询用户信息,登录
5     * @param username
6     * @return
7     */
8    UserVO findUserInfoByUsername(String username);
9}

UserVO 是一个自定义的实体类,用于封装用户、角色、权限信息,具体可以看项目源码

  • UserMapper.xml
 1<?xml version="1.0" encoding="UTF-8"?>
 2<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 3<mapper namespace="com.kangaroohy.shiroredis.mapper.UserMapper">
 4
 5    <resultMap id="userInfoMap" type="com.kangaroohy.shiroredis.domain.entity.vo.UserVO" >
 6        <id property="id" column="u_id" />
 7        <result property="username" column="u_username"/>
 8        <result property="password" column="u_password"/>
 9        <result property="gender" column="u_gender"/>
10        <result property="updateTime" column="u_updateTime"/>
11        <collection property="roleList" ofType="com.kangaroohy.shiroredis.domain.entity.vo.RoleVO" >
12            <id property="role.id" column="r_id" />
13            <result property="role.name" column="r_name"/>
14            <result property="role.description" column="r_description"/>
15            <collection property="permissionList" ofType="com.kangaroohy.shiroredis.domain.entity.po.Permission" >
16                <id property="id" column="p_id" />
17                <result property="name" column="p_name"/>
18                <result property="url" column="p_url"/>
19            </collection>
20        </collection>
21    </resultMap>
22
23    <select id="findUserInfoByUsername" resultMap="userInfoMap">
24        SELECT
25            u.id u_id,
26            u.username u_username,
27            u.password u_password,
28            u.gender u_gender,
29            u.update_time u_updateTime,
30            r.id r_id,
31            r.name r_name,
32            r.description r_description,
33            p.id p_id,
34            p.name p_name,
35            p.url p_url
36        FROM
37            t_user u
38        LEFT JOIN t_user_role ur ON ur.uid = u.id
39        LEFT JOIN t_role r ON r.id = ur.rid
40        LEFT JOIN t_role_permission rp ON rp.rid = r.id
41        LEFT JOIN t_permission p ON p.id = rp.pid
42        WHERE
43            u.username = #{username}
44    </select>
45</mapper>
46

3.4.3 service

这个地方就是一个简单的调用 mapper 中的方法,具体可以看源码

不要忘记启动器类配置 MyBatis 的 mapper 扫描路径,这个集成 mybatis-plus 的时候应该有配置

1@MapperScan("com.kangaroohy.shiroredis.mapper")

3.5 配置 Shiro

3.5.1 自定义 Realm:CustomRealm.java

 1@Slf4j
 2public class CustomRealm extends AuthorizingRealm {
 3    @Autowired
 4    private UserService userService;
 5
 6    /**
 7     * 授权时调用(权限校验)
 8     *
 9     * @param principalCollection
10     * @return
11     */
12    @Override
13    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
14        //获得当前对象
15        UserVO user = (UserVO) principalCollection.getPrimaryPrincipal();
16
17        log.info("用户 {} 调用了 doGetAuthorizationInfo 进行授权", user.getUsername());
18
19        UserVO info = userService.findUserInfoByUsername(user.getUsername());
20
21        Set<String> stringRolesList = new HashSet<>();
22        Set<String> stringPermissionsList = new HashSet<>();
23
24        List<RoleVO> roleList = info.getRoleList();
25        for (RoleVO role : roleList) {
26            stringRolesList.add(role.getRole().getName());
27            List<Permission> permissionList = role.getPermissionList();
28            for (Permission permission : permissionList){
29                if (permission != null){
30                    stringPermissionsList.add(permission.getName());
31                }
32            }
33        }
34
35        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
36        simpleAuthorizationInfo.addRoles(stringRolesList);
37        simpleAuthorizationInfo.addStringPermissions(stringPermissionsList);
38        return simpleAuthorizationInfo;
39    }
40
41    /**
42     * 登录时调用
43     *
44     * @param authenticationToken
45     * @return
46     * @throws AuthenticationException
47     */
48    @Override
49    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
50        //从token中获得用户信息,这个token是用户的输入信息
51        String username = (String) authenticationToken.getPrincipal();
52        UserVO info = userService.findUserInfoByUsername(username);
53
54        //取密码
55        String password = info.getPassword();
56        if (password == null || "".equals(password)) {
57            return null;
58        }
59        //设置盐值,使用账号作为盐值
60        ByteSource salt = ByteSource.Util.bytes(username);
61        //参数一传入对象,便于全局获取当前对象信息
62        return new SimpleAuthenticationInfo(info, password, salt, getName());
63    }
64}

Realm 能做的工作主要有以下几个方面:

  • 验证是否能登录,并返回验证信息(getAuthenticationInfo 方法)
  • 验证是否有访问指定资源的权限,并返回所拥有的所有权限(getAuthorizationInfo 方法)
  • 判断是否支持 token(例如:HostAuthenticationToken,UsernamePasswordToken 等)(supports 方法)

自定义 Realm 中实现的是前两个

3.5.2 自定义 SessionManager:CustomSessionManager.java

将生成的 sessionId 作为认证时的 token,登录之后的每次请求,header 中必须携带 token 参数

 1public class CustomSessionManager extends DefaultWebSessionManager {
 2
 3    private static final String AUTHORIZATION = "token";
 4
 5    public CustomSessionManager() {
 6        super();
 7    }
 8
 9    @Override
10    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
11        String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
12        if (sessionId != null) {
13            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
14                    ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
15            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
16            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
17            return sessionId;
18        } else {
19            return super.getSessionId(request, response);
20        }
21    }
22}

3.5.3 自定义 sessionId 生成器:CustomSessionId.java

1public class CustomSessionId implements SessionIdGenerator {
2    @Override
3    public Serializable generateId(Session session) {
4        return "kangaroohy" + UUID.randomUUID().toString().replace("-", "");
5    }
6}

3.5.4 shiro 配置文件:ShiroConfig.java

  1@Configuration
  2public class ShiroConfig {
  3
  4    @Value("${spring.redis.database}")
  5    private Integer database;
  6
  7    @Value("${spring.redis.host}")
  8    private String host;
  9
 10    @Value("${spring.redis.port}")
 11    private Integer port;
 12
 13    @Value("${spring.redis.password}")
 14    private String password;
 15
 16    @Bean(name = "shiroFilter")
 17    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
 18        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
 19
 20        //设置securityManager
 21        shiroFilterFactoryBean.setSecurityManager(securityManager);
 22        //需要登陆的接口,没有登陆就访问需要登陆的接口,则调用这个接口(非前后端分离,则调用登录界面)
 23        shiroFilterFactoryBean.setLoginUrl("/pub/need_login");
 24        //登录成功,跳转url,前后端分离的情况下,则没有这个接口的调用
 25        shiroFilterFactoryBean.setSuccessUrl("/");
 26        //已经登陆,但是访问的接口没有权限,类似403界面
 27        shiroFilterFactoryBean.setUnauthorizedUrl("/pub/unauthorized");
 28
 29        //自定义退出后重定向的地址,前后端分离,用于返回退出成功信息
 30        LogoutFilter logout = new LogoutFilter();
 31        logout.setRedirectUrl("/pub/logout");
 32
 33        //设置自定义filter
 34        Map<String, Filter> filterMap = new LinkedHashMap<>();
 35        //自定义退出filter
 36        filterMap.put("logout",logout);
 37        shiroFilterFactoryBean.setFilters(filterMap);
 38
 39        //拦截器路径,务必设置为LinkedHashMap,否则部分路径拦截时有时无(不生效),因为如果使用HashMap,无序,而LinkedHashMap,有序
 40        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
 41        //退出过滤器,退出成功后,默认返回LoginUrl接口,即登录页,通过自定义,改到/pub/logout接口
 42        filterChainDefinitionMap.put("/logout", "logout");
 43        //匿名可访问
 44        filterChainDefinitionMap.put("/pub/**", "anon");
 45        //登录后可以访问
 46        filterChainDefinitionMap.put("/user/**", "authc");
 47        //通过认证才能访问
 48        filterChainDefinitionMap.put("/**", "authc");
 49
 50        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
 51        return shiroFilterFactoryBean;
 52    }
 53
 54    @Bean(name = "securityManager")
 55    public SecurityManager securityManager() {
 56        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
 57        //如果不是前后端分离,不用设置这个
 58        securityManager.setSessionManager(sessionManager());
 59        //设置自定义的cacheManager
 60        securityManager.setCacheManager(cacheManager());
 61        //设置realm(推荐放到最后)
 62        securityManager.setRealm(customRealm());
 63        return securityManager;
 64    }
 65
 66    /**
 67     * 配置具体的cache实现类
 68     */
 69    public org.crazycake.shiro.RedisCacheManager cacheManager(){
 70        org.crazycake.shiro.RedisCacheManager redisCacheManager = new RedisCacheManager();
 71        redisCacheManager.setRedisManager(redisManager());
 72        //过期时间,单位 s
 73        redisCacheManager.setExpire(Constant.CACHE_EXPIRE_TIME);
 74        redisCacheManager.setPrincipalIdFieldName(Constant.ACCOUNT);
 75        redisCacheManager.setKeyPrefix(Constant.ACTIVE_SHIRO_CACHE);
 76        return redisCacheManager;
 77    }
 78
 79    /**
 80     * 自定义realm
 81     * @return
 82     */
 83    @Bean(name = "customRealm")
 84    public CustomRealm customRealm() {
 85        CustomRealm realm = new CustomRealm();
 86        realm.setCredentialsMatcher(credentialsMatcher());
 87        return realm;
 88    }
 89
 90    /**
 91     * 密码加解密规则设置
 92     * @return
 93     */
 94    @Bean(name = "credentialsMatcher")
 95    public HashedCredentialsMatcher credentialsMatcher() {
 96        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
 97        //设置散列算法,这里使用MD5
 98        credentialsMatcher.setHashAlgorithmName(Constant.HASH_NAME);
 99        //散列次数
100        credentialsMatcher.setHashIterations(Constant.HASH_TIME);
101        return credentialsMatcher;
102    }
103
104    @Bean(name = "sessionManager")
105    public SessionManager sessionManager(){
106        CustomSessionManager sessionManager = new CustomSessionManager();
107        //过期时间,默认30分钟超时,方法中单位是毫秒,此处15分钟过期:15 * 60 * 1000
108        sessionManager.setGlobalSessionTimeout(Constant.SESSION_EXPIRE_TIME);
109        //配置session持久化
110        sessionManager.setSessionDAO(redisSessionDAO());
111        sessionManager.setSessionIdCookieEnabled(false);
112        sessionManager.setSessionIdUrlRewritingEnabled(false);
113        sessionManager.setDeleteInvalidSessions(true);
114        return sessionManager;
115    }
116
117    /**
118     *  自定义Session持久化
119     * @return
120     */
121    public RedisSessionDAO redisSessionDAO(){
122        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
123        redisSessionDAO.setRedisManager(redisManager());
124        redisSessionDAO.setSessionIdGenerator(new CustomSessionId());
125        redisSessionDAO.setKeyPrefix(Constant.ACTIVE_SHIRO_SESSION);
126        return redisSessionDAO;
127    }
128
129    /**
130     *  配置redisManager
131     */
132    public RedisManager redisManager(){
133        RedisManager redisManager = new RedisManager();
134        redisManager.setDatabase(database);
135        //在shiro-redis 3.2.3版本中,host合并了port
136        redisManager.setHost(host + ":" + port);
137        //redisManager.setPort(port);
138        redisManager.setPassword(password);
139        return redisManager;
140    }
141
142    /**
143     * 管理shiro一些Bean的生命周期,初始化和销毁
144     * @return
145     */
146    @Bean(name = "lifecycleBeanPostProcessor")
147    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
148        return new LifecycleBeanPostProcessor();
149    }
150
151    /**
152     * 使注解生效,不加这个,shrio的AOP注解不生效
153     * 如 Controller中 shiro的 @RequiresGust(游客可以访问) 注解
154     * @return
155     */
156    @Bean(name = "authorizationAttributeSourceAdvisor")
157    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
158        AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor();
159        sourceAdvisor.setSecurityManager(securityManager());
160        return sourceAdvisor;
161    }
162
163    /**
164     * 用来扫描上下文,寻找所有的Advisor(通知器),将符合条件的Advisor应用到切入点的Bean中
165     * 需要在LifecycleBeanPostProcessor创建之后才可以创建
166     * @return
167     */
168    @Bean
169    @DependsOn("lifecycleBeanPostProcessor")
170    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
171        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
172        // 强制使用cglib,防止重复代理和可能引起代理出错的问题
173        // https://zhuanlan.zhihu.com/p/29161098
174        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
175        return defaultAdvisorAutoProxyCreator;
176    }
177}

3.5.5 登录用到的 controller:PublicController.java

 1@RestController
 2@RequestMapping("/pub")
 3@Slf4j
 4public class PublicController {
 5
 6    UserService userService;
 7
 8    public PublicController(UserService userService) {
 9        this.userService = userService;
10    }
11
12    @GetMapping("/need_login")
13    public RestResult<String> needLogin() {
14        return RestResult.error(RestCode.NEED_LOGIN_ERROR);
15    }
16
17    @GetMapping("/unauthorized")
18    public RestResult<String> unauthorized() {
19        return RestResult.error(RestCode.AUTHORIZATION_ERROR);
20    }
21
22    @PostMapping("/login")
23    public RestResult<UserVO> login(@RequestParam String username, @RequestParam String password, HttpServletResponse response) {
24        Subject subject = SecurityUtils.getSubject();
25        //踢出之前登录状态,同一账号 同一时间 只能在同一个地方登录
26        kickOutBefore(username.trim());
27
28        UserVO userInfo = userService.findUserInfoByUsername(username.trim());
29        //用户名查询用户
30        if (userInfo == null) {
31            return RestResult.error("账号不存在");
32        }
33        try {
34            UsernamePasswordToken userPwdToken = new UsernamePasswordToken(username.trim(), password.trim());
35            subject.login(userPwdToken);
36            String token = subject.getSession().getId().toString();
37            UserVO info = userService.findUserInfoByUsername(username.trim());
38            info.setPassword(null);
39            JedisUtil.setJson(Constant.ACTIVE_SHIRO_TOKEN + username.trim(), token, Constant.ONLINE_TOKEN_EXPIRE_TIME);
40            response.setHeader(Constant.AUTHORIZATION, token);
41            response.setHeader("Access-Control-Expose-Headers", Constant.AUTHORIZATION);
42            return RestResult.ok(info);
43        } catch (Exception e) {
44            e.printStackTrace();
45            return RestResult.error("用户名或密码错误");
46        }
47    }
48
49    /**
50     * 根据userId踢出 同一用户 不同浏览器登录session
51     * @param username 账号
52     */
53    private void kickOutBefore(String username) {
54        String key = Constant.ACTIVE_SHIRO_TOKEN + username.trim();
55        if (JedisUtil.exists(key)) {
56            String value = JedisUtil.getJson(key);
57            if (JedisUtil.exists(Constant.ACTIVE_SHIRO_SESSION + value)) {
58                JedisUtil.delete(key, Constant.ACTIVE_SHIRO_SESSION + value);
59            } else {
60                JedisUtil.delete(key);
61            }
62        }
63    }
64}

3.6 返回 JSON 数据的自定义类

  • RestCode.java
 1public enum RestCode {
 2    //运行时错误
 3    UNKNOWN_ERROR(-1, "Unknown Error! Contact the administrator if this problem continues."),
 4    //操作成功
 5    SUCCESS(200, "SUCCESS"),
 6    //请求参数错误、不合法
 7    PARAM_ERROR(400, "Illegal Parameter!"),
 8    //需要登录才能访问
 9    NEED_LOGIN_ERROR(401, "Please login and visit again!"),
10    //没有访问权限,禁止访问
11    AUTHORIZATION_ERROR(403, "You are forbidden to view it!"),
12    //资源不存在
13    NOTFOUND_ERROR(404, "Not Found"),
14    //方法不支持
15    NOT_SUPPORT_ERROR(500, "Not support this method");
16
17    private Integer code;
18    private String message;
19
20    RestCode(){
21    }
22
23    RestCode(Integer code, String message) {
24        this.code = code;
25        this.message = message;
26    }
27
28    public Integer getCode() {
29        return code;
30    }
31
32    public void setCode(Integer code) {
33        this.code = code;
34    }
35
36    public String getMessage() {
37        return message;
38    }
39
40    public void setMessage(String message) {
41        this.message = message;
42    }
43}
  • RestResult.java
 1@Data
 2public class RestResult<T> implements Serializable {
 3
 4    private static final long serialVersionUID = 7711799662216684129L;
 5
 6    @JSONField(ordinal = 1)
 7    public int code;
 8
 9    @JSONField(ordinal = 2)
10    private String msg;
11
12    @JSONField(ordinal = 3)
13    private T data;
14
15    public RestResult() {
16    }
17
18    public RestResult(int code, String msg) {
19        this.code = code;
20        this.msg = msg;
21    }
22
23    public RestResult(int code, String msg, T data) {
24        this.code = code;
25        this.msg = msg;
26        this.data = data;
27    }
28
29    public static <T> RestResult<T> ok() {
30        return new RestResult<>(RestCode.SUCCESS.getCode(), RestCode.SUCCESS.getMessage());
31    }
32
33    public static <T> RestResult<T> ok(String msg) {
34        return new RestResult<>(RestCode.SUCCESS.getCode(), msg);
35    }
36
37    public static <T> RestResult<T> ok(T data) {
38        return new RestResult<>(RestCode.SUCCESS.getCode(), RestCode.SUCCESS.getMessage(), data);
39    }
40
41    public static <T> RestResult<T> error(String msg) {
42        return new RestResult<>(RestCode.UNKNOWN_ERROR.getCode(), msg);
43    }
44
45    public static <T> RestResult<T> error(RestCode restCode) {
46        return new RestResult<>(restCode.getCode(), restCode.getMessage());
47    }
48
49    public static <T> RestResult<T> error(RestCode restCode, String msg) {
50        return new RestResult<>(restCode.getCode(), msg);
51    }
52
53    public static <T> RestResult<T> error(RestCode restCode, String msg, T data) {
54        return new RestResult<>(restCode.getCode(), msg, data);
55    }
56}

4 Demo 下载

无 Redis 缓存版本:https://github.com/kangaroo1122/ssm_shiro

有 Redis 缓存版本:https://github.com/kangaroo1122/shiro-redis

两个版本都可以直接下载,按照给的数据库设计,创建数据库,修改数据库地址即可运行。

用户密码加密方式:可以查看自定义 Realm,这样就能自己修改数据库用户数据了

其他

关于 Redis 的配置文件和操作 Redis 数据库的 util,可以直接查看 demo 源码,Redis 版本还集成了 mybatis-plus 枚举类型转换器,具体可以看 mybatis-plus 的官方介绍。


标题:Spring Boot 2.x+Shiro+Redis
作者:kangaroo1122
地址:https://kangaroohy.com/articles/2019/12/19/1576767517312.html
声明:如非特别说明,版权归kangaroo1122 所有,转载请注明出处,谢谢!
签名:No pains, no gains.