基于Spring的单点登录SSO实现(redis+JWT+SpringSecurity)

本文介绍了基于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# 端口,默认为6379port: 6379# 数据库索引database: 1# 密码password: root# 连接超时时间timeout: 10slettuce:pool:# 连接池中的最小空闲连接min-idle: 0# 连接池中的最大空闲连接max-idle: 8# 连接池的最大数据库连接数max-active: 8# #连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1ms
token-redis:# 地址host: 10.110.1.1# 端口,默认为6379port: 6379# 数据库索引database: 4# 密码password: root# 连接超时时间(s)timeout: 10slettuce: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 RedisTemplate redisTemplate;public <T> void setCacheObject(final String key, final T value) {redisTemplate.opsForValue().set(key, value);
}//其他}

4、使用

主要集中在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);
}

四、实现示例

1、RedisConfig.java

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<String> 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();// timeoutif(redisProperties.getTimeout() != null) {builder.commandTimeout(redisProperties.getTimeout());}// shutdownTimeoutif(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();// timeoutif(redisProperties.getTimeout() != null) {builder.commandTimeout(redisProperties.getTimeout());}// shutdownTimeoutif(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<Object, Object> redisTemplate(@Qualifier("redisConnectionFactory") RedisConnectionFactory connectionFactory){RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);// 使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(fastJson2JsonRedisSerializer);// Hash的key也采用StringRedisSerializer的序列化方式template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(fastJson2JsonRedisSerializer);template.afterPropertiesSet();return template;}/*** token业务缓存的redis* @param redisConnectionFactory* @return*/@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;}@Beanpublic DefaultRedisScript<Long> limitScript(){DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();redisScript.setScriptText(limitScriptText());redisScript.setResultType(Long.class);return redisScript;}/*** 限流脚本*/private String limitScriptText(){return "local key = KEYS[1]\n" +"local count = tonumber(ARGV[1])\n" +"local time = tonumber(ARGV[2])\n" +"local current = redis.call('get', key);\n" +"if current and tonumber(current) > count then\n" +"    return current;\n" +"end\n" +"current = redis.call('incr', key)\n" +"if tonumber(current) == 1 then\n" +"    redis.call('expire', key, time)\n" +"end\n" +"return current;";}/*** 自定义redis缓存超时等配置策略* @param template* @return*/@Beanpublic CacheManager cacheManager(RedisTemplate<Object, Object> template) {return RedisCacheManager.RedisCacheManagerBuilder// Redis 连接工厂.fromConnectionFactory(template.getConnectionFactory()).cacheDefaults(getCacheConfigurationWithTtl(template, 0)) // 默认缓存永不超时.withCacheConfiguration("gen_table", getCacheConfigurationWithTtl(template, 0))// 配置同步修改或删除 put/evict.transactionAware().build();}private RedisCacheConfiguration getCacheConfigurationWithTtl(RedisTemplate<Object, Object> template, long seconds) {return RedisCacheConfiguration.defaultCacheConfig()// 设置key为String.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(template.getStringSerializer()))// 设置value 为自动转Json的Object.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new FastJson2JsonRedisSerializer(Object.class)))// 不缓存null.disableCachingNullValues()// 变双冒号为单冒号.computePrefixWith(keyName -> keyName + ":")// 缓存数据超时时间.entryTtl(Duration.ofSeconds(seconds));}
}

2、RedisToken.java

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 <T> void setCacheObject(final String key, final T value) {redisTemplate.opsForValue().set(key, value);}/*** 缓存基本的对象,Integer、String、实体类等** @param key      缓存的键值* @param value    缓存的值* @param timeout  时间* @param timeUnit 时间颗粒度*/public <T> 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> T getCacheObject(final String key) {ValueOperations<String, T> 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 <T> long setCacheList(final String key, final List<T> dataList) {Long count = redisTemplate.opsForList().rightPushAll(key, dataList);return count == null ? 0 : count;}/*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public <T> List<T> getCacheList(final String key) {return redisTemplate.opsForList().range(key, 0, -1);}/*** 缓存Set** @param key     缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator();while (it.hasNext()) {setOperation.add(it.next());}return setOperation;}/*** 获得缓存的set** @param key* @return*/public <T> Set<T> getCacheSet(final String key) {return redisTemplate.opsForSet().members(key);}/*** 缓存Map** @param key* @param dataMap*/public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 获得缓存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(final String key) {return redisTemplate.opsForHash().entries(key);}/*** 往Hash中存入数据** @param key   Redis键* @param hKey  Hash键* @param value 值*/public <T> 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> T getCacheMapValue(final String key, final String hKey) {HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();return opsForHash.get(key, hKey);}/*** 获取多个Hash中的数据** @param key   Redis键* @param hKeys Hash键集合* @return Hash对象集合*/public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> 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<String> keys(final String pattern) {return redisTemplate.keys(pattern);}/*** 通配符删除key** @param key key值*/public void deleteKey(final String key) {Collection<String> keys = this.keys(key);redisTemplate.delete(keys);}
}

