一文上手SpringSecurity【九】
在校验token的过滤器当中, 由于需要根据用户名称, 去查询出要认证的对象,然后再去数据库当中查询出角色列表、权限列表.频繁的操作数据库,可能会带来性能瓶颈, 那么我们该如何解决呢?
我们可以引入Redis, 将认证的对象,存储到Redis当中,在校验token的过滤器当中,可以直接从Redis当中拿,这样就避免了频繁查询数据库的操作.
一、引入Redis
1.1 Redis配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
添加配置文件
spring:
data:
redis:
host: ip地址
port: 6379
password: Rj1024
client-type: lettuce
database: 0
配置文件
@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接口
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注解的使用