使用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";
}
}
感谢shobull指向我Justin Taylor's answer这个问题。为了完整起见,我想在这里提供完整的答案。这是一个两个部分的解决方案:
- 让春季会议上提交急切地 - 因为弹簧会议V1.0有注释属性
@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE)
这立即保存会话数据为Redis的。文档here。添加到会话当前请求的头 -
简单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随时更改属性。
我想你的字面意思是“复制”RedisOperationsSessionRepository,因为RedisSession不能被覆盖?另外,您尝试链接到saveDelta的行号不存在。我假设你的意思是在setAttribute()方法的末尾调用saveDelta?然后,你是否注册了RedisOperationsSessionRepository类型的bean来覆盖默认值? –
嗨安德鲁,你的问题的解决方案是在另一个问题描述http://stackoverflow.com/questions/34751700/spring-zuul-api-gateway-with-spring-session-redis-authenticate-and-route-in -sa?rq = 1#answer-38867506 – shobull
太棒了!它似乎在工作!我已经有一半的解决方案,并认为我已经尝试了第二部分,但我想现在不会导致它的工作!再次感谢您指出这一点! –