基于Ruoyi的同一token跨系统访问,后端单点登录并且鉴权方案

基于Ruoyi的同一token跨系统访问,后端单点登录并且鉴权方案

需求场景以及先决条件

  1. 同一环境下的多个ruoyi项目,各自使用相同的一组用户(我这里用的是LDAP的登录,不影响本文),但是每个权限拥有各自项目的权限.
  2. 希望一个前端登录的token,可以跨越不同的后端同时使用,即一个token访问所有系统,每个系统的权限,系统内部自行判断.

默认方案

  1. 默认情况下,ruoyi框架的设计是使用redis存储一个uuid作为token的key,用户信息作为value,存入到redis中的,例如

login_tokens:27e3c1ed-dee7-4495-99c7-d175beb4f0b1
{“@type”:“com.ruoyi.framework.security.LoginUser”,“browser”:“Firefox 13”,“deptId”:100,“expireTime”:1731988245275,“ipaddr”:“127.0.0.1”,“loginLocation”:“内网IP”,“loginTime”:1731984645275,“os”:“Windows 10”,“permissions”:Set[“:😗”],“token”:“27e3c1ed-dee7-4495-99c7-d175beb4f0b1”,“…省略以下”}

  1. 且每一个项目使用一个redis的db,互相独立
  2. 前端持有的jwt,是以uuid生成的令牌,即

Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjI3ZTNjMWVkLWRlZTctNDQ5NS05OWM3LWQxNzViZWI0ZjBiMSJ9.Ec0a7hRsyAL2ehZfiXWox0aczb58rKpARVipWfhbuir6sPrNsCArbbF47zDA2Mtmf1CHBdyodJx1bsWZrjLiKw

等价于

login_tokens:27e3c1ed-dee7-4495-99c7-d175beb4f0b1

改造思路

  1. 使用一个db,例如db0,所有的项目都多建立一个redis连接,连到db0.实现redis的部分共享
  2. 读取到token存在,即认为用户已登录
  3. 读取对应的程序的token,如果不存在,就是系统内部登录,创建一个对应token.
  4. 示例如下,指示一个token: 27e… 同时登录到了report和spc_mini2个系统,第一个key存储的值是username,其他的都是自己用的user信息,即权限信息等; 3和7是各自系统的其他redis缓存
    在这里插入图片描述

改造代码,一共4个类需要变更

在这里插入图片描述

  1. 新建数据源RedisCacheUser
  2. 配置数据源在RedisConfig
 @Bean
    @SuppressWarnings(value = {
   
    "unchecked", "rawtypes" })
    public RedisTemplate<String, Object> redisTemplateDb0(RedisConnectionFactory connectionFactory) {
   
   
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 使用原工厂配置,创建一个新的,但是指向其他的db
        LettuceConnectionFactory originalLettuceFactory = (LettuceConnectionFactory) connectionFactory;
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(originalLettuceFactory.getHostName());
        redisStandaloneConfiguration.setPort(originalLettuceFactory.getPort());
        redisStandaloneConfiguration.setPassword(originalLettuceFactory.getPassword());
        LettuceConnectionFactory clonedFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);
        clonedFactory.setDatabase(0);
        clonedFactory.afterPropertiesSet();
        template.setConnectionFactory(clonedFactory);


        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        serializer.setObjectMapper(mapper);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }
  1. 修改登录和获取用户的方式,就是存入到redis的key
    /**
     * 获取用户身份信息
     *
     * @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 = redisCache.getCacheObject(userKey);
                if (user == null)
                {
   
   
                    String userKeyUser = getTokenUser(uuid);
                    String username = redisCache.getCacheObject(userKeyUser);
                    user = (LoginUser) userDetailsService.loadUserByUsername(username);
                    user.setToken(uuid);
                    setUserAgent(user);
                    refreshToken(user);
                }
                return user;
            } catch (Exception e) {
   
   
            }
        }
        return null;
    }

完整需要修改的代码

package com.ruoyi.framework.config;

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.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
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;

/**
 * redis配置
 *
 * @author ruoyi
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
   
   
    @Bean
    @Primary
    @SuppressWarnings(value = {
   
    "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
   
   
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper
posted @ 2024-11-19 11:25  tomcat4014  阅读(12)  评论(0)    收藏  举报  来源