使用Spring Boot,Session和Redis创建会话时不会复制会话

问题描述:

我试图使用Spring Cloud的Zuul,Eureka和我自己的服务来实现微服务架构。我有多个具有UI和服务的服务,并且每个服务都可以使用x509安全性对用户进行身份验证。现在我试图把祖鲁放在这些服务之前。由于Zuul无法将客户端证书转发到后端,因此我认为下一个最好的做法是在Zuul的前门验证用户,然后使用Spring Session在后端服务中复制其身份验证状态。我遵循Dave Syer的教程here,它几乎可以工作,但不是第一次请求。这是我的基本设置:使用Spring Boot,Session和Redis创建会话时不会复制会话

  • Zuul代理在它自己的应用程序集中路由到后端服务。是否已启用Spring安全性来执行x509身份验证。成功授权用户。还有Spring会话与@EnableRedisHttpSession
  • 后端服务也启用弹簧安全。我已经尝试在这里启用/禁用x509,但始终要求用户对特定端点进行身份验证。还使用Spring会话和@EnableRedisHttpSession。

如果清除所有会话并重新开始,并尝试打代理,然后将其发送使用zuul服务器证书的请求传递给后台。后端服务然后根据该用户证书查找用户,并认为用户是服务器,而不是在Zuul代理中进行身份验证的用户。如果您只是刷新页面,那么您突然成为后端的正确用户(在Zuul代理中进行身份验证的用户)。我检查的方式是在后端控制器中打印Principal用户。所以在第一次请求时,我看到服务器用户,并在第二次请求时看到真实用户。如果我在后台禁用x509,在第一次请求时,我得到一个403,然后在刷新时,它让我进入。

看起来好像会话没有被快速复制到后端,所以当用户在前端进行身份验证时,在Zuul转发请求之前,它并未将其发送到后端。

有没有办法保证会话在第一个请求(即会话创建)上被复制?或者我错过了一个步骤来确保它能正常工作?

下面是一些重要的代码片段:

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"; 
    } 
} 
+1

嗨安德鲁,你的问题的解决方案是在另一个问题描述http://stackoverflow.com/questions/34751700/spring-zuul-api-gateway-with-spring-session-redis-authenticate-and-route-in -sa?rq = 1#answer-38867506 – shobull

+0

太棒了!它似乎在工作!我已经有一半的解决方案,并认为我已经尝试了第二部分,但我想现在不会导致它的工作!再次感谢您指出这一点! –

感谢shobull指向我Justin Taylor's answer这个问题。为了完整起见,我想在这里提供完整的答案。这是一个两个部分的解决方案:

  1. 让春季会议上提交急切地 - 因为弹簧会议V1.0有注释属性@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE)这立即保存会话数据为Redis的。文档here。添加到会话当前请求的头
  2. 简单Zuul过滤器:

    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代理之内。

春季会议上支持该请求被提交时当前写入数据存储。这是为了尽量通过一次写入所有属性来减少“聊天流量”。

这是公认的,这是不理想的一些情况下(如你正在面临的)。对于这些我们有spring-session/issues/250。解决方法是复制RedisOperationsSessionRepository并调用saveDelta随时更改属性。

+0

我想你的字面意思是“复制”RedisOperationsSessionRepository,因为RedisSession不能被覆盖?另外,您尝试链接到saveDelta的行号不存在。我假设你的意思是在setAttribute()方法的末尾调用saveDelta?然后,你是否注册了RedisOperationsSessionRepository类型的bean来覆盖默认值? –