SpringSecurityOAuth2整合SpringCloud+Gateway后Session不一致导致的用户登录重定向问题

目录

  • 情景描述
  • 解决方案
    • Session不一致的解决方案
      • 具体实现
      • 测试
    • session 一致后仍然无法获得保存在session的请求
    • 重定向后无法从SecurityContextHolder获得身份信息的解决方案
  • 结语

情景描述

本文介绍的系统采用的技术是:SpringSecurity,SpringSecurityOAuth2,SpringCloud,Gateway,Nacos

概述:使用了SpringSecurity后,用户使用具有访问权限的接口A会存储当前请求A,并重定向至登录界面,当用户成功登录后将会从存储的请求中找到登录前的请求A进行重定向。但在分布式环境下Session在重定向后并不一致,因此导致无法从之前的session中获得存储的请求A,因此成功登录后并不能重定向至请求A。

如图:SpringSecurityOAuth2整合SpringCloud+Gateway后Session不一致导致的用户登录重定向问题_第1张图片

解决方案

Session不一致的解决方案

使用RedisHttpSession解决Session不一致问题。

具体实现

  1. pom.xml
    <dependency>
       <groupId>org.springframework.sessiongroupId>
       <artifactId>spring-session-data-redisartifactId>
    dependency>
    
    <dependency>
       <groupId>org.springframework.bootgroupId>
       <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>
    
  2. application.yml
      #Redis
      redis:
        port: 6379
        host: 
        database: 
        password: 
        timeout: 150000ms
        jedis:
          pool:
            max-active: 8
            max-wait: -1ms
            max-idle: 8
            min-idle: 0
    
  3. @EnableRedisHttpSession注解
    在启动类上添加注解
    @SpringBootApplication
    @ServletComponentScan("")
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    @EnableDiscoveryClient
    @EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE)
    public class AuhtorizationServer6001Application {
        public static void main(String[] args) {
            SpringApplication.run(AuhtorizationServer6001Application.class,args);
        }
    }
    

测试

这里我写了一个过滤器,再进入时打印每个请求的sessionId。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest newRequest = (HttpServletRequest) request;
    log.info(CLASS_TITLE+"SESSION ID : " + newRequest.getSession().getId());
    chain.doFilter(request,response);
}

控制台输出结果如图:
测试结果

session 一致后仍然无法获得保存在session的请求

在session一致已经测试成功的情况下,当用户成功登录以后SavedRequestAwareAuthenticationSuccessHandler仍然无法通过 requestCache.getRequest(request, response); 获得之前缓存的请求。

我的解决方案是在发现用户未登录缓存请求的时候就将请求保存到当前session,后续再通过session那出来,因此需要重写SpringSecurity框架中的代码。

  1. SavedRequestAwareAuthenticationSuccessHandler中的 onAuthenticationSuccess方法
    @Override
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        String MY_REQUEST_CACHE = "MY_REQUEST_CACHE";
        // 从当前request中获取session查看是否有登陆前的请求
        SavedRequest savedRequest = requestCache.getRequest(request, response); // SpringSecurity缓存的请求
        savedRequest = savedRequest == null ? (SavedRequest) request.getSession().getAttribute(MY_REQUEST_CACHE) : savedRequest;// 自行换粗的请求
        if (savedRequest == null) { // 如果没有缓存请求直接返回认证成功的JSON数据
            HttpServletUtils.writeResponseData(response, Result.ok(createToken(authentication)));
        }
        else { // 根据缓存的request进行重定向,此处先再当前request的session存储一次
            request.getSession().setAttribute("SPRING_SECURITY_SAVED_REQUEST",savedRequest);
            requestCache.saveRequest(request,response);
    		super.onAuthenticationSuccess(request, response, authentication);
        }
    
  2. BasicAuthenticationFilter 中的 onUnsuccessfulAuthentication 方法
    @Override
    protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
        String MY_REQUEST_CACHE = "MY_REQUEST_CACHE";
        if(request.getSession().getAttribute(MY_REQUEST_CACHE) == null) {
            PortResolver portResolver = new PortResolverImpl();
            DefaultSavedRequest savedRequest = new DefaultSavedRequest(request,portResolver);
            request.getSession().setAttribute(MY_REQUEST_CACHE,savedRequest);
        }
        requestCache.saveRequest(request,response);
        super.onUnsuccessfulAuthentication(request, response, failed);
    }
    

代码到这个阶段就解决了session不一致导致的无法获取登陆前的请求,但是当重定向到登陆前请求时,重定向的请求本就是通过gateway转发的请求,因此当前重定向请求仍然没有在请求头中携带有用户登录的信息,导致又重定向至登录页。因此下一步要解决的是如何再重定向后使得重定向请求能够携带当前用户的认证信息。

重定向后无法从SecurityContextHolder获得身份信息的解决方案

我使用的是JWT token,因此只需要重定向请求中能够携带 成功用户登录后获得的 accessToken 即可。因此需要修改的代码如下:

  1. SavedRequestAwareAuthenticationSuccessHandler中的 onAuthenticationSuccess方法
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
       String MY_REQUEST_CACHE = "MY_REQUEST_CACHE";
       // 从当前request中获取session查看是否有登陆前的请求
       SavedRequest savedRequest = requestCache.getRequest(request, response); // SpringSecurity缓存的请求
       savedRequest = savedRequest == null ? (SavedRequest) request.getSession().getAttribute(MY_REQUEST_CACHE) : savedRequest;// 自行换粗的请求
       if (savedRequest == null) { // 如果没有缓存请求直接返回认证成功的JSON数据
           HttpServletUtils.writeResponseData(response, Result.ok(createToken(authentication)));
       }
       else { // 根据缓存的request进行重定向
           String targetUrlParameter = getTargetUrlParameter();
           if (isAlwaysUseDefaultTargetUrl()
                   || (targetUrlParameter != null && StringUtils.hasText(request
                   .getParameter(targetUrlParameter)))) {
               requestCache.removeRequest(request, response);
               super.onAuthenticationSuccess(request, response, authentication);
               return;
           }
           clearAuthenticationAttributes(request);
           // 将当前用户的身份信息拼接到路径中
           UserAccessToken token = createToken(authentication);
           String accessToken = token.getAccessToken();
           String targetUrl = savedRequest.getRedirectUrl();
           targetUrl += "&DAKIN_OAUTH_TOKEN=" + accessToken;
           logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
           getRedirectStrategy().sendRedirect(request, response, targetUrl);
       }
    }
    
  2. BasicAuthenticationFilter 中的 doFilterInternal 方法

    这个方法大家写的各不相同,因此不细写了,没有参考价值。关键在于判断用户登录状态时增加从request.getParameter()中获得token

结语

没啥好结的。

你可能感兴趣的:(OAuth认证中心,spring,cloud,gateway,java)