• 【业务功能篇112】Springboot + Spring Security 权限管理-登录模块开发实战


    合家云社区物业管理平台

    4.权限管理模块研发

    4.3 登录模块开发

    前台和后台的认证授权统一都使用SpringSecurity安全框架来实现。首次登录过程如下图:

    image.png

    4.3.1 生成图片校验码

    4.3.1.1 导入工具类
    (1) 导入Constants 常量类
    /**
     * 通用常量类
     * @author spikeCong
     * @date 2023/5/3
     **/
    public class Constants {
    
        /**
         * UTF-8 字符集
         */
        public static final String UTF8 = "UTF-8";
    
        /**
         * GBK 字符集
         */
        public static final String GBK = "GBK";
    
        /**
         * http请求
         */
        public static final String HTTP = "http://";
    
        /**
         * https请求
         */
        public static final String HTTPS = "https://";
    
        /**
         * 通用成功标识
         */
        public static final String SUCCESS = "0";
    
        /**
         * 通用失败标识
         */
        public static final String FAIL = "1";
    
        /**
         * 登录成功
         */
        public static final String LOGIN_SUCCESS = "Success";
    
        /**
         * 注销
         */
        public static final String LOGOUT = "Logout";
    
        /**
         * 登录失败
         */
        public static final String LOGIN_FAIL = "Error";
    
        /**
         * 验证码 redis key
         */
        public static final String CAPTCHA_CODE_KEY = "captcha_codes:";
    
        /**
         * 登录用户 redis key
         */
        public static final String LOGIN_TOKEN_KEY = "login_tokens:";
    
        /**
         * 防重提交 redis key
         */
        public static final String REPEAT_SUBMIT_KEY = "repeat_submit:";
    
        /**
         * 验证码有效期(分钟)
         */
        public static final Integer CAPTCHA_EXPIRATION = 2;
    
        /**
         * 令牌
         */
        public static final String TOKEN = "token";
    
        /**
         * 令牌前缀
         */
        public static final String TOKEN_PREFIX = "Bearer ";
    
        /**
         * 令牌前缀
         */
        public static final String LOGIN_USER_KEY = "login_user_key";
    
        /**
         * 用户ID
         */
        public static final String JWT_USERID = "userid";
    
        /**
         * 用户名称
         */
        public static final String JWT_USERNAME = "sub";
    
        /**
         * 用户头像
         */
        public static final String JWT_AVATAR = "avatar";
    
        /**
         * 创建时间
         */
        public static final String JWT_CREATED = "created";
    
        /**
         * 用户权限
         */
        public static final String JWT_AUTHORITIES = "authorities";
    
        /**
         * 参数管理 cache key
         */
        public static final String SYS_CONFIG_KEY = "sys_config:";
    
        /**
         * 字典管理 cache key
         */
        public static final String SYS_DICT_KEY = "sys_dict:";
    
        /**
         * 资源映射路径 前缀
         */
        public static final String RESOURCE_PREFIX = "/profile";
    
        /**
         * 默认为空消息
         */
        public static final String DEFAULT_NULL_MESSAGE = "暂无承载数据";
        /**
         * 默认成功消息
         */
        public static final String DEFAULT_SUCCESS_MESSAGE = "操作成功";
        /**
         * 默认失败消息
         */
        public static final String DEFAULT_FAILURE_MESSAGE = "操作失败";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    (2) 导入UUIDUtils

    UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。

    /**
     * UUID生成器工具类
     * @author spikeCong
     * @date 2023/5/3
     **/
    public class UUIDUtils {
        /**
         * 获取随机UUID
         *
         * @return 随机UUID
         */
        public static String randomUUID()
        {
            return UUID.randomUUID().toString();
        }
    
        /**
         * 简化的UUID,去掉了横线
         *
         * @return 简化的UUID,去掉了横线
         */
        public static String simpleUUID()
        {
            return UUID.randomUUID().toString().replaceAll("-", "");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    (3) 导入Kv类 链式map

    链式映射是指在 Java 中使用 Map 接口的一种实现方式,它允许在一个键值映射中进行多次操作而无需创建新的 Map 对象。例如,可以在同一个 Map 中链式地添加、删除或更新键值对

    核心就是 重写 Map 接口的 put() 方法,以返回 this 引用,以实现链式调用

    /**
     * 链式Map
     *  继承 LinkedCaseInsensitiveMap, 对key大小写不敏感的LinkedHashMap实现
     * @author spikeCong
     * @date 2023/5/3
     **/
    public class ChainedMap extends LinkedCaseInsensitiveMap<Object> {
        private ChainedMap() {
            super();
        }
    
        /**
         * 创建ChainedMap
         *
         * @return ChainedMap
         */
        public static ChainedMap create() {
            return new ChainedMap();
        }
    
        public static <K, V> HashMap<K, V> newMap() {
            return new HashMap<>(16);
        }
    
        /**
         * 设置列
         *
         * @param attr  属性
         * @param value 值
         * @return 本身
         */
        public ChainedMap set(String attr, Object value) {
            this.put(attr, value);
            return this;
        }
    
        /**
         * 设置全部
         *
         * @param map 属性
         * @return 本身
         */
        public ChainedMap setAll(Map<? extends String, ?> map) {
            if (map != null) {
                this.putAll(map);
            }
            return this;
        }
    
        /**
         * 设置列,当键或值为null时忽略
         *
         * @param attr  属性
         * @param value 值
         * @return 本身
         */
        public ChainedMap setIgnoreNull(String attr, Object value) {
            if (attr != null && value != null) {
                set(attr, value);
            }
            return this;
        }
    
        public Object getObj(String key) {
            return super.get(key);
        }
    
        /**
         * 获得特定类型值
         *
         * @param           值类型
         * @param attr         字段名
         * @param defaultValue 默认值
         * @return 字段值
         */
        @SuppressWarnings("unchecked")
        public <T> T get(String attr, T defaultValue) {
            final Object result = get(attr);
            return (T) (result != null ? result : defaultValue);
        }
    
        /**
         * 获得特定类型值
         *
         * @param attr 字段名
         * @return 字段值
         */
        public String getStr(String attr) {
            if (null == attr || attr.equals(StringPool.NULL)) {
                return StringPool.NULL;
            }
            return attr;
        }
    
        /**
         * 获得特定类型值
         *
         * @param attr 字段名
         * @return 字段值
         */
        public Integer getInt(String attr) {
            if (attr == null) {
                return -1;
            }
            try {
                return Integer.valueOf(attr);
            } catch (final NumberFormatException nfe) {
                return -1;
            }
        }
    
        /**
         * 获得特定类型值
         *
         * @param attr 字段名
         * @return 字段值
         */
        public Long getLong(String attr) {
            if (attr == null) {
                return -1L;
            }
            try {
                return Long.valueOf(attr);
            } catch (final NumberFormatException nfe) {
                return -1L;
            }
        }
    
        /**
         * 获得特定类型值
         *
         * @param attr 字段名
         * @return 字段值
         */
        public Float getFloat(String attr) {
            if (attr != null) {
                return Float.valueOf(attr.trim());
            }
            return null;
        }
    
        public Double getDouble(String attr) {
            if (attr != null) {
                return Double.valueOf(attr.trim());
            }
            return null;
        }
    
    
        /**
         * 获得特定类型值
         *
         * @param attr 字段名
         * @return 字段值
         */
        public Boolean getBool(String attr) {
            if (attr != null) {
                String val = String.valueOf(attr);
                val = val.toLowerCase().trim();
                return Boolean.parseBoolean(val);
            }
            return null;
        }
    
        /**
         * 获得特定类型值
         *
         * @param attr 字段名
         * @return 字段值
         */
        public byte[] getBytes(String attr) {
            return get(attr, null);
        }
    
        /**
         * 获得特定类型值
         *
         * @param attr 字段名
         * @return 字段值
         */
        public Date getDate(String attr) {
            return get(attr, null);
        }
    
        /**
         * 获得特定类型值
         *
         * @param attr 字段名
         * @return 字段值
         */
        public Time getTime(String attr) {
            return get(attr, null);
        }
    
        /**
         * 获得特定类型值
         *
         * @param attr 字段名
         * @return 字段值
         */
        public Timestamp getTimestamp(String attr) {
            return get(attr, null);
        }
    
        /**
         * 获得特定类型值
         *
         * @param attr 字段名
         * @return 字段值
         */
        public Number getNumber(String attr) {
            return get(attr, null);
        }
    
        @Override
        public ChainedMap clone() {
            ChainedMap clone = new ChainedMap();
            clone.putAll(this);
            return clone;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    (4) 导入序列化工具类

    添加序列化工具类,让Redis使用FastJson序列化,提高序列化效率, 将存储在Redis中的value值,序列化为JSON格式便于查看

    public class FastJsonJsonRedisSerializer<T> implements RedisSerializer<T>
    
    • 1
    (5) 导入Redis工具类
    • 当Redis当做数据库或者消息队列来操作时,我们一般使用RedisTemplate来操作
    • 当Redis作为缓存使用时,我们可以将它作为Spring Cache的实现,直接通过注解使用
    @Component
    public class RedisCache{}
    
    • 1
    • 2
    (6) 导入redis配置类
    @Configuration
    public class RedisConfig {}
    
    • 1
    • 2
    4.3.1.2 生成验证码
    (1) 导入依赖
    <dependency>
        <groupId>com.github.whvcsegroupId>
        <artifactId>easy-captchaartifactId>
        <version>1.6.2version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    (2) application.yml 增加redis配置
    # Spring配置
    spring:
      # redis 配置
      redis:
        # 地址
        host: localhost
        # 端口,默认为6379
        port: 6379
        # 密码
        password:
        # 连接超时时间
        timeout: 10s
        jedis:
          pool:
            # 连接池中的最小空闲连接
            min-idle: 3
            # 连接池中的最大空闲连接
            max-idle: 8
            # 连接池的最大数据库连接数
            max-active: 8
            # #连接池最大阻塞等待时间(使用负值表示没有限制)
            max-wait: -1ms
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    (3) 创建CaptchaController
    @RestController
    public class CaptchaController {
    
        //当Redis当做数据库或者消息队列来操作时,我们一般使用RedisTemplate来操作
        @Autowired
        private RedisTemplate redisTemplate;
    
        /**
         * 生成验证码
         * @param response
         * @return: com.mashibing.springsecurity_example.common.ResponseResult
         */
        @GetMapping("/captchaImage")
        public ChainedMap getCode(HttpServletResponse response){
            SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4);
    
            //生成验证码,及验证码唯一标识
            String uuid = UUIDUtils.simpleUUID();
            String key = Constants.CAPTCHA_CODE_KEY + uuid;
            String code = specCaptcha.text().toLowerCase();
    
            //保存到redis
            redisTemplate.opsForValue().set(key, code, Duration.ofMinutes(30));
    
            return ChainedMap.create().set("uuid",uuid).set("img",specCaptcha.toBase64());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    (4) 查看接口文档进行测试

    4.3.2 登录接口实现

    4.3.2.1 数据库查询用户信息
    (1) 创建SysUser类
    • 创建sys_user表对应实体类, com.msb.hjycommunity.system.domain.SysUser
    public class SysUser extends BaseEntity {
    
        /** 用户ID */
        @Excel(name = "用户序号")
        @TableId
        private Long userId;
    
        /** 部门ID */
        @Excel(name = "部门编号")
        private Long deptId;
    
        /** 用户账号 */
        @Excel(name = "登录名称")
        private String userName;
    
        /** 用户昵称 */
        @Excel(name = "用户名称")
        private String nickName;
    
        /** 用户邮箱 */
        @Excel(name = "用户邮箱")
        private String email;
    
        /** 手机号码 */
        @Excel(name = "手机号码")
        private String phonenumber;
    
        /** 用户性别 */
        @Excel(name="用户性别",replace = {"男_0","女_1","未知_0"})
        private String sex;
    
        /** 用户头像 */
        private String avatar;
    
        /** 密码 */
        private String password;
    
        /** 盐加密 */
        private String salt;
    
        /** 帐号状态(0正常 1停用) */
        @Excel(name = "帐号状态",replace = {"正常_0","停用_1"})
        private String status;
    
        /** 删除标志(0代表存在 2代表删除) */
        private String delFlag;
    
        /** 最后登录IP */
        @Excel(name = "最后登录IP")
        private String loginIp;
    
        /** 最后登录时间 */
        @Excel(name = "最后登录时间", width = 30, format = "yyyy-MM-dd HH:mm:ss")
        private Date loginDate;
    
        public SysUser() {
        }
    
        //对 用户名 邮箱 手机号进行校验
        @NotBlank(message = "用户账号不能为空")
        @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符")    public String getUserName() {
            return userName;
        }
    
        @Email(message = "邮箱格式不正确")
        @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符")
        public String getEmail() {
            return email;
        }
    
        @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符")
        public String getPhonenumber() {
            return phonenumber;
        }
    
        //序列化时忽略密码
        @JsonIgnore
        public String getPassword() {
            return password;
        }
    
        //......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    (2) 创建 SysUserMapper
    public interface SysUserMapper extends BaseMapper<SysUser> {
    
        /**
         * 通过用户名查询用户
         * @param userName 用户名
         * @return 用户对象信息
         */
        public SysUser selectUserByUserName(String userName);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    <mapper namespace="com.msb.hjycommunity.system.mapper.SysUserMapper">
    
        <select id="selectUserByUserName" parameterType="string" resultType="SysUser">
    
            SELECT * FROM sys_user where user_name = #{userName}
        select>
    
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 测试
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class TestHjyCommunityApplication {
    
        @Autowired
        SysUserMapper userMapper;
    
        @Test
        public void testSelectUserByUserName(){
            SysUser admin = userMapper.selectUserByUserName("admin");
            System.out.println(admin);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    (3) 创建 SysUserService
    public interface SysUserService {
      
        /**
         * 通过用户名查询用户
         * @param userName 
         * @return: com.msb.hjycommunity.system.domain.SysUser
         */
        public SysUser selectUserByUserName(String userName);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    @Service
    @Slf4j
    public class SysUserServiceImpl implements SysUserService {
    
        @Resource
        private SysUserMapper sysUserMapper;
    
        @Override
        public SysUser selectUserByUserName(String userName) {
            return sysUserMapper.selectUserByUserName(userName);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    4.3.2.2 引入SpringSecurity
    • 认证流程图

    image.png

    (1) 实现UserDetailsService接口
    /**
     * 用户验证处理
     * @author spikeCong
     * @date 2023/5/3
     **/
    @Service
    @Slf4j
    public class UserDetailsServiceImpl implements UserDetailsService {
    
        @Autowired
        private SysUserService userService;
    
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
            SysUser user = userService.selectUserByUserName(username);
    
            if(Objects.isNull(user)){
                log.info("登录用户:{} 不存在",username);
                throw new UsernameNotFoundException("登录用户: " + username + " 不存在");
            }
            else if(UserStatus.DELETED.getCode().equals(user.getDelFlag())){
                log.info("登录用户:{} 已被删除",username);
                throw new BaseException("对不起,您的账号: " + username + " 以被删除" );
            }
            else if(UserStatus.DISABLE.getCode().equals(user.getStatus())){
                log.info("登录用户:{} 已被停用",username);
                throw new BaseException("对不起,您的账号: " + username + " 以被停用" );
            }
      
            return createLoginUser(user);
        }
    
        public UserDetails createLoginUser(SysUser user) {
      
            return new LoginUser(user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 用户状态枚举
    /**
     * 用户状态
     * @author spikeCong
     * @date 2023/5/3
     **/
    public enum UserStatus {
    
        OK("0","正常"),DISABLE("1","停用"),DELETED("2","删除");
      
        private final String code;
        private final String info;
    
        UserStatus(String code, String info) {
            this.code = code;
            this.info = info;
        }
    
        public String getCode() {
            return code;
        }
        public String getInfo() {
            return info;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • LoginUser
    /**
     * 登录用户 身份权限对象
     * @author spikeCong
     * @date 2023/5/3
     **/
    public class LoginUser implements UserDetails {
    
        private SysUser user;
    
        public LoginUser(SysUser user) {
            this.user = user;
        }
        /**
         *  用于获取用户被授予的权限,可以用于实现访问控制。
         */
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return null;
        }
    
        /**
         * 用于获取用户的密码,一般用于进行密码验证。
         */
        @Override
        public String getPassword() {
            return user.getPassword();
        }
    
        /**
         * 用于获取用户的用户名,一般用于进行身份验证。
         */
        @JsonIgnore
        @Override
        public String getUsername() {
            return user.getPassword();
        }
    
        /**
         * 用于判断用户的账户是否未过期,可以用于实现账户有效期控制。
         */
        @JsonIgnore
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        /**
         * 用于判断用户的账户是否未锁定,可以用于实现账户锁定功能。
         */
        @JsonIgnore
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        /**
         * 用于判断用户的凭证(如密码)是否未过期,可以用于实现密码有效期控制。
         */
        @JsonIgnore
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        /**
         * 用于判断用户是否已激活,可以用于实现账户激活功能。
         */
        @JsonIgnore
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    (2) 编写配置类 SecurityConfig
    • 包路径 com.msb.hjycommunity.framework.security.SecurityConfig
    /**
     * Security配置
     * @author spikeCong
     * @date 2023/5/3
     **/
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        /**
         * 认证失败处理器
         */
        @Autowired
        private AuthenticationEntryPoint unauthorizedHandler;
    
    
        /**
         * anyRequest          |   匹配所有请求路径
         * access              |   SpringEl表达式结果为true时可以访问
         * anonymous           |   匿名可以访问
         * denyAll             |   用户不能访问
         * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
         * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
         * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
         * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
         * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
         * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
         * permitAll           |   用户可以任意访问
         * rememberMe          |   允许通过remember-me登录的用户访问
         * authenticated       |   用户登录后可访问
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http
                    // CSRF禁用,因为不使用session
                    .csrf().disable().sessionManagement()
                    //基于token,所以不需要session
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    
            http
                    //过滤请求
                    .authorizeRequests()
                    // 对于登录login 验证码captchaImage 允许匿名访问
                    .mvcMatchers("/login","/captchaImage").anonymous()
                    // 除上面外的所有请求全部需要鉴权认证
                    .anyRequest().authenticated();
            http
                    //认证失败处理器
                    .exceptionHandling().authenticationEntryPoint(unauthorizedHandler);
    
            //添加JWTFilter
    
            //添加 CORS filter
        }
    
        /*
         * 配置密码加密方式
         */
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    securedEnabled: 开启 Spring Security 提供的 @Secured 注解支持,该注解不支持权限表达式

    (3) 添加自定义认证失败处理器
    @Component
    public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
    
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            //状态码 401
            Integer code = HttpStatus.UNAUTHORIZED;
            ServletUtils.renderString(response, JSON.toJSONString(BaseResponse.fail(code.toString(),"认证失败,无法访问系统资源")));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    4.3.2.3 自定义登录接口
    (1) 创建用户登录对象
    //com.msb.hjycommunity.system.domain.vo.LoginBody
      
    /**
     * 用户登录对象
     * @author spikeCong
     * @date 2023/5/4
     **/
    public class LoginBody {
    
        /**
         * 用户名
         */
        private String username;
    
        /**
         * 用户密码
         */
        private String password;
    
        /**
         * 验证码
         */
        private String code;
    
        /**
         * 唯一标识
         */
        private String uuid = "";
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public String getUuid() {
            return uuid;
        }
    
        public void setUuid(String uuid) {
            this.uuid = uuid;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    (2) 创建LoginService
     public interface SysLoginService {
     
         public String login(String username, String password, String code, String uuid);
     }
    
    • 1
    • 2
    • 3
    • 4
     /**
      * 登录校验
      * @author spikeCong
      * @date 2023/5/4
      **/
     @Component
     public class SysLoginServiceImpl implements SysLoginService {
     
         @Autowired
         private AuthenticationManager authenticationManager;
     
         @Autowired
         private RedisCache redisCache;
     
         /**
          * 带验证码登录
          * @param username
          * @param password
          * @param code
          * @param uuid
          * @return: java.lang.String
          */
         @Override
         public String login(String username, String password, String code, String uuid) {
     
             //1.从redis中获取验证码,判断是否正确
             String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
             String captcha = redisCache.getCacheObject(verifyKey);
             redisCache.deleteObject(verifyKey);
     
             if (captcha == null || !code.equalsIgnoreCase(captcha)){
                 throw new CaptchaNotMatchException("验证码错误!");
             }
     
             //2.进行用户认证
             Authentication authentication = null;
             try {
                 //该方法会去调用UserDetailsServiceImpl.loadUserByUsername
                 authentication = authenticationManager
                         .authenticate(new UsernamePasswordAuthenticationToken(username, password));
             }catch (Exception e){
                 throw new BaseException("用户不存在或密码错误!");
             }
             
             //3. 获取经过身份验证的用户的主体信息
             LoginUser loginUser = (LoginUser) authentication.getPrincipal();
             
             //4.调用TokenService 生成token
             return tokenService.createToken(loginUser);
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    **验证码验证错误异常 **

     //com.msb.hjycommunity.common.core.exception.CaptchaNotMatchException
     /**
      * 验证码异常
      * @author spikeCong
      * @date 2023/5/4
      **/
     public class CaptchaNotMatchException extends BaseException {
     
         public CaptchaNotMatchException(String defaultMessage) {
             super(defaultMessage);
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    (3) 创建TokenService

    主配置文件中添加token相关配置

     # token配置
     token:
       # 令牌自定义标识
       header: Authorization
       # 令牌密钥
       secret: msbhjy
       # 令牌有效期(默认30分钟)
       expireTime: 30
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    创建TokenService

     /**
      * token验证处理
      * @author spikeCong
      * @date 2023/5/4
      **/
     public interface TokenService {
     
         /**
          * 创建令牌
          * @param loginUser
          * @return: java.lang.String
          */
         public String createToken(LoginUser loginUser);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
     /**
      * Token处理器
      * @author spikeCong
      * @date 2023/5/4
      **/
     public class TokenServiceImpl implements TokenService {
     
         // 令牌自定义标识
         @Value("${token.header}")
         private String header;
     
         // 令牌秘钥
         @Value("${token.secret}")
         private String secret;
     
         // 令牌有效期(默认30分钟)
         @Value("${token.expireTime}")
         private int expireTime;
     
         /**
          * 创建令牌
          * @param loginUser
          * @return: java.lang.String
          */
         @Override
         public String createToken(LoginUser loginUser) {
     
             //设置唯一用户标识
             String userKey = UUIDUtils.randomUUID();
             loginUser.setToken(userKey);
     
             Map claims = new HashMap<>();
             claims.put(Constants.LOGIN_USER_KEY, userKey);
     
             //创建token, 将用户唯一标识 通过setClaims方法 保存到token中
             String token = Jwts.builder()
                     .setClaims(claims)
                     .signWith(SignatureAlgorithm.HS512, secret).compact();
     
             return token;
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    (4) 创建SysLoginController
     /**
      * 登录验证
      * @author spikeCong
      * @date 2023/5/4
      **/
     @RestController
     public class SysLoginController {
     
         @Autowired
         private SysLoginService loginService;
     
         /**
          * 登录方法
          * @param loginBody
          * @return: com.msb.hjycommunity.common.utils.ChainedMap
          */
         @PostMapping("/login")
         public ChainedMap login(@RequestBody LoginBody loginBody){
     
             //生成令牌
             String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(),
                     loginBody.getCode(), loginBody.getUuid());
             return ChainedMap.create().set("token",token);
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    (5) 设计业务异常体系
    • 创建业务异常基类
     /**
      * 业务异常
      * @author spikeCong
      * @date 2023/5/5
      **/
     public class CustomException extends RuntimeException {
     
         /**
          * 状态码
          */
         private int code;
     
         /**
          * 是否成功
          */
         private boolean success;
     
         /**
          * 承载数据
          */
         private T data;
     
         /**
          * 返回消息
          */
         private String msg;
     
         public CustomException() {
         }
     
         public CustomException(String msg,int code) {
             this.code = code;
             this.msg = msg;
             this.success = HttpServletResponse.SC_OK == code;
         }
     
         public CustomException(int code, boolean success, T data, String msg) {
             this.code = code;
             this.success = success;
             this.data = data;
             this.msg = msg;
         }
     
         public int getCode() {
             return code;
         }
     
         public void setCode(int code) {
             this.code = code;
         }
     
         public boolean isSuccess() {
             return success;
         }
     
         public void setSuccess(boolean success) {
             this.success = success;
         }
     
         public T getData() {
             return data;
         }
     
         public void setData(T data) {
             this.data = data;
         }
     
         public String getMsg() {
             return msg;
         }
     
         public void setMsg(String msg) {
             this.msg = msg;
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 验证码异常继承 CustomException
     /**
      * 验证码异常
      * @author spikeCong
      * @date 2023/5/4
      **/
     public class CaptchaNotMatchException extends CustomException {
     
         public CaptchaNotMatchException() {
             super("验证码错误",400);
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • BaseResponse 响应结果类中,添加一个接收三个参数的构造方法
     /**
      * 响应结果封装对象
      * @author spikeCong
      * @date 2023/2/28
      **/
     public class BaseResponse implements Serializable {
     
         private static final long serialVersionUID = 1L;
     
         /**
          * 响应状态码
          */
         private String code;
     
         /**
          * 响应结果描述
          */
         private String msg; 
     
         /**
          * 返回的数据
          */
         private T data;
     
         /**
          * 是否成功
          */
         private boolean success;
     
     
         /**
          * 失败返回 三个参数·
          * @param code
          * @param message
          * @return: com.msb.hjycommunity.common.core.domain.BaseResponse
          */
         public static  BaseResponse fail(String code,String message,boolean success){
             BaseResponse response = new BaseResponse<>();
             response.setCode(code);
             response.setMsg(message);
             response.setSuccess(success);
             return response;
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 在全局异常类 GlobalExceptionHandler中添加业务异常
     /**
      * 全局异常处理器
      * @author spikeCong
      * @date 2023/2/28
      **/
     @RestControllerAdvice(annotations = RestController.class)
     public class GlobalExceptionHandler {
     
         /**
          * 业务异常
          */
         @ExceptionHandler(CustomException.class)
         public BaseResponse businessException(CustomException e) {
             if(Objects.isNull(e.getCode())){
                 return BaseResponse.fail(e.getMsg());
             }
             return BaseResponse.fail(e.getCode()+"", e.getMsg(),e.isSuccess());
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    (6) 根据接口文档进行测试

    获取验证码: http://localhost:9999/hejiayun/captchaImage

    image.png

    查看redis中的验证码

    image.png

    访问登录接口,携带 用户名,密码,验证码,UUID: http://localhost:9999/hejiayun/login

    image.png

    转换一下token,查看载荷信息

    image.png

    4.3.2.3 TokenService功能设计
    (1) LoginUser添加新的属性
     // com.msb.hjycommunity.system.domain.LoginUser
     /**
      * 登录用户 身份权限对象
      * @author spikeCong
      * @date 2023/5/3
      **/
     public class LoginUser implements UserDetails {
     
         /**
          * 用户唯一标识
          */
         private String token;
     
         /**
          * 用户信息
          */
         private SysUser user;
     
         /**
          * 登录时间
          */
         private Long loginTime;
     
         /**
          * 过期时间
          */
         private Long expireTime;
     
         /**
          * 权限列表
          */
         private Set permissions;
     
         //一定要有空参构造,否则序列化会失败
         public LoginUser() {
         }
     
         public LoginUser(SysUser user, Set permissions) {
             this.user = user;
             this.permissions = permissions;
         }
         
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    (2) TokenService刷新令牌有效期&缓存用户信息

    在createToken方法中创建令牌时,要刷新Token

     public void refreshToken(LoginUser loginUser);
    
    • 1
     //com.msb.hjycommunity.system.service.impl.TokenServiceImpl
     
     @Component
     public class TokenServiceImpl implements TokenService {
     
         @Autowired
         private RedisCache redisCache;
     
         // 令牌自定义标识
         @Value("${token.header}")
         private String header;
     
         // 令牌秘钥
         @Value("${token.secret}")
         private String secret;
     
         // 令牌有效期(默认30分钟)
         @Value("${token.expireTime}")
         private int expireTime;
     
         //毫秒
         private static final long MILLIS_SECOND = 1000;
     
         //分钟
         private static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
     
         //20分钟
         private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
     
         /**
          * 创建令牌
          * @param loginUser
          * @return: java.lang.String
          */
         @Override
         public String createToken(LoginUser loginUser) {
     
             //设置唯一用户标识
             String userKey = UUIDUtils.randomUUID();
             loginUser.setToken(userKey);
     
             //todo 刷新令牌保存用户信息
             refreshToken(loginUser);
     
             Map claims = new HashMap<>();
             claims.put(Constants.LOGIN_USER_KEY, userKey);
     
             //创建token, 将用户唯一标识 通过setClaims方法 保存到token中
             String token = Jwts.builder()
                     .setClaims(claims)
                     .signWith(SignatureAlgorithm.HS512, secret).compact();
     
             return token;
         }
     
         /**
          * 缓存用户信息&刷新令牌有效期
          * @param loginUser
          */
         @Override
         public void refreshToken(LoginUser loginUser) {
             loginUser.setLoginTime(System.currentTimeMillis());
             //过期时间30分钟
             loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE );
             // 根据uuid将loginUser缓存
             String userKey = getTokenKey(loginUser.getToken());
             redisCache.setCacheObject(userKey,loginUser,expireTime, TimeUnit.MINUTES);
         }
     
         //拼接tokenkey
         private String getTokenKey(String uuid) {
             return Constants.LOGIN_TOKEN_KEY + uuid;
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    (3) 获取请求中的token

    w3c规定,请求头 Authorization用于验证用户身份。token应该写在请求头 Authorization中.

    jwt token的标准写法 Authorization: Bearer aaa.bbb.ccc。 (bearer: 持票人)

     /**
          * 从request的请求头中 获取token
          * @param request 
          * @return: java.lang.String
          */
     private String getToken(HttpServletRequest request){
     
         String token = request.getHeader(this.header);
         if(!StringUtils.isEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)){
             token = token.replace(Constants.TOKEN_PREFIX,"");
         }
     
         return token;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    (4) 获取用户身份信息
      LoginUser getLoginUser(HttpServletRequest request);
    
    • 1
         /**
          * 从Redis获取用户身份信息
          * @param request
          * @return: com.msb.hjycommunity.system.domain.LoginUser
          */
         @Override
         public LoginUser getLoginUser(HttpServletRequest request) {
     
             //获取请求携带的token
             String token = getToken(request);
             if(!StringUtils.isEmpty(token)){
                 Claims claims = parseToken(token);
                 //解析对应的用户信息和权限信息
                 String uuid =(String) claims.get(Constants.LOGIN_USER_KEY);
                 String userKey = getTokenKey(uuid);
                 LoginUser loginUser = redisCache.getCacheObject(userKey);
                 return loginUser;
             }
     
             return null;
         }
     
         /**
          * 从令牌中获取数据声明
          *
          * @param token 令牌
          * @return 数据声明
          */
         private Claims parseToken(String token)
         {
             return Jwts.parser()
                     .setSigningKey(secret)
                     .parseClaimsJws(token)
                     .getBody();
         }
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    (5) 验证令牌有效期
     public void verifyToken(LoginUser loginUser);
    
    • 1
     /**
          * 验证令牌有效期,相差不足20分钟,自动刷新缓存
          * @param loginUser
          */
     @Override
     public void verifyToken(LoginUser loginUser){
         Long expireTime = loginUser.getExpireTime();
         long currentTimeMillis = System.currentTimeMillis();
         if(expireTime - currentTimeMillis <= MILLIS_MINUTE_TEN){
             refreshToken(loginUser);
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    (6) 设置用户与删除用户
         public void setLoginUser(LoginUser loginUser);
     
         public void delLoginUser(String token);
    
    • 1
    • 2
    • 3
         /**
          * 设置用户身份信息
          */
         @Override
         public void setLoginUser(LoginUser loginUser){
             if(!Objects.isNull(loginUser) && !StringUtils.isEmpty(loginUser.getToken())){
                 refreshToken(loginUser);
             }
         }
     
         /**
          * 删除用户身份信息
          */
         @Override
         public void delLoginUser(String token){
             if(!StringUtils.isEmpty(token)){
                 String userKey = getTokenKey(token);
                 redisCache.deleteObject(userKey);
             }
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    4.3.2.4 实现认证过滤器
    (1) 创建JwtAuthenticationTokenFilter

    当用户再次发送请求的时候,要进行校验,用户会携带登录时生成的JWT,所以我们需要自定义一个Jwt认证过滤器

    image.png

    • 获取token
    • 解析token获取其中的用户唯一标识
    • 从redis中获取用户信息
    • 存入SecurityContextHolder

    自定义一个过滤器,这个过滤器会去获取请求头中的token,对token进行解析取出其中的userid

     /**
      * token过滤器 验证token有效性
      * @author spikeCong
      * @date 2023/5/6
      **/
     @Component
     public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
     
         @Autowired
         private TokenService tokenService;
     
         @Override
         protected void doFilterInternal(HttpServletRequest request,
                                         HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
     
             //从Redis获取用户信息
             LoginUser loginUser = tokenService.getLoginUser(request);
             Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
     
             //判断: loginUser不为空,authentication为空,用户持有token 需要验证
             if(!Objects.isNull(loginUser) && Objects.isNull(authentication)){
                 tokenService.verifyToken(loginUser);
            
                 UsernamePasswordAuthenticationToken authenticationToken =
                         new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
                 //设置与当前身份验证相关的详细信息(远程IP地址、会话ID等)
                 authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                 SecurityContextHolder.getContext().setAuthentication(authenticationToken);
     
             }
     
             filterChain.doFilter(request,response);
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    (2) 解决跨域问题
     /**
      * 通用配置
      * @author spikeCong
      * @date 2023/5/7
      **/
     @Configuration
     public class ResourcesConfig {
     
         /**
          * 跨域配置
          */
         @Bean
         public CorsFilter corsFilter()
         {
             UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
     
             CorsConfiguration config = new CorsConfiguration();
             config.setAllowCredentials(true);
             // 设置访问源地址
             config.addAllowedOrigin("*");
             // 设置访问源请求头
             config.addAllowedHeader("*");
             // 设置访问源请求方法
             config.addAllowedMethod("*");
             // 对接口配置跨域设置
             source.registerCorsConfiguration("/**", config);
             return new CorsFilter(source);
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    (3) 配置SecurityConfig
     /**
      * Security配置
      * @author spikeCong
      * @date 2023/5/3
      **/
     @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
     public class SecurityConfig extends WebSecurityConfigurerAdapter {
     
         /**
          * 认证失败处理器
          */
         @Autowired
         private AuthenticationEntryPoint unauthorizedHandler;
     
         @Autowired
         private JwtAuthenticationTokenFilter authenticationTokenFilter;
     
         @Autowired
         private CorsFilter corsFilter;
     
         /**
          * 解决 无法直接注入 AuthenticationManager
          */
         @Bean
         @Override
         public AuthenticationManager authenticationManagerBean() throws Exception
         {
             return super.authenticationManagerBean();
         }
     
     
         @Override
         protected void configure(HttpSecurity http) throws Exception {
     
             http
                     // CSRF禁用,因为不使用session
                     .csrf().disable().sessionManagement()
                     //基于token,所以不需要session
                     .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
     
             http
                     //过滤请求
                     .authorizeRequests()
                     // 对于登录login 验证码captchaImage 允许匿名访问
                     .mvcMatchers("/login","/captchaImage").anonymous()
                     // 除上面外的所有请求全部需要鉴权认证
                     .anyRequest().authenticated();
             http
                     //认证失败处理器
                     .exceptionHandling().authenticationEntryPoint(unauthorizedHandler);
     
             //添加JWTFilter
             http.addFilterBefore(authenticationTokenFilter,
                     UsernamePasswordAuthenticationFilter.class);
     
             //添加CORS filter
             http.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
             //确保在用户注销登录时,响应头中包含必要的跨域资源共享(CORS)字段
             http.addFilterBefore(corsFilter, LogoutFilter.class);
         }
     
         /*
          * 配置密码加密方式
          */
         @Bean
         public PasswordEncoder passwordEncoder(){
             return new BCryptPasswordEncoder();
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    (4) 测试
    1. 获取验证码

    image.png

    image.png

    1. 发送登录请求,携带验证码

    image.png

    1. 未携带token,查询小区数据

    image.png

    1. 请求头中携带token访问

    image.png

    4.3.3 获取用户权限信息接口

    image.png

    4.3.3.1 创建角色与菜单实体类
    (1) 创建角色实体类
     /**
      * 角色表 sys_role
      * @author spikeCong
      * @date 2023/5/9
      **/
     public class SysRole extends BaseEntity {
     
         private static final long serialVersionUID = 1L;
     
         /** 角色ID */
         @Excel(name = "角色序号")
         @TableId
         private Long roleId;
     
         /** 角色名称 */
         @Excel(name = "角色名称")
         private String roleName;
     
         /** 角色权限 */
         @Excel(name = "角色权限")
         private String roleKey;
     
         /** 角色排序 */
         @Excel(name = "角色排序")
         private String roleSort;
     
         /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限) */
         @Excel(name = "数据范围", replace = {"所有数据权限_1","自定义数据权限_2,","本部门数据权限_3","本部门及以下数据权限_4"})
         private String dataScope;
     
         /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */
         private boolean menuCheckStrictly;
     
         /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */
         private boolean deptCheckStrictly;
     
         /** 角色状态(0正常 1停用) */
         @Excel(name = "角色状态",replace = {"正常_0","停用_1"})
         private String status;
     
         /** 删除标志(0代表存在 2代表删除) */
         private String delFlag;
     
         /** 用户是否存在此角色标识 默认不存在 */
         private boolean flag = false;
     
         /** 菜单组 */
         private Long[] menuIds;
     
         /** 部门组(数据权限) */
         private Long[] deptIds;
     
         //判断是否是admin
         public boolean isAdmin()
         {
             return isAdmin(this.roleId);
         }
     
         public static boolean isAdmin(Long roleId)
         {
             return roleId != null && 1L == roleId;
         }
         
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    (2) 创建菜单实体类
     /**
      * 菜单权限表 sys_menu
      * @author spikeCong
      * @date 2023/5/9
      **/
     public class SysMenu extends BaseEntity {
     
         private static final long serialVersionUID = 1L;
     
         /** 菜单ID */
         @TableId
         private Long menuId;
     
         /** 菜单名称 */
         private String menuName;
     
         /** 父菜单名称 */
         private String parentName;
     
         /** 父菜单ID */
         private Long parentId;
     
         /** 显示顺序 */
         private String orderNum;
     
         /** 路由地址 */
         private String path;
     
         /** 组件路径 */
         private String component;
     
         /** 是否为外链(0是 1否) */
         private String isFrame;
     
         /** 是否缓存(0缓存 1不缓存) */
         private String isCache;
     
         /** 类型(M目录 C菜单 F按钮) */
         private String menuType;
     
         /** 显示状态(0显示 1隐藏) */
         private String visible;
     
         /** 菜单状态(0显示 1隐藏) */
         private String status;
     
         /** 权限字符串 */
         private String perms;
     
         /** 菜单图标 */
         private String icon;
     
         /** 子菜单 */
         private List children = new ArrayList();
         
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    4.3.3.2 根据用户ID获取角色权限信息
    (1) 创建SysRoleMapper
     /**
      * 角色表 数据层
      * @author spikeCong
      * @date 2023/5/9
      **/
     public interface SysRoleMapper extends BaseMapper {
     
     
         /**
          * 根据用户ID 查询角色
          * @param userId
          * @return: 角色列表
          */
         public List selectRolePermissionByUserId(Long userId);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
     
     
     
     
     
         
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    (2) 创建SysRoleService
     /**
      * 角色业务层
      * @author spikeCong
      * @date 2023/5/9
      **/
     public interface SysRoleService {
     
         /**
          * 根据用户ID查询角色信息
          * @param userId
          * @return: 角色权限列表
          */
         public Set selectRolePermissionByUserId(Long userId);
     
     }
     
     /**
      * 角色业务处理层
      * @author spikeCong
      * @date 2023/5/9
      **/
     @Service
     public class SysRoleServiceImpl implements SysRoleService {
     
         @Autowired
         private SysRoleMapper sysRoleMapper;
     
         /**
          * 根据用户ID查询角色信息
          * @param userId
          * @return: 角色权限列表
          */
         @Override
         public Set selectRolePermissionByUserId(Long userId) {
     
             //根据用户Id获取角色信息
             List roleList = sysRoleMapper.selectRolePermissionByUserId(userId);
     
             //将角色信息List集合转换为Set集合
             Set permsSet = new HashSet<>();
             for (String roleKey : roleList) {
                 if(!StringUtils.isEmpty(roleKey)){
                     permsSet.add(roleKey);
                 }
             }
             return permsSet;
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    4.3.3.3 根据用户ID获取菜单权限信息
    (1) 创建SysMenuMapper
     /**
      * 菜单表 数据层
      * @author spikeCong
      * @date 2023/5/9
      **/
     public interface SysMenuMapper extends BaseMapper {
     
         /**
          * 根据用户ID查询权限
          *
          * @param userId 用户ID
          * @return 权限列表
          */
         public List selectMenuPermsByUserId(Long userId);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
     
     
     
     
     
         
     
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    (2) 创建SysMenuService
     /**
      * 菜单业务层
      * @author spikeCong
      * @date 2023/5/9
      **/
     public interface SysMenuService {
     
         /**
          * 根据用户Id查询用户权限
          * @param userId
          * @return: java.util.Set
          */
         public Set selectMenuPermsByUserId(Long userId);
     }
     
     /**
      * @author spikeCong
      * @date 2023/5/9
      **/
     @Service
     public class SysMenuServiceImpl implements SysMenuService {
     
         @Autowired
         private SysMenuMapper menuMapper;
     
         @Override
         public Set selectMenuPermsByUserId(Long userId) {
             List menuList = menuMapper.selectMenuPermsByUserId(userId);
             Set permsSet = new HashSet<>();
             for (String menu : menuList) {
                 if(!StringUtils.isEmpty(menu)){
                     permsSet.add(menu);
                 }
             }
             return permsSet;
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    4.3.3.4 根据用户名获取完整用户信息
    (1) 修改SysUser

    通过查看接口文档可以发现,返回的用户信息中,要求包含 :

    • dept 部门对象
    • roles 角色对象集合
    • roleIds 角色组
    • postIds 岗位组
     /**
      * 用户表 sys_user
      * @author spikeCong
      * @date 2023/5/3
      **/
     public class SysUser extends BaseEntity
     {
         private static final long serialVersionUID = 1L;
     
         /** 用户ID */
         @Excel(name = "用户序号")
         @TableId
         private Long userId;
     
         /** 部门ID */
         @Excel(name = "部门编号")
         private Long deptId;
     
         /** 用户账号 */
         @Excel(name = "登录名称")
         private String userName;
     
         /** 用户昵称 */
         @Excel(name = "用户名称")
         private String nickName;
     
         /** 用户邮箱 */
         @Excel(name = "用户邮箱")
         private String email;
     
         /** 手机号码 */
         @Excel(name = "手机号码")
         private String phonenumber;
     
         /** 用户性别 */
         @Excel(name="用户性别",replace = {"男_0","女_1","未知_0"})
         private String sex;
     
         /** 用户头像 */
         private String avatar;
     
         /** 密码 */
         private String password;
     
         /** 盐加密 */
         private String salt;
     
         /** 帐号状态(0正常 1停用) */
         @Excel(name = "帐号状态",replace = {"正常_0","停用_1"})
         private String status;
     
         /** 删除标志(0代表存在 2代表删除) */
         private String delFlag;
     
         /** 最后登录IP */
         @Excel(name = "最后登录IP")
         private String loginIp;
     
         /** 最后登录时间 */
         @Excel(name = "最后登录时间", width = 30, format = "yyyy-MM-dd HH:mm:ss")
         private Date loginDate;
     
         /** 部门对象 */
         private SysDept dept;
     
         /** 角色对象 */
         private List roles;
     
         /** 角色组 */
         private Long[] roleIds;
     
         /** 岗位组 */
         private Long[] postIds;
     
         //判断当前用户是否是admin 
         public boolean isAdmin()
         {
             return isAdmin(this.userId);
         }
     
         public static boolean isAdmin(Long userId)
         {
             return userId != null && 1L == userId;
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    (2) 修改SysUserMapper中的selectUserByUserName方法
    • **根据用户名查询用户信息的方法是在 **UserDetailsServiceImpl 中,调用的SysUserMapper中的 selectUserByUserName方法, 所以需要获取更加详细的用户信息的话,修改XML即可
     
     
     
     
         
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
             
         
     
         
             
             
             
             
             
             
         
     
         
             
             
             
             
             
             
         
     
         
             SELECT
                 u.user_id, u.dept_id, u.user_name,
                 u.nick_name, u.email, u.avatar, u.phonenumber,
                 u.password, u.sex, u.status, u.del_flag, u.login_ip,
                 u.login_date, u.create_by, u.create_time, u.remark,
                 d.dept_id, d.parent_id, d.dept_name, d.order_num,
                 d.leader, d.status AS dept_status,
                 r.role_id, r.role_name, r.role_key, r.role_sort,
                 r.data_scope, r.status AS role_status
             FROM sys_user u
                 LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
                 LEFT JOIN sys_user_role ur ON u.user_id = ur.user_id
                 LEFT JOIN sys_role r ON r.role_id = ur.role_id
         
     
         
     
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    (3) 测试查询完整用户数据
     @Test
     public void testSelectUserByUserName(){
         SysUser admin = userMapper.selectUserByUserName("admin");
         System.out.println(admin);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    4.3.3.5 用户信息接口编写
    (1) 创建用户权限处理Service
    • 创建一个专门用于获取用户权限的service,让controller直接调用
     //com.msb.hjycommunity.framework.service.SysPermissionService
     /**
      * 用户权限处理
      * @author spikeCong
      * @date 2023/5/9
      **/
     @Component
     public class SysPermissionService {
     
         @Autowired
         private SysRoleService roleService;
     
         @Autowired
         private SysMenuService menuService;
     
         /**
          * 获取角色数据权限
          * @param user
          * @return: 角色权限信息
          */
         public Set getRolePermission(SysUser user){
     
             Set roles = new HashSet<>();
             //管理员拥有所有权限
             if(user.isAdmin()){
                 roles.add("admin");
             }else{
                 roles = roleService.selectRolePermissionByUserId(user.getUserId());
             }
     
             return roles;
         }
     
         /**
          * 获取菜单数据权限
          * @param user
          * @return: java.util.Set
          */
         public Set getMenuPermission(SysUser user){
             Set perms = new HashSet<>();
             //管理员拥有所有权限
             if(user.isAdmin()){
                 perms.add("*:*:*");
             }else{
                 perms = menuService.selectMenuPermsByUserId(user.getUserId());
             }
             return perms;
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    (2) SysLoginController编写获取用户信息方法
     
     /**
      * 登录验证
      * @author spikeCong
      * @date 2023/5/4
      **/
     @RestController
     public class SysLoginController {
     
         @Autowired
         private SysLoginService loginService;
     
         @Autowired
         private SysPermissionService permissionService;
     
         @Autowired
         private TokenService tokenService;
     
         /**
          * 获取 用户信息
          * @param
          * @return: 用户信息
          */
         @GetMapping("/getInfo")
         public ChainedMap getInfo(){
     
             //用户信息
             LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
             SysUser user = loginUser.getUser();
             //角色集合
             Set roles = permissionService.getRolePermission(user);
             //权限集合
             Set permissions = permissionService.getMenuPermission(user);
             
             ChainedMap map = ChainedMap.create().set("code", 200).set("msg", "操作成功");
             map.put("user",user);
             map.put("roles",roles);
             map.put("permissions",permissions);
             return map;
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    (3) 根据接口文档进行测试

    4.3.4 获取路由导航菜单信息

    在首页加载时,前端会向后端发送请求,获取左侧导航菜单及其子菜单数据.

    image.png

    4.3.4.1 SysMenuMapper
    (1) 查询所有菜单
     /**
      * 菜单表 数据层
      * @author spikeCong
      * @date 2023/5/9
      **/
     public interface SysMenuMapper extends BaseMapper {
         /**
          * 用户为admin时,查询全部菜单信息
          * @param
          * @return: 菜单列表
          */
         public List selectMenuTreeAll();
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    (2) 根据用户ID查询菜单
     /**
          * 根据用户id 查询菜单信息
          * @param
          * @return: 菜单列表
          */
     public List selectMenuTreeByUserId(Long userId);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    4.3.4.2 SysMenuService
    (1) 根据用户ID查询菜单树信息
     /**
          * 根据用户ID 查询菜单树信息
          * @param userId
          * @return: 菜单列表
          */
     public List selectMenuTreeByUserId(Long userId);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
     @Override
     public List selectMenuTreeByUserId(Long userId) {
     
         List menus = null;
         if(userId != null && 1L == userId){
             menus = menuMapper.selectMenuTreeAll();
         }else{
             menus = menuMapper.selectMenuTreeByUserId(userId);
         }
     
         //todo 获取子菜单
          return getChildPerms(menus,0);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    (2) 根据父节点ID获取所有子节点
     /**
          * 根据父节点ID 获取所有子节点
          * @param menus
          * @param parentId 传入的父节点Id
          * @return: java.util.List
          */
     private List getChildPerms(List menus, int parentId) {
     
         List returnList = new ArrayList<>();
         menus.stream()
             .filter(m-> m.getParentId() == parentId)
             .forEach(m -> {
                 recursionFn(menus,m);
                 returnList.add(m);
             });
     
         return returnList;
     }
     
     /**
          * 递归获取子菜单
          * @param menus
          * @param m
          */
     private void recursionFn(List menus, SysMenu m) {
         //得到子节点列表,保存到父菜单的children中
         List childList = getChildList(menus,m);
         m.setChildren(childList);
         for (SysMenu childMenu : childList) {
             //判断子节点下是否还有子节点
             if(getChildList(menus, childMenu).size() > 0 ? true : false){
                 recursionFn(menus, childMenu);
             }
         }
     }
     
     /**
          * 得到子节点列表
          * @param menus
          * @param m
          * @return: 子菜单集合
          */
     private List getChildList(List menus, SysMenu m) {
         List subMenus = menus.stream()
             .filter(sub -> sub.getParentId().longValue() == m.getMenuId().longValue())
             .collect(Collectors.toList());
     
         return subMenus;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    4.3.4.3 SysLoginController
    (1) 实现获取路由信息功能
         /**
          * 获取路由信息
          * @param  
          * @return: 路由信息
          */
         @GetMapping("/getRouters")
         public BaseResponse getRouters(){
             LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
             SysUser user = loginUser.getUser();
             List menus = sysMenuService.selectMenuTreeByUserId(user.getUserId());
             
             return BaseResponse.success(menus);
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    (2) 根据接口文档进行测试

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    4.3.4.4 构建前端路由所需要的菜单

    测试获取的JSON数据不符合接口文档要求

    (1) 创建前端所需的菜单路由实体
    • 路由配置信息
     /**
      * 路由配置信息VO
      * @author spikeCong
      * @date 2023/5/11
      **/
     public class RouterVo {
     
         /**
          * 路由名字
          */
         private String name;
     
         /**
          * 路由地址
          */
         private String path;
     
         /**
          * 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现
          */
         private boolean hidden;
     
         /**
          * 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
          */
         private String redirect;
     
         /**
          * 组件地址
          */
         private String component;
     
         /**
          * 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
          */
         private Boolean alwaysShow;
     
         /**
          * 其他元素
          */
         private MetaVo meta;
         
         /**
          * 子路由
          */
         private List children;
     
     //get... set...
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 路由显示信息
     /**
      * 路由显示信息
      * @author spikeCong
      * @date 2023/5/11
      **/
     public class MetaVo {
     
         /**
          * 设置该路由在侧边栏和面包屑中展示的名字
          */
         private String title;
     
         /**
          * 设置该路由的图标,对应路径src/assets/icons/svg
          */
         private String icon;
     
         /**
          * 设置为true,则不会被 缓存
          */
         private boolean noCache;
     
         public MetaVo()
         {
         }
     
         public MetaVo(String title, String icon)
         {
             this.title = title;
             this.icon = icon;
         }
     
         public MetaVo(String title, String icon, boolean noCache)
         {
             this.title = title;
             this.icon = icon;
             this.noCache = noCache;
         }
     
         public boolean isNoCache()
         {
             return noCache;
         }
     
         public void setNoCache(boolean noCache)
         {
             this.noCache = noCache;
         }
     
         public String getTitle()
         {
             return title;
         }
     
         public void setTitle(String title)
         {
             this.title = title;
         }
     
         public String getIcon()
         {
             return icon;
         }
     
         public void setIcon(String icon)
         {
             this.icon = icon;
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 添加用户菜单常量类
     /**
      * 用户常量信息
      * @author spikeCong
      * @date 2023/5/11
      **/
     public class UserConstants {
     
         /**
          * 平台内系统用户的唯一标志
          */
         public static final String SYS_USER = "SYS_USER";
     
         /** 正常状态 */
         public static final String NORMAL = "0";
     
         /** 异常状态 */
         public static final String EXCEPTION = "1";
     
         /** 用户封禁状态 */
         public static final String USER_DISABLE = "1";
     
         /** 角色封禁状态 */
         public static final String ROLE_DISABLE = "1";
     
         /** 部门正常状态 */
         public static final String DEPT_NORMAL = "0";
     
         /** 部门停用状态 */
         public static final String DEPT_DISABLE = "1";
     
         /** 字典正常状态 */
         public static final String DICT_NORMAL = "0";
     
         /** 是否为系统默认(是) */
         public static final String YES = "Y";
     
         /** 是否菜单外链(是) */
         public static final String YES_FRAME = "0";
     
         /** 是否菜单外链(否) */
         public static final String NO_FRAME = "1";
     
         /** 菜单类型(目录) */
         public static final String TYPE_DIR = "M";
     
         /** 菜单类型(菜单) */
         public static final String TYPE_MENU = "C";
     
         /** 菜单类型(按钮) */
         public static final String TYPE_BUTTON = "F";
     
         /** Layout组件标识 */
         public final static String LAYOUT = "Layout";
     
         /** ParentView组件标识 */
         public final static String PARENT_VIEW = "ParentView";
     
         /** 校验返回结果码 */
         public final static String UNIQUE = "0";
         public final static String NOT_UNIQUE = "1";
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    (2) 构建前端路由所需要的菜单

    image.png

    image.png

    image.png

    • SysMenuService
     /**
          * 构建前端路由所需要的菜单
          * @param menus 菜单列表
          * @return: 路由列表
          */
     public List buildMenus(List menus);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • SysMenuServiceImpl ==> 设置路由名称
         @Override
         public List buildMenus(List menus) {
     
             List routers = new LinkedList<>();
             for (SysMenu menu : menus) {
                 RouterVo routerVo = new RouterVo();
                 routerVo.setName(getRouteName(menu));
     
             }
     
             return null;
         }
     
         /**
          * 获取路由名称
          * @param menu  菜单信息
          * @return: 路由名称
          */
         public String getRouteName(SysMenu menu) {
             String routerName = org.apache.commons.lang3.StringUtils.capitalize(menu.getPath());
             return routerName;
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • SysMenuServiceImpl ==> 设置路由地址
     @Override
     public List buildMenus(List menus) {
     
         List routers = new LinkedList<>();
         for (SysMenu menu : menus) {
             RouterVo routerVo = new RouterVo();
             routerVo.setName(getRouteName(menu));
             routerVo.setPath(getRoutePath(menu));
     
         }
     
         return null;
     }
     
     /**
          * 获取路由地址
          * @param menu 菜单信息
          * @return: 路由地址
          */
     public String getRoutePath(SysMenu menu) {
         String routerPath = menu.getPath();
         //非外链 并且是一级目录,菜单类型为 M(目录)
         if(0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType())
            && UserConstants.NO_FRAME.equals(menu.getIsFrame())){
             routerPath = "/" + menu.getPath();
         }
     
         return routerPath;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • SysMenuServiceImpl ==> 设置组件信息
     @Override
     public List buildMenus(List menus) {
     
         List routers = new LinkedList<>();
         for (SysMenu menu : menus) {
             RouterVo routerVo = new RouterVo();
             routerVo.setName(getRouteName(menu));
             routerVo.setPath(getRoutePath(menu));
             routerVo.setComponent(getComponent(menu));
     
         }
     
         return null;
     }
     
     /**
          * 获取组件信息
          * @param menu
          * @return: 组件信息
          */
     public String getComponent(SysMenu menu) {
         String component = UserConstants.LAYOUT;
         if(!StringUtils.isEmpty(menu.getComponent())){
             component = menu.getComponent();
         }else if(menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType())){
             component = UserConstants.PARENT_VIEW;
         }
     
         return component;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • SysMenuServiceImpl ==> 设置其他信息
     @Override
     public List buildMenus(List menus) {
     
         List routers = new LinkedList<>();
         for (SysMenu menu : menus) {
             RouterVo routerVo = new RouterVo();
             //设置路由名称 例如: System 开头字母大写
             routerVo.setName(getRouteName(menu));
             //设置路由地址 例如: 根目录 /system , 二级目录 user
             routerVo.setPath(getRoutePath(menu));
             //设置组件地址 例如: system/user/index
             routerVo.setComponent(getComponent(menu));
             //设置是否隐藏 ,隐藏后侧边栏不会出现
             routerVo.setHidden("1".equals(menu.getVisible()));
             //基础元素
             routerVo.setMeta(new MetaVo(menu.getMenuName(),menu.getIcon(),"1".equals(menu.getIsCache())));
             //子菜单
             List subMenus = menu.getChildren();
             //子菜单不为空 && 类型为M 菜单类型(目录 顶级父菜单)
             if(!subMenus.isEmpty() && subMenus.size() > 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType())){
                 routerVo.setAlwaysShow(true);   //下面有子路由
                 routerVo.setRedirect("noRedirect"); //在导航栏中不可点击
                 routerVo.setChildren(buildMenus(subMenus)); //递归设置子菜单
             }
     
             routers.add(routerVo);
         }
         return routers;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    (3) 修改SysLoginController

     /**
          * 获取路由信息
          * @param
          * @return: 路由信息
          */
     @GetMapping("/getRouters")
     public BaseResponse getRouters(){
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         SysUser user = loginUser.getUser();
         //获取菜单列表
         List menus = sysMenuService.selectMenuTreeByUserId(user.getUserId());
         //转换为前端需要的路由列表
         List routerVoList = sysMenuService.buildMenus(menus);
     
         return BaseResponse.success(routerVoList);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    image.png

    4.3.5 自定义权限校验规则

    (1) 修改LoginUser

    添加permissions属性和构造方法

     public class LoginUser implements UserDetails {
     
         /**
          * 用户信息
          */
         private SysUser user;
     
         /**
          * 权限列表
          */
         private Set permissions;
     
         public LoginUser(SysUser user, Set permissions) {
             this.user = user;
             this.permissions = permissions;
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    (2) 修改UserDetailsServiceImpl
     /**
      * 用户验证处理
      * @author spikeCong
      * @date 2023/5/3
      **/
     @Service
     @Slf4j
     public class UserDetailsServiceImpl implements UserDetailsService {
     
         @Autowired
         private SysUserService userService;
     
         @Autowired
         private SysPermissionService permissionService;
     
         @Override
         public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
     
             SysUser user = userService.selectUserByUserName(username);
     
             if(Objects.isNull(user)){
                 log.info("登录用户:{} 不存在",username);
                 throw new UsernameNotFoundException("登录用户: " + username + " 不存在");
             }
             else if(UserStatus.DELETED.getCode().equals(user.getDelFlag())){
                 log.info("登录用户:{} 已被删除",username);
                 throw new BaseException("对不起,您的账号: " + username + " 以被删除" );
             }
             else if(UserStatus.DISABLE.getCode().equals(user.getStatus())){
                 log.info("登录用户:{} 已被停用",username);
                 throw new BaseException("对不起,您的账号: " + username + " 以被停用" );
             }
     
             return createLoginUser(user);
         }
     
         public UserDetails createLoginUser(SysUser user) {
     
             return new LoginUser(user,permissionService.getMenuPermission(user));
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    (3) 自定义权限校验

    主要有以下几种校验方式

    • 验证用户是否具备某权限
    • 验证用户是否具有以下任意一个权限
    • 判断用户是否拥有某个角色
    • 验证用户是否具有以下任意一个角色
    /**
     * 自定义权限校验
     * @author spikeCong
     * @date 2023/5/9
     **/
    @Component("pe")
    public class PermsExpressionService {
    
        /** 所有权限的标识 */
        private static final String ALL_PERMISSION = "*:*:*";
    
        private static final String DELIMITERS = ",";
      
        @Autowired
        private TokenService tokenService;
      
        /**
         * 验证用户是否具备某权限
         * @param permission    权限字符串
         * @return: boolean     是否拥有权限
         */
        public boolean hasPerms(String permission){
            if(StringUtils.isEmpty(permission)){
                return false;
            }
      
            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
      
            if(Objects.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())){
                return false;
            }
      
            return hasPermissions(loginUser.getPermissions(),permission);
        }
    
        /**
         * 判断是否包含权限
         * @param permissions 权限列表
         * @param permission  权限字符串
         * @return: boolean
         */
        private boolean hasPermissions(Set permissions, String permission) {
      
            return permissions.contains(ALL_PERMISSION) || permissions.contains(permission);
        }
    
        /**
         * 验证用户是否具有以下任意一个权限
         * @param permissions 
         * @return: boolean
         */
        public boolean hasAnyPerms(String permissions){
            if(StringUtils.isEmpty(permissions)){
                return false;
            }
    
            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
            if(Objects.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())){
                return false;
            }
    
            Set authorities = loginUser.getPermissions();
            for (String permission : permissions.split(DELIMITERS)) {
                if(permission != null && hasPermissions(authorities,permission)){
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 判断用户是否拥有某个角色
         * @param role  角色字符串
         * @return: boolean
         */
        public boolean hasRole(String role){
            if(StringUtils.isEmpty(role)){
                return false;
            }
            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
            if(Objects.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())){
                return false;
            }
            for (SysRole sysRole : loginUser.getUser().getRoles()) {
                String roleKey = sysRole.getRoleKey();
                if("admin".equals(roleKey) || roleKey.equals(role)){
                    return true;
                }
            }
            return false;
        }
    
    
        /**
         * 判断用户是否具有以下任意一个角色
         * @param roles  角色字符串,多个角色用逗号分隔
         * @return: boolean
         */
        public boolean hasAnyRole(String roles){
    
            if(StringUtils.isEmpty(roles)){
                return false;
            }
            LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
            if(Objects.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())){
                return false;
            }
            for (String role : roles.split(DELIMITERS)) {
                if(hasRole(role)){
                    return true;
                }
            }
            return false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    (4) 测试权限校验

    第一步: 添加权限校验

    • 在获取部门列表接口上加一个权限校验 @PreAuthorize("@pe.hasPermi('system:dept:list')")
    /**
         * 获取部门列表
         * @param sysDept
         * @return: com.msb.hjycommunity.common.core.domain.BaseResponse
         */
    @PreAuthorize("@pe.hasPerms('system:dept:list')")
    @GetMapping("/list")
    public BaseResponse list(SysDept sysDept){
    
        List<SysDept> sysDepts = deptService.selectDeptList(sysDept);
        return BaseResponse.success(sysDepts);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 在查询小区的接口上加一个权限校验 @PreAuthorize("@pe.hasPermi('system:community:list')")
    /**
         * 查询小区
         * @param hjyCommunity
         * @return: com.msb.hjycommunity.common.core.page.PageResult
         */
    @GetMapping("/list")
    @PreAuthorize("@pe.hasPerms('system:community:list')")
    public PageResult list(HjyCommunity hjyCommunity){
    
        startPage();
        List<HjyCommunityDto> list = hjyCommunityService.selectHjyCommunityList(hjyCommunity);
    
        //响应数据
        return getData(list);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    第二步: 使用 laoli 账号登录

    image.png

    第三步: 获取用户 laoli 拥有的权限信息, laoli只有查看小区信息的权限,没有查看部门信息的权限

    image.png

    第四步: 分别访问查询小区信息接口、查询部门信息接口

    • 查询小区信息,可以获取数据

    image.png

    • 查询部门信息,被拦截 没有通过验证

    image.png

  • 相关阅读:
    计算机竞赛 机器视觉的试卷批改系统 - opencv python 视觉识别
    Linux下对PC/SC智能卡接口编程
    优思学院|六西格玛管理常用的假设检验是什么?
    vue面试题6
    Flink CDC (Mysql为例)
    直播平台Stacked完成1290万美元A轮融资,计划转型为Web3流媒体
    企业使用人工智能创建营销内容的8种实践
    外卖项目01---软件开发整体介绍
    vue3+vite在线预览pdf
    OpenCV-Python学习(15)—— OpenCV 图像旋转角度计算(NumPy 三角函数)
  • 原文地址:https://blog.csdn.net/studyday1/article/details/132872357