使用Spring Boot,Session和Redis创建会话时未复制会话 [英] Session not replicated on session creation with Spring Boot, Session, and Redis

查看:281
本文介绍了使用Spring Boot,Session和Redis创建会话时未复制会话的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用Spring Cloud的Zuul,Eureka和我自己的服务来实现微服务架构.我有多个具有UI和服务的服务,每个服务都可以使用x509安全性对用户进行身份验证.现在,我试图将Zuul放在这些服务的前面.由于Zuul无法将客户端证书转发到后端,所以我认为下一件最好的事情是在Zuul的前门对用户进行身份验证,然后使用Spring Session在后端服务之间复制其身份验证状态.我已按照教程此处,它几乎可以正常运行,但不是在第一个请求时.这是我的基本设置:

I'm attempting to implement a microservices architecture using Spring Cloud's Zuul, Eureka, and my own services. I have multiple services that have UIs and services and each can authenticate users using x509 security. Now I'm trying to place Zuul in front of those services. Since Zuul can't forward client certs to the backend, I thought the next best thing would be to authenticate the user at the front door in Zuul, then use Spring Session to replicate their authenticated state across the backend services. I have followed the tutorial here from Dave Syer and it almost works, but not on the first request. Here is my basic setup:

  • 其自身的应用程序中的设置为路由到后端服务.已启用Spring安全性以执行x509身份验证.成功验证用户.也有带有@EnableRedisHttpSession
  • 的Spring Session
  • 后端服务还启用了Spring安全性.我曾尝试在此处启用/禁用x509,但始终要求对特定端点进行身份验证.还使用Spring Session和@EnableRedisHttpSession.
  • Zuul Proxy in it's own application set to route to the backend services. Has Spring security enabled to do x509 auth. Successfully auths users. Also has Spring Session with @EnableRedisHttpSession
  • Backend service also has spring security enabled. I have tried both enabling/disabling x509 here but always requiring the user to be authenticated for specific endpoints. Also uses Spring Session and @EnableRedisHttpSession.

如果您清除所有会话并重新开始并尝试访问代理,则它将使用zuul服务器的证书将请求发送到后端.然后,后端服务根据该用户证书查找用户,并认为该用户是服务器,而不是在Zuul代理中经过身份验证的用户.如果您只是刷新页面,那么您突然成为后端的正确用户(该用户在Zuul代理中进行了身份验证).我正在检查的方法是在后端控制器中打印出Principal用户.因此,在第一个请求上,我看到了服务器用户,在第二个请求上,我看到了真实用户.如果在后端禁用x509,则在第一个请求时,我得到403,然后在刷新时,它允许我进入.

If you clear all the sessions and start fresh and try to hit the proxy, then it sends the request to the backend using the zuul server's certificate. The backend service then looks up the user based on that user cert and thinks the user is the server, not the user that was authenticated in the Zuul proxy. If you just refresh the page, then you suddenly become the correct user on the back end (the user authenticated in the Zuul proxy). The way I'm checking is to print out the Principal user in the backend controller. So on first request, I see the server user, and on second request, I see the real user. If I disable x509 on the back end, on the first request, I get a 403, then on refresh, it lets me in.

似乎会话没有足够快地复制到后端,因此当在前端对用户进行身份验证时,在Zuul转发请求时它尚未到达后端.

It seems like the session isn't replicated to the backend fast enough so when the user is authenticated in the frontend, it hasn't made it to the backend by the time Zuul forwards the request.

是否有办法确保在第一个请求(即会话创建)上复制会话?还是我错过了确保其正确运行的步骤?

Is there a way to guarantee the session is replicated on the first request (i.e. session creation)? Or am I missing a step to ensure this works correctly?

以下是一些重要的代码段:

Here are some of the important code snippets:

Zuul代理:

@SpringBootApplication
@Controller
@EnableAutoConfiguration
@EnableZuulProxy
@EnableRedisHttpSession
public class ZuulEdgeServer {
    public static void main(String[] args) {
        new SpringApplicationBuilder(ZuulEdgeServer.class).web(true).run(args);
    }
}

Zuul配置:

info:
  component: Zuul Server

endpoints:
  restart:
    enabled: true
  shutdown:
    enabled: true
  health:
    sensitive: false

zuul:
  routes:
    service1: /**

logging:
  level:
    ROOT: INFO
#    org.springframework.web: DEBUG
    net.acesinc: DEBUG

security.sessions: ALWAYS
server:
  port: 8443
  ssl:
      key-store: classpath:dev/localhost.jks
      key-store-password: thepassword
      keyStoreType: JKS
      keyAlias: localhost
      clientAuth: want
      trust-store: classpath:dev/localhost.jks

ribbon:
    IsSecure: true

后端服务:

@SpringBootApplication
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, ThymeleafAutoConfiguration.class, org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class })
@EnableEurekaClient
@EnableRedisHttpSession
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

后端服务配置:

spring.jmx.default-domain: ${spring.application.name}

server:
  port: 8444
  ssl:
      key-store: classpath:dev/localhost.jks
      key-store-password: thepassword
      keyStoreType: JKS
      keyAlias: localhost
      clientAuth: want
      trust-store: classpath:dev/localhost.jks

#Change the base url of all REST endpoints to be under /rest
spring.data.rest.base-uri: /rest

security.sessions: NEVER

logging:
  level:
    ROOT: INFO
#    org.springframework.web: INFO
#    org.springframework.security: DEBUG
    net.acesinc: DEBUG

eureka:
  instance: 
    nonSecurePortEnabled: false
    securePortEnabled: true
    securePort: ${server.port}
    homePageUrl: https://${eureka.instance.hostname}:${server.port}/
    secureVirtualHostName: ${spring.application.name}

后端控制器之一:

@Controller
public class SecureContent1Controller {
    private static final Logger log = LoggerFactory.getLogger(SecureContent1Controller.class);

    @RequestMapping(value = {"/secure1"}, method = RequestMethod.GET)
    @PreAuthorize("isAuthenticated()")
    public @ResponseBody String getHomepage(ModelMap model, Principal p) {
        log.debug("Secure Content for user [ " + p.getName() + " ]");
        model.addAttribute("pageName", "secure1");
        return "You are: [ " + p.getName() + " ] and here is your secure content: secure1";
    }
}

推荐答案

感谢shobull将我指向

Thanks to shobull for pointing me to Justin Taylor's answer to this problem. For completeness, I wanted to put the full answer here too. It's a two part solution:

  1. 使Spring Session积极提交-自spring-session v1.0起,就有注释属性@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE),可将会话数据立即保存到Redis中.文档此处.
  2. 简单的Zuul过滤器,用于将会话添加到当前请求的标头中:

  1. Make Spring Session commit eagerly - since spring-session v1.0 there is annotation property @EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE) which saves session data into Redis immediately. Documentation here.
  2. Simple Zuul filter for adding session into current request's header:

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.stereotype.Component;

@Component
public class SessionSavingZuulPreFilter extends ZuulFilter {
    @Autowired
    private SessionRepository repository;

    private static final Logger log = LoggerFactory.getLogger(SessionSavingZuulPreFilter.class);

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();

        HttpSession httpSession = context.getRequest().getSession();
        Session session = repository.getSession(httpSession.getId());

        context.addZuulRequestHeader("Cookie", "SESSION=" + httpSession.getId());

        log.trace("ZuulPreFilter session proxy: {}", session.getId());

        return null;
    }
}

这两个都应该在您的Zuul代理中.

Both of these should be within your Zuul Proxy.

这篇关于使用Spring Boot,Session和Redis创建会话时未复制会话的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