一文上手SpringSecurity【九】

在校验token的过滤器当中, 由于需要根据用户名称, 去查询出要认证的对象,然后再去数据库当中查询出角色列表、权限列表.频繁的操作数据库,可能会带来性能瓶颈, 那么我们该如何解决呢?

我们可以引入Redis, 将认证的对象,存储到Redis当中,在校验token的过滤器当中,可以直接从Redis当中拿,这样就避免了频繁查询数据库的操作.

一、引入Redis

1.1 Redis配置

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

添加配置文件

yaml 复制代码
spring:
  data:
    redis:
      host: ip地址
      port: 6379
      password: Rj1024
      client-type: lettuce
      database: 0

配置文件

java 复制代码
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        ObjectMapper objectMapper = new ObjectMapper();

        // 开启序列化LocalDate/LocalDateTime
        // jsr310里包括的.
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        objectMapper.registerModule(javaTimeModule);
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
        // 设置key和value的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);

        // 设置hashKey和hashValue的序列化规则
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);

        // 设置支持事务
        redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

二、修改业务逻辑

2.1 修改service层

在生成token的时候,我们直接返回给前端了,此处我们将token直接存储到Redis当中.

修改之后的代码如下所示:

修改校验token的过滤器,从Redis缓存中取数据

修改完代码如下所示

2.2 测试效果

启动服务器,请求,redis存储的数据如下所示

但是在校验token的过滤器当中, 从redis反序化出User对象的时候抛出了异常, 异常信息如下图所示

找到User源码, 查看一下, 发现User确实没有无参构造,造成反序列化错误

默认内置提供的User对象,不能满足我们的需求,此时我们只能自定义认证对象,自己去实现UserDetails接口了.

2.3 自定义认证对象

自定义一个类,实现UserDetails接口

java 复制代码
JsonIncludeProperties({"tbSysUser", "authorityList"})
public class LoginUser implements UserDetails {
    private TbSysUser tbSysUser;
    // 用于接收权限列表, 通过此集合转换为Collection<? extends GrantedAuthority>类型的权限列表
    // 因为GrantedAuthority的实现类SimpleGrantedAuthority也没有无参构造,所以要避免序列化
    // SimpleGrantedAuthority对象,我们直接序列化List<String>
    // 在使用的时候,将List<String>转换为Collection<? extends GrantedAuthority>即可
    private List<String> authorityList;

    @JsonIgnore()
    private List<SimpleGrantedAuthority> simpleGrantedAuthorityList;

    public LoginUser() {
    }

    public LoginUser(TbSysUser tbSysUser, List<String> authorityList) {
        this.tbSysUser = tbSysUser;
        this.authorityList = authorityList;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(simpleGrantedAuthorityList != null){
            return simpleGrantedAuthorityList;
        }

        simpleGrantedAuthorityList = new ArrayList<SimpleGrantedAuthority>();
        for (String s : authorityList) {
            simpleGrantedAuthorityList.add(new SimpleGrantedAuthority(s));
        }
        return simpleGrantedAuthorityList;
    }

    @Override
    public String getPassword() {
        return tbSysUser.getPassword();
    }

    @Override
    public String getUsername() {
        return tbSysUser.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

这里需要注意的是: GrantedAuthority的实现类SimpleGrantedAuthority也没有无参构造,所以要避免序列化.

修改UserDetailsService实现类

修改放入redis的对象

修改校验token的过滤器,从redis当中反序列化出LoginUser对象

redis当中保存的数据

测试一下效果

确实从缓存当中反序列化的数据,并没有去查询数据库.到此,替换完成.

三、总结

3.1 重点内容

  • 将认证对象放到缓存当中避免频繁的操作数据库
  • User对象、SimpleGrantedAuthority对象都没有无参构造, 导致jackson无法进行序列化,这里我们使用了自定义UserDetails对象的方式来解决
  • 注意jackson注解的使用
相关推荐
这周也會开心1 分钟前
云服务器安装JDK、Tomcat、MySQL
java·服务器·tomcat
hrrrrb1 小时前
【Spring Security】Spring Security 概念
java·数据库·spring
小信丶1 小时前
Spring 中解决 “Could not autowire. There is more than one bean of type“ 错误
java·spring
周杰伦_Jay2 小时前
【Java虚拟机(JVM)全面解析】从原理到面试实战、JVM故障处理、类加载、内存区域、垃圾回收
java·jvm
xujiangyan_2 小时前
Redis详解
数据库·redis·缓存
摇滚侠3 小时前
Spring Boot 3零基础教程,IOC容器中组件的注册,笔记08
spring boot·笔记·后端
程序员小凯5 小时前
Spring Boot测试框架详解
java·spring boot·后端
豐儀麟阁贵6 小时前
基本数据类型
java·算法
_extraordinary_6 小时前
Java SpringMVC(二) --- 响应,综合性练习
java·开发语言
程序员 Harry6 小时前
深度解析:使用ZIP流式读取大型PPTX文件的最佳实践
java