2015-09-25 107 views
5

我試圖使用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

太棒了!它似乎在工作!我已經有一半的解決方案,並認爲我已經嘗試了第二部分,但我想現在不會導致它的工作!再次感謝您指出這一點! –

回答

2

感謝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代理之內。

0

春季會議上支持該請求被提交時當前寫入數據存儲。這是爲了儘量通過一次寫入所有屬性來減少「聊天流量」。

這是公認的,這是不理想的一些情況下(如你正在面臨的)。對於這些我們有spring-session/issues/250。解決方法是複製RedisOperationsSessionRepository並調用saveDelta隨時更改屬性。

+0

我想你的字面意思是「複製」RedisOperationsSessionRepository,因爲RedisSession不能被覆蓋?另外,您嘗試鏈接到saveDelta的行號不存在。我假設你的意思是在setAttribute()方法的末尾調用saveDelta?然後,你是否註冊了RedisOperationsSessionRepository類型的bean來覆蓋默認值? –