3、TokenService.java

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;@Autowiredprivate 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<String, Object> 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<String, Object> 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;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/53360.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JVM中的GC过程

堆内存结构&#xff1a;在详细讨论GC过程之前&#xff0c;需要了解JVM堆内存的结构。JVM堆内存通常被分为新生代&#xff08;Young Generation&#xff09;和老年代&#xff08;Old Generation&#xff09;&#xff0c;其中新生代又进一步细分为Eden区&#xff08;Eden Space&a…

9、类和对象

9.1 封装 9.1.1 封装的例子 class Student { public:string name;int age; public:void setName(string name_) {name name_;} }; int main() {Student s1;s1.setName("zhangsan");return 0; }类中的行为都叫做成员&#xff0c;例如成员属性&#xff0c;成员变量&…

Spring Cloud全解析:负载均衡算法

负载均衡算法 集中式负载均衡 在服务的消费方和提供方之间使用独立的LB设施(可以是硬件&#xff0c;如F5&#xff0c;也可以是软件&#xff0c;如Nginx)&#xff0c;由该设施负责把访问请求通过某种策略转发至服务的提供方 进程内负载均衡 将LB逻辑集成到消费方&#xff0c…

Redis篇 - 深入了解查询缓存与缓存带来的问题

引言 在现代Web应用程序中&#xff0c;为了提高数据访问速度和减轻数据库的压力&#xff0c;缓存技术变得越来越重要。Redis作为一款高性能的键值存储系统&#xff0c;在缓存领域有着广泛的应用。然而&#xff0c;随着缓存的引入&#xff0c;一系列新的挑战也随之而来。本文将…

飞速(FS)S5800-48T4S:如何使用MLAG?

MLAG&#xff08;多机箱链路聚合组&#xff09;可实现无缝故障转移并优化带宽利用率&#xff0c;从而增强网络冗余和提高可扩展性。它允许多台交换机作为一个统一实体运行&#xff0c;从而降低停机风险并确保网络运行不中断。飞速&#xff08;FS&#xff09;S5800-48T4S是一款支…

IP学习——Fiveday

设备排错 [R1]display ip interface brief 查看路由器接口的IP地址信息 [R1]display current-configuration int g0/0/1.10 查看路由器接口的IP地址信息 TG---> trunk查看vlan指令:displayvan其中UT--->accessc.vlan确认完成后 即链路层配置完成排查网络层错误 排查终端主…

二维高斯函数的两种形式

第一种形式很常见 多元正态分布 多元正态分布&#xff08;Multivariate Normal Distribution&#xff09;&#xff0c;也称为多变量正态分布或多维正态分布&#xff0c;是统计学中一种重要的概率分布&#xff0c;用于描述多个随机变量的联合分布。 假设有 n n n 个随机变量…

Monkey日志ANR、CRASH、空指针异常及其他异常数据分析

引言 在Android开发过程中&#xff0c;monkey测试是一种常用的随机测试手段&#xff0c;用于模拟用户的各种操作来发现应用中的稳定性问题。通过monkey测试生成的日志文件包含了丰富的信息&#xff0c;包括应用程序崩溃&#xff08;Crash&#xff09;、无响应&#xff08;ANR&…

【LabVIEW学习篇 - 18】:人机界面交互设计02

文章目录 错误处理函数简单错误处理器通用错误处理器清楚错误合并错误错误代码至错误簇转换查找第一个错误 鼠标指针 错误处理函数 在LabVIEW中&#xff0c;是通过错误输入簇和错误输出簇来传递错误信息&#xff0c;可以将底层错误信息传递到上层VI。设计人员需要对不同程度的…

2024 数学建模高教社杯 国赛(C题)| 农作物的种植策略 | 建模秘籍文章代码思路大全

铛铛&#xff01;小秘籍来咯&#xff01; 小秘籍团队独辟蹊径&#xff0c;运用等多目标规划等强大工具&#xff0c;构建了这一题的详细解答哦&#xff01; 为大家量身打造创新解决方案。小秘籍团队&#xff0c;始终引领着建模问题求解的风潮。 抓紧小秘籍&#xff0c;我们出发…

过滤器(Filter)和拦截器(Interceptor)

在Web开发中&#xff0c;过滤器&#xff08;Filter&#xff09;和拦截器&#xff08;Interceptor&#xff09;都是重要的组件&#xff0c;它们都可以对HTTP请求进行预处理、后处理以及一些额外的操作。然而&#xff0c;它们之间在多个方面存在明显的区别 1. 运行位置 过滤器&…

在python项目的docker镜像里使用pdm管理依赖

前言 在 DjangoStarter 项目中&#xff0c;我已经使用 pdm 作为默认的包管理器&#xff0c;不再直接使用 pip 所以部署的时候 dockerfile 和 docker-compose 配置也得修改一下。 dockerfile 首先修改一下 dockerfile ARG PYTHON_BASE3.11FROM python:$PYTHON_BASE# 设置 pytho…

高并发内存池(一):项目介绍与定长内存池的实现

目录​​​​​​​ 项目介绍 池化技术 内存池 内存碎片 malloc工作原理 定长内存池 申请内存 释放内存 定位new VirtualAlloc函数 封装VirtualAlloc 定长内存池的最终代码 项目介绍 项目原型&#xff1a;goole的开源项目tcmalloc&#xff08;Thread-Caching Mal…

9.5 面试题

1、多继承下&#xff0c;地址转换问题&#xff1a; 在 C 中&#xff0c;如果类 C 是多继承自 A 和 B&#xff0c;在执行强制类型转换时&#xff0c;地址值是否发生改变&#xff0c;取决于内存布局和继承方式。具体来说&#xff1a; 1. 标准布局下&#xff08;无虚继承&#x…

CIOE中国光博会&电巢科技即将联办“智能消费电子创新发展论坛”

在科技浪潮汹涌澎湃的当下&#xff0c;从通信领域的高速光传输&#xff0c;到消费电子中的高清显示与先进成像技术&#xff0c;光电技术的应用范围不断拓展且日益深化。而AIGC 凭借其丰富的内容供给与个性化反馈能力&#xff0c;正为新一代消费电子及智能穿戴产品开辟崭新的发展…

前端请求的路径baseURL怎么来的 ?nodejs解决cors问题的一种方法

背景&#xff1a;后端使用node.js搭建&#xff0c;用的是express 前端请求的路径baseURL怎么来的 &#xff1f; 前后端都在同一台电脑上运行&#xff0c;后端的域名就是localhost&#xff0c;如果使用的是http协议&#xff0c;后端监听的端口号为3000&#xff0c;那么前端请求…

#include <iostream>介绍

在C编程中&#xff0c;#include <iostream> 是一个非常重要的预处理指令&#xff0c;用于引入输入输出流库。以下是对 #include <iostream> 的详细介绍&#xff1a; 1. 作用 #include <iostream> 指令的主要作用是使程序能够使用标准输入输出流的功能。它包…

S3C2440开发板:时钟,PWM定时器控制蜂鸣器发声

时钟 时钟和电源管理模块由三部分组成&#xff1a;时钟控制&#xff0c;USB 控制和电源控制。 S3C2440A 中的时钟控制逻辑可以产生必须的时钟信号&#xff0c;包括 CPU 的 FCLK&#xff0c;AHB 总线外设的 HCLK 以及 APB 总线外设的 PCLK。S3C2440A 包含两个锁相环&#xff08…

VBA进行excel坐标转换

在Excel里利用坐标绘图时&#xff0c;可以比较容易想到采用数据透视表&#xff0c;但是数据透视表生成的图不可更改&#xff0c;因此本案例采用VBA进行坐标变换而不改变原始值来转换图像&#xff0c;即实现图像的左右翻转和上下翻转&#xff0c;如下图所示&#xff0c;选择map的…

redis的持久化RDB和AOF

redis的持久化有RDB和AOF两种方式。RDB(Redis Database)是redis在指定间隔将当前时间点的内存数据快照保存成一个二级制rdb文件。AOF(Append Only File)是将每一条写指令写入到特定日志文件&#xff0c;然后当服务重启时通过回放这些指令来恢复原来的数据集。redis可以同时开启…