本文介绍了基于Spring的单点登录SSO实现(redis+JWT+SpringSecurity) 方法。
平台包含多个系统应用的,实现只要在一个应用登录一次,就可以访问其他相互信任的应用。常用于多应用平台中,此时常常建立门户网站系统,进行单点登录。
单点登录可基于cookie、session、token等方式。本文主要基于token和redis实现。
登录信息及token通常存储于redis中,鉴权和校验时需要取redis的用户信息。正常单个应用只有一个服务器的一个redis数据库,同时存储其他业务信息(字典、应用系统配置、其他业务数据)和登录信息,在同一数据库中以key值区分。在没有引入单点登录时,由于多应用的其他业务信息(字典、应用系统配置、其他业务数据)和登录信息有相同的key会冲突,所以应用之间的数据库是不同的。
单点登录的实现主要是将所有应用的登录信息存储到同一个redis数据库中,而各自的业务信息依然存储到原来应用自己的数据库。这样使得各个子系统登录信息实现共享,校验登录状态和token时取自同一信息,其他业务数据进行分离。
因此相当于将redis设置主库从库。实现时主要应用注解@primary、@Qualifier
@Bean、@ConfigurationProperties
三个系统如下设置,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
src/main/java/com/inspur/framework/config/RedisConfig.java
@ConfigurationProperties(prefix = "spring.token-redis")
@ConfigurationProperties(prefix = "spring.token")
区分配置文件的连接,firstRedisProperties和secondRedisProperties返回RedisProperties连接配置
@Primary
不同类实现同一接口时,不注明名称则默认用当下的bean,注解在子系统的默认redis配置
@Bean(name = "mainRedisProperties")
@Bean(name = "tokenRedisProperties")
给返回的配置RedisProperties命名,在传参时标注@Qualifier配合使用。@Bean在项目启动时生成。
连接池配置并和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) {
// 同上
}
用于实际业务中需要操作redis时的变量注入
@Bean(name = "redisTemplate")
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate
@Bean(name = "tokenRedisTemplate")
public RedisTemplate tokenRedisTemplate(@Qualifier("tokenRedisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
RedisTemplate 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;
}
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 RedisTemplate redisTemplate;
public void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
//其他
}
主要集中在tokenService.java、UserDetailsServiceImpl.java等文件中
在用到登陆信息的存储、更新、获取时,均使用
@Autowired
private RedisTokenCache redisTokenCache;
操作,
主要集中在tokenService.java、UserDetailsServiceImpl.java等文件中。
// 登录方法
@Component
public class TokenService{
@Autowired
private RedisTokenCache redisTokenCache;
public LoginUser getLoginUser(HttpServletRequest request)
{
// 获取请求携带的令牌
String token = getToken(request);
if (StringUtils.isNotEmpty(token))
{
try
{
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
LoginUser user = redisTokenCache.getCacheObject(userKey);
return user;
}
catch (Exception e)
{
}
}
return null;
}
// 更新token方法
public void refreshToken(LoginUser loginUser)
{
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken());
redisTokenCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
package com.framework.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* redis配置
*
* @author Inspur
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
private static final Long TTL_DAY = 60 * 60 * 24L;
private static final Long TTL_WEEK = 60 * 60 * 24 * 7L;
private static final Long TTL_MONTH = 60 * 60 * 24 * 30L;
private static final RedisSerializer strSerializer = new StringRedisSerializer();
@SuppressWarnings(value = {"rawtypes"})
private static final FastJson2JsonRedisSerializer fastJson2JsonRedisSerializer = new FastJson2JsonRedisSerializer<>(Object.class);
/**
* 主要业务redis缓存,以业务redis为主,兼容后续的redisson
* @return
*/
@Primary
@Bean(name = "mainRedisProperties")
@ConfigurationProperties(prefix = "spring.redis")
public RedisProperties firstRedisProperties() {
return new RedisProperties();
}
/**
* token认证存储的redis缓存
* @return
*/
@Bean(name = "tokenRedisProperties")
@ConfigurationProperties(prefix = "spring.token-redis")
public RedisProperties secondRedisProperties() {
return new RedisProperties();
}
/**
* 以业务redis为主,兼容后续的redisson
* @param redisProperties
* @return
*/
@Primary
@Bean(name = "redisConnectionFactory")
public RedisConnectionFactory redisConnectionFactory(@Qualifier("mainRedisProperties") RedisProperties redisProperties) {
// redis单体模式连接配置
RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
redisConfig.setHostName(redisProperties.getHost());
redisConfig.setPort(redisProperties.getPort());
redisConfig.setDatabase(redisProperties.getDatabase());
redisConfig.setPassword(RedisPassword.of(redisProperties.getPassword()));
redisConfig.setDatabase(redisProperties.getDatabase());
// 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);
}
@Bean(name = "tokenRedisConnectionFactory")
public RedisConnectionFactory tokenRedisConnectionFactory(@Qualifier("tokenRedisProperties") RedisProperties redisProperties) {
// redis单体模式连接配置
RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
redisConfig.setHostName(redisProperties.getHost());
redisConfig.setPort(redisProperties.getPort());
redisConfig.setDatabase(redisProperties.getDatabase());
redisConfig.setPassword(RedisPassword.of(redisProperties.getPassword()));
redisConfig.setDatabase(redisProperties.getDatabase());
// 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);
}
/**
* 主要业务缓存的redis
* @param connectionFactory
* @return
*/
@Bean(name = "redisTemplate")
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate
src/main/java/com/inspur/common/core/redis/RedisCache.java
package com.inspur.common.core.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* spring redis 工具类,仅操作用户登录token
*
* @author Inspur
**/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisTokenCache {
@Autowired
@Qualifier("tokenRedisTemplate")
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获取有效时间
*
* @param key Redis键
* @return 有效时间
*/
public long getExpire(final String key) {
return redisTemplate.getExpire(key);
}
/**
* 判断 key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public T getCacheObject(final String key) {
ValueOperations operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public boolean deleteObject(final Collection collection) {
return redisTemplate.delete(collection) > 0;
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public long setCacheList(final String key, final List dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public List getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public BoundSetOperations setCacheSet(final String key, final Set dataSet) {
BoundSetOperations setOperation = redisTemplate.boundSetOps(key);
Iterator it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public Set getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public void setCacheMap(final String key, final Map dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public Map getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public T getCacheMapValue(final String key, final String hKey) {
HashOperations opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public List getMultiCacheMapValue(final String key, final Collection hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 删除Hash中的某条数据
*
* @param key Redis键
* @param hKey Hash键
* @return 是否成功
*/
public boolean deleteCacheMapValue(final String key, final String hKey) {
return redisTemplate.opsForHash().delete(key, hKey) > 0;
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection keys(final String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 通配符删除key
*
* @param key key值
*/
public void deleteKey(final String key) {
Collection keys = this.keys(key);
redisTemplate.delete(keys);
}
}
package com.inspur.common.service;
import com.inspur.common.constant.Constants;
import com.inspur.common.core.domain.model.LoginUser;
import com.inspur.common.core.redis.RedisTokenCache;
import com.inspur.common.utils.ServletUtils;
import com.inspur.common.utils.StringUtils;
import com.inspur.common.utils.ip.AddressUtils;
import com.inspur.common.utils.ip.IpUtils;
import com.inspur.common.utils.uuid.IdUtils;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* token验证处理
*
* @author Inspur
*/
@Component
public class TokenService
{
// 令牌自定义标识
@Value("${token.header}")
private String header;
// 令牌秘钥
@Value("${token.secret}")
private String secret;
// 令牌有效期(默认30分钟)
@Value("${token.expireTime}")
private int expireTime;
protected static final long MILLIS_SECOND = 1000;
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
@Autowired
private RedisTokenCache redisTokenCache;
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser(HttpServletRequest request)
{
// 获取请求携带的令牌
String token = getToken(request);
if (StringUtils.isNotEmpty(token))
{
try
{
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
LoginUser user = redisTokenCache.getCacheObject(userKey);
return user;
}
catch (Exception e)
{
}
}
return null;
}
public LoginUser getLoginUser(String token) {
if (StringUtils.isNotEmpty(token))
{
try
{
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
LoginUser user = redisTokenCache.getCacheObject(userKey);
return user;
}
catch (Exception e)
{
}
}
return null;
}
/**
* 设置用户身份信息
*/
public void setLoginUser(LoginUser loginUser)
{
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
{
refreshToken(loginUser);
}
}
/**
* 删除用户身份信息
*/
public void delLoginUser(String token)
{
if (StringUtils.isNotEmpty(token))
{
String userKey = getTokenKey(token);
redisTokenCache.deleteObject(userKey);
}
}
/**
* 创建令牌
*
* @param loginUser 用户信息
* @return 令牌
*/
public String createToken(LoginUser loginUser)
{
String token = IdUtils.fastUUID();
loginUser.setToken(token);
setUserAgent(loginUser);
refreshToken(loginUser);
Map claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token);
claims.put(Constants.JWT_USERID, loginUser.getUserId());
claims.put(Constants.JWT_USERNAME, loginUser.getUsername());
return createToken(claims);
}
/**
* 验证令牌有效期,相差不足20分钟,自动刷新缓存
*
* @param loginUser
* @return 令牌
*/
public void verifyToken(LoginUser loginUser)
{
long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
{
refreshToken(loginUser);
}
}
/**
* 刷新令牌有效期
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser)
{
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken());
redisTokenCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
/**
* 设置用户代理信息
*
* @param loginUser 登录信息
*/
public void setUserAgent(LoginUser loginUser)
{
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
loginUser.setIpaddr(ip);
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
loginUser.setBrowser(userAgent.getBrowser().getName());
loginUser.setOs(userAgent.getOperatingSystem().getName());
}
/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String createToken(Map claims)
{
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
return token;
}
/**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
private Claims parseToken(String token)
{
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token)
{
Claims claims = parseToken(token);
return claims.getSubject();
}
public LoginUser getLoginUserFromToken(String token)
{
if (StringUtils.isNotEmpty(token))
{
try
{
Claims claims = parseToken(token);
// 解析对应的权限以及用户信息
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
LoginUser user = redisTokenCache.getCacheObject(userKey);
return user;
}
catch (Exception e)
{
}
}
return null;
}
/**
* 获取请求token
*
* @param request
* @return token
*/
private String getToken(HttpServletRequest request)
{
String token = request.getHeader(header);
if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
{
token = token.replace(Constants.TOKEN_PREFIX, "");
}
return token;
}
private String getTokenKey(String uuid)
{
return Constants.LOGIN_TOKEN_KEY + uuid;
}
}