基于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
  # 端口,默认为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 redisTemplate(@Qualifier("redisConnectionFactory") RedisConnectionFactory connectionFactory)
{

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;

}

(4)连接redis数据库操作

@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;
}

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  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 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 redisTemplate(@Qualifier("redisConnectionFactory") RedisConnectionFactory connectionFactory)
    {
        RedisTemplate 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 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;
    }

    @Bean
    public DefaultRedisScript limitScript()
    {
        DefaultRedisScript 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
     */
    @Bean
    public CacheManager cacheManager(RedisTemplate 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 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  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);
    }
}

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;

    @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;
    }

}

你可能感兴趣的:(java,spring,redis)