博主前言:本以为这个就是代替传统 jwt 的插件,没想到复杂程度如此之高。Spring Security 本身是个高度自定义化的组件,必须花时间重点学习一下。以下为个人配置学习的流程,从零到权限管理、redis嵌入等步骤。
本文基于尚硅谷的 Spring Security 教程学习,文章与原教程有不小出入,仅供参考。
B站视频链接:尚硅谷Java项目SpringSecurity+OAuth2权限管理实战教程
这一篇最大的难点在于,为什么要集成redis
,以及要替换掉 Spring Security 上的那一部分。我在网上冲浪了很久,始终不明确集成的意义。都是用一台计算机上的内存,有什么性能上的优势吗?
实际上,问题无外乎以下两点:
- 重启后数据会丢失。
- 无法在分布式环境中共享数据。
对于单部署的小应用来说,确实优势不大。但是对于微服务这种跨机器的后台网络,redis
是必不可少的。因为在访问请求后,token
的数据存储到本机的线程上,这使得其他机器访问不到该线程,从而拿不到用户的数据,很多业务也就无法进行。指定一个机器运行redis
,让其他机器通过该机器拿用户数据,就解决了这个问题。
还有一个问题是,对于token
,我们不能在上面存放太多数据,不然会变得很长——存对象的token
要比只存id
的token
长上数倍,而且每次访问接口都要现场解析复杂的token
,浪费性能。更何况解析的时候往往产生序列相关的异常,总之就是十分麻烦。
我们可以只在token
内存放id
,登录的时候,不仅生成token
,同时将用户数据存入redis
,设id
为键,这样每次访问接口,只需要快速解析出id
,就能从redis
获取用户数据,同时解决了复杂解析和序列化的两大难题,对于近些年来的后端程序,redis
近乎是必备。
这么讲,集成redis
的目的就很明确了:将原本和线程一并存储的用户数据分离,需要的时候在调用。
一、配置redis
- 引入依赖:
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>3.3.3</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.37</version>
</dependency>
- 编辑配置文件(application.yml):
spring:
data:
redis:
host: localhost
port: 6379
database: 0
password: # 没有请留空
timeout: 10s
lettuce:
pool:
min-idle: 0
max-idle: 10
max-active: 200
max-wait: -1ms
- 配置
redis
工具类:
[!IMPORTANT]
DatabaseException()
是我自定义的异常类,交由【全局异常管理】监听,读者可按需配置。详见这篇文章:Spring Boot 全局异常拦截配置
/**
* Redis 工具类
*
* @author Amane64
@Slf4j
@Component
@RequiredArgsConstructor
public class RedisUtils {
private final RedisTemplate<String, String> redisTemplate;
private final JsonUtils jsonUtils;
/**
* 存入缓存
*
* @param key 键
* @param value 值
*/
public void set(String key, Object value) {
this.setBySetTime(key, value, 3, TimeUnit.MINUTES);
}
/**
* 存入缓存,自定义过期时间
*
* @param key 键
* @param value 值
* @param timeout 过期时间
* @param timeUnit 时间单位
*/
public void setBySetTime(String key, Object value, long timeout, TimeUnit timeUnit) {
try {
redisTemplate.opsForValue().set(key, jsonUtils.serialize(value), timeout, timeUnit);
} catch (Exception e) {
log.error(e.getMessage());
throw new DatabaseException("存入缓存数据出错:" + key);
}
log.debug("存入缓存数据:{}", key);
}
/**
* 获取缓存数据
*
* @param key 键
* @param clazz 值类型
* @return 值
*/
public <T> T get(String key, Class<T> clazz) {
T res;
try {
res = jsonUtils.deserialize(redisTemplate.opsForValue().get(key), clazz);
} catch (Exception e) {
log.error(e.getMessage());
throw new DatabaseException("获取缓存数据出错:" + key);
}
log.debug("获取缓存数据:{}", key);
return res;
}
/**
* 判断缓存是否存在
*
* @param key 键
* @return 存在返回 true,不存在返回 false
*/
public boolean hasKey(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
/**
* 删除缓存
*
* @param key 键
*/
public void delete(String key) {
if (!hasKey(key)) throw new DatabaseException("缓存数据不存在:" + key);
redisTemplate.delete(key);
log.debug("删除缓存数据:{}", key);
}
}
- 配置序列化,供
redis
使用
[!IMPORTANT]
一般情况下是不需要序列化的,设置泛型为RedisTemplate<String, Object>,手动强转类型即可。
我有原创的监听Mybatis-Plus与Redis实现缓存同步的构造,必须使用双String的方式实现,故配置序列化。
对应文章传送门:[Mybatis-Plus 与 Redis 实现缓存同步]
/**
* 序列化与反序列化转换器
*
* @author Amane64
*/
public class JacksonObjectMapper extends ObjectMapper {
public JacksonObjectMapper() {
super();
// 前端参数蛇形命名法
this.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
// 收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
// 反序列化时,属性不存在的兼容处理
DeserializationConfig deserializationConfig = this.getDeserializationConfig().withoutFeatures(FAIL_ON_UNKNOWN_PROPERTIES);
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(new JavaTimeModule());
}
}
/**
* Json 序列化工具
*
* @author Amane64
*/
@Component
public class JsonUtils {
private final ObjectMapper objectMapper;
public JsonUtils() {
objectMapper = new JacksonObjectMapper();
}
public String serialize(Object object) throws Exception {
return objectMapper.writeValueAsString(object);
}
public <T> T deserialize(String json, Class<T> clazz) throws Exception {
return objectMapper.readValue(json, clazz);
}
}
- 配置
redis
的Config
类:
/**
* redis 配置
*
* @author Amane64
*/
@Configuration
@Slf4j
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
log.info("注册 redis 序列化器...");
var template = new RedisTemplate<String, String>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
更多内容请访问:个人博客传送门