本文介绍了基于Spring的单点登录SSO实现(redis+JWT+SpringSecurity) 方法。
一、应用场景
平台包含多个系统应用的,实现只要在一个应用登录一次,就可以访问其他相互信任的应用。常用于多应用平台中,此时常常建立门户网站系统,进行单点登录。
二、实现思路
单点登录可基于cookie、session、token等方式。本文主要基于token和redis实现。
登录信息及token通常存储于redis中,鉴权和校验时需要取redis的用户信息。正常单个应用只有一个服务器的一个redis数据库,同时存储其他业务信息(字典、应用系统配置、其他业务数据)和登录信息,在同一数据库中以key值区分。在没有引入单点登录时,由于多应用的其他业务信息(字典、应用系统配置、其他业务数据)和登录信息有相同的key会冲突,所以应用之间的数据库是不同的。
单点登录的实现主要是将所有应用的登录信息存储到同一个redis数据库中,而各自的业务信息依然存储到原来应用自己的数据库。这样使得各个子系统登录信息实现共享,校验登录状态和token时取自同一信息,其他业务数据进行分离。
因此相当于将redis设置主库从库。实现时主要应用注解@primary、@Qualifier
@Bean、@ConfigurationProperties
三、实现方法
1、配置文件
三个系统如下设置,redis配置子系统的数据(datasebase不同),token-redis配置存储token的数据(database相同)
| 子系统1 |
子系统2 |
子系统3 |
|
| 业务数据存储 |
10.110.1.1:6379 database: 1 |
10.110.1.1:6379 database: 2 |
10.110.12.1:6379 database: 3 |
| Token及登录信息存储 |
10.110.1.1:6379 database: 4 |
10.110.1.1:6379 database: 4 |
10.110.1.1:6379 database: 4 |
redis:
# 地址
host: 10.110.1.1:6379
# 端口,默认为6379
port: 6379
# 数据库索引
database: 1
# 密码
password: root
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
token-redis:
# 地址
host: 10.110.1.1
# 端口,默认为6379
port: 6379
# 数据库索引
database: 4
# 密码
password: root
# 连接超时时间(s)
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
2、redisConfig配置的主要注解和方法
src/main/java/com/inspur/framework/config/RedisConfig.java
(1)配置读取
@ConfigurationProperties(prefix = "spring.token-redis")
@ConfigurationProperties(prefix = "spring.token")
区分配置文件的连接,firstRedisProperties和secondRedisProperties返回RedisProperties连接配置
@Primary
不同类实现同一接口时,不注明名称则默认用当下的bean,注解在子系统的默认redis配置
@Bean(name = "mainRedisProperties")
@Bean(name = "tokenRedisProperties")
给返回的配置RedisProperties命名,在传参时标注@Qualifier配合使用。@Bean在项目启动时生成。
(2)连接方法
连接池配置并和redis建立连接,连接参数由@Qualifier指定
// 单系统默认配置
@Primary
@Bean(name = "redisConnectionFactory")
public RedisConnectionFactory redisConnectionFactory(@Qualifier("mainRedisProperties") RedisProperties redisProperties) {
// redis单体模式连接配置
RedisStandaloneConfiguration standaloneConfig = new RedisStandaloneConfiguration();
standaloneConfig.setHostName(redisProperties.getHost());
standaloneConfig.setPort(redisProperties.getPort());
standaloneConfig.setDatabase(redisProperties.getDatabase());
standaloneConfig.setPassword(RedisPassword.of(redisProperties.getPassword()));
standaloneConfig.setDatabase(redisProperties.getDatabase());
redisConfig = standaloneConfig;
// lettuce连接池配置
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
RedisProperties.Lettuce lettuce = redisProperties.getLettuce();
if(lettuce.getPool() != null) {
RedisProperties.Pool pool = redisProperties.getLettuce().getPool();
// 连接池最大连接数
poolConfig.setMaxTotal(pool.getMaxActive());
// 连接池中的最大空闲连接
poolConfig.setMaxIdle(pool.getMaxIdle());
// 连接池中的最小空闲连接
poolConfig.setMinIdle(pool.getMinIdle());
// 连接池最大阻塞等待时间(使用负值表示没有限制)
poolConfig.setMaxWaitMillis(pool.getMaxWait().toMillis());
}
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
// timeout
if(redisProperties.getTimeout() != null) {
builder.commandTimeout(redisProperties.getTimeout());
}
// shutdownTimeout
if(lettuce.getShutdownTimeout() != null) {
builder.shutdownTimeout(lettuce.getShutdownTimeout());
}
// 创建Factory对象
LettuceClientConfiguration clientConfig = builder.poolConfig(poolConfig).build();
return new LettuceConnectionFactory(redisConfig, clientConfig);
}
// tokenredis配置
@Bean(name = "tokenRedisConnectionFactory")
public RedisConnectionFactory tokenRedisConnectionFactory(@Qualifier("tokenRedisProperties") RedisProperties redisProperties) {
// 同上
}
(3) 连接池变量
用于实际业务中需要操作redis时的变量注入
@Bean(name = "redisTemplate")
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(@Qualifier("redisConnectionFactory") RedisConnectionFactory connectionFactory)
{
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(fastJson2JsonRedisSerializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(fastJson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
(4)连接redis数据库操作
@Bean(name = "tokenRedisTemplate")
public RedisTemplate<String, Object> tokenRedisTemplate(@Qualifier("tokenRedisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(fastJson2JsonRedisSerializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(fastJson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
3、RedisToken增删改查操作
com/common/core/redis/RedisTokenCache.java
与原有的RedisCache.java相比基本相同,主要时redisTemplate采用的连接不同,通过注解指定名称实现@Qualifier("tokenRedisTemplate")
@Autowired
@Qualifier("tokenRedisTemplate")
public RedisTemplate redisTemplate
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisTokenCache {
@Autowired
@Qualifier("tokenRedisTemplate")
public RedisTempl

&spm=1001.2101.3001.5002&articleId=141727998&d=1&t=3&u=d076124da3c245b18bd8e92c05dc237c)

被折叠的 条评论
为什么被折叠?



