Spring zuul代理不接受不记名令牌

Posted

技术标签:

【中文标题】Spring zuul代理不接受不记名令牌【英文标题】:Spring zuul proxy not accepting bearer token 【发布时间】:2016-05-22 12:45:03 【问题描述】:

我有一个 zuul 代理 (http://localhost:8765) 为 Angular Web 应用程序 (http://localhost:8080/app) 提供服务。 zuul代理后面还有一个oauth2服务器(http://localhost:8899)。

网络资源在http://localhost:8765/web 下代理,资源在http://localhost:8765/api 下代理。 Zuul 代理无需身份验证即可提供静态 Web 资源。因此,第一次身份验证是通过 JSON 调用 (GET /api/user) 完成的,这当然会以 401 失败。

现在我将页面转发到“http://localhost:8899/uaa/oauth/authorize?response_type=token&client_id=web&redirect_uri=http://localhost:8765/web/index.html”以进行隐式授权 oauth 2 流。我现在可以授权 Web 应用程序并转发回我的 Web 应用程序。令牌是 url 的一部分,我可以解析它。

恕我直言,我现在唯一要做的就是将此令牌添加为授权标头(例如 Authorization:Bearer 2829d5e2-4fbe-4f91-b74d-c99b2fe894a7)。但是zuul代理不会接受这个授权的请求。

我正在使用 spring boot 1.3.2 和 spring cloud Brixton.M4。 Zuul 服务器应用程序可以在here 和安全配置here 找到。

这是我的请求标头:

Accept:application/json
Accept-Encoding:gzip, deflate, sdch
Accept-Language:de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
Authorization:Bearer 2829d5e2-4fbe-4f91-b74d-c99b2fe894a7
Connection:keep-alive
Cookie:XSRF-TOKEN=a6ddea36-e3b7-4f22-b80c-b4c8b6fd7760; JSESSIONID=DAE4649D11386D586A0CF739148E505A; XSRF-TOKEN=3a7a57ad-68f6-4cc6-923b-4e8fe340fe1e
Host:localhost:8765
Referer:http://localhost:8765/web/index.html
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/48.0.2564.82 Chrome/48.0.2564.82 Safari/537.36
X-Auth-Token:2829d5e2-4fbe-4f91-b74d-c99b2fe894a7
X-Requested-With:XMLHttpRequest
X-XSRF-TOKEN:a6ddea36-e3b7-4f22-b80c-b4c8b6fd7760

我的Zuul配置是:

server:
  context-path: /

security:
  user:
    password: none
  oauth2:
    sso:
        loginPath: /login
    client:
      accessTokenUri: $authserver.protocol://$authserver.hostname:$authserver.port/$authserver.contextPath/oauth/token
      userAuthorizationUri: $authserver.protocol://$authserver.hostname:$authserver.port/$authserver.contextPath/oauth/authorize
      clientId: web
    resource:
      userInfoUri: $authserver.protocol://$authserver.hostname:$authserver.port/$authserver.contextPath/user
      preferTokenInfo: false

zuul:
  routes:
    web-portal:
      path: /web/**
      url: http://localhost:8080/app
    user:
      path: /api/user/**
      url: $authserver.protocol://$authserver.hostname:$authserver.port/$authserver.contextPath/user
    authentication-service:
      path: /uaa/**
      stripPrefix: false

---
spring:
  profiles: local

logging:
  level:
     org:
       springframework:
         security: DEBUG

authserver:
  protocol: http
  hostname: localhost
  port: 8899
  contextPath: uaa

zuul服务器日志为:

2016-02-11 17:11:02.958 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request '/api/user' matched by universal pattern '/**'
2016-02-11 17:11:02.958 DEBUG 3242 --- [nio-8765-exec-4] o.s.security.web.FilterChainProxy        : /api/user at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2016-02-11 17:11:02.958 DEBUG 3242 --- [nio-8765-exec-4] o.s.security.web.FilterChainProxy        : /api/user at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2016-02-11 17:11:02.958 DEBUG 3242 --- [nio-8765-exec-4] w.c.HttpSessionSecurityContextRepository : HttpSession returned null object for SPRING_SECURITY_CONTEXT
2016-02-11 17:11:02.958 DEBUG 3242 --- [nio-8765-exec-4] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@5571734d. A new one will be created.
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.security.web.FilterChainProxy        : /api/user at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@42c144ce
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.security.web.FilterChainProxy        : /api/user at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.util.matcher.AndRequestMatcher   : Trying to match using org.springframework.security.web.csrf.CsrfFilter$DefaultRequiresCsrfMatcher@4ad95822
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.util.matcher.AndRequestMatcher   : Did not match
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.security.web.FilterChainProxy        : /api/user at position 5 of 13 in additional filter chain; firing Filter: ''
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.security.web.FilterChainProxy        : /api/user at position 6 of 13 in additional filter chain; firing Filter: 'LogoutFilter'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request 'GET /api/user' doesn't match 'POST /logout
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.security.web.FilterChainProxy        : /api/user at position 7 of 13 in additional filter chain; firing Filter: 'OAuth2ClientAuthenticationProcessingFilter'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/user'; against '/login'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.security.web.FilterChainProxy        : /api/user at position 8 of 13 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.security.web.FilterChainProxy        : /api/user at position 9 of 13 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.security.web.FilterChainProxy        : /api/user at position 10 of 13 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.a.AnonymousAuthenticationFilter  : Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@905571d8: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: DAE4649D11386D586A0CF739148E505A; Granted Authorities: ROLE_ANONYMOUS'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.security.web.FilterChainProxy        : /api/user at position 11 of 13 in additional filter chain; firing Filter: 'SessionManagementFilter'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.security.web.FilterChainProxy        : /api/user at position 12 of 13 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.security.web.FilterChainProxy        : /api/user at position 13 of 13 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/user'; against '/index.html'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/user'; against '/home.html'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/user'; against '/web/**'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/user'; against '/uaa/oauth/**'
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.a.i.FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /api/user; Attributes: [authenticated]
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.a.i.FilterSecurityInterceptor    : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@905571d8: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: DAE4649D11386D586A0CF739148E505A; Granted Authorities: ROLE_ANONYMOUS
2016-02-11 17:11:02.959 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@77816ac4, returned: -1
2016-02-11 17:11:02.960 DEBUG 3242 --- [nio-8765-exec-4] o.s.s.w.a.ExceptionTranslationFilter     : Access is denied (user is anonymous); redirecting to authentication entry point

org.springframework.security.access.AccessDeniedException: Access is denied

如何针对访问令牌强制对 zuul 代理进行身份验证?

--- 编辑: 如果我通过删除它的 http 安全异常来启用静态 Web 资源的身份验证,我会被转发到授权页面。当请求被转发回来时,一切正常。 zuul 代理以其/login URL 作为返回地址转发到oauth 服务器。这似乎是正确的方法。我想它会在会话中保存一些信息,然后转发回初始请求页面(在我的情况下为 /web/index.html)。

当我现在重新启动身份验证服务(如模拟过期令牌)时,会提供来自 Web 应用程序的资源,但对 /api/user(代理到身份验证服务器)的请求被拒绝。

当我来自手动构建的授权 URL 时,也会发生同样的情况 http://localhost:8899/uaa/oauth/authorize?response_type=token&client_id=web&redirect_uri=http://localhost:8765/web/index.html。首先我进入 oauth 服务器的授权页面。这是对的。单击授权后,请求将转发到 Web 应用程序 (/web/index.html)。提供所有静态内容都没有问题,但对/api/user 的访问再次被拒绝。这次在 oauth 服务器中记录了一个错误:无效的访问令牌:dff5121b-06e4-4bd7-b48e-08ad82d71404

【问题讨论】:

如果要代理做资源服务器,为什么要配置security.oauth2.sso.*?您应该可以只使用@EnableResourceServer 而不要触摸 SSO 的东西。 @Dave:感谢您的提示,刚刚删除了这个 sso 配置。关于资源服务器注释:我试图根据您的教程构建它,没有@EnableResourceServer。资源服务器注释在我的 oauth 服务器 /user 控制器上。当然试过了,可惜没用。 “没用”信息量不大。也许您可以发布一个指向您的代码的链接? 是的,当然。将最新更改推送到 github。您可以在github.com/thomasletsch/moserp 下查看所有代码。 zuul代理在github.com/thomasletsch/moserp/tree/master/backend/…@DaveSyer:感谢大家的支持。我通过切换到@EnableResourceServer 再次开始尝试您建议的解决方案。你是对的,这是要走的路!它现在按预期工作!或许您可以回答我最后一个问题:我使用代理服务器的方式是否值得推荐?到目前为止,我看到的所有示例都使用 SSO。 【参考方案1】:

zuul api 默认不转发 header 所以禁用它我们需要添加它

zuul:
  sensitive-headers: Cookie,Set-Cookie

【讨论】:

我的具体问题是 Zuul 没有传递 Authorization 标头来验证用户以向后端服务发出请求。大概是因为默认情况下这些标头被排除在外。这为我解决了问题。【参考方案2】:

您应该迁移到 Spring Boot 1.3x。

然后,您可以使用 @EnableOAuath2Sso 注释来注释您的 Zulu 代理。 在 Zuul 的 application.yml 中,指定以下内容(对于 Spring Boot 1.3x):

security:
   user:
     password: none
   oauth2:
     client:
       accessTokenUri: $oauthserver:$oauthport/oauth/token
       userAuthorizationUri: $oauthserver:$oauthport/oauth/authorize
       clientId: acme
       clientSecret: acme secret

【讨论】:

其实我用的是spring 1.3。我将相应地编辑我的问题。 是否可以通过zuul代理auth服务器?创建到 auth 服务器的 zuul 路由,然后在 oauth2:client: 你建议的配置中使用它? 是的,这很可能使用以下方法: code@SpringBootApplication @EnableEurekaClient @EnableOAuth2Client @EnableZuulProxy @EnableOAuth2Sso【参考方案3】:

感谢@Dave,我可以回答我自己的问题: 要让 Zuul 代理接受 OAuth Bearer Token 标头,您必须将其配置为资源服务器而不是 SSO 服务器。 删除@EnableOAuth2Sso 注释并改用@EnableResourceServer 注释。首先我还有我的WebSecurityConfigurerAdapter。所以这是通过改变来解决的

@Configuration
@EnableOAuth2Sso
public class OAuthConfiguration extends WebSecurityConfigurerAdapter 

@Configuration
@EnableResourceServer
public class OAuthConfiguration extends ResourceServerConfigurerAdapter 

是否应该使用隐式身份验证是另一个主题(请参阅 Dave 的评论)。

【讨论】:

您是说客户端 UI 现在使用 SSO 进行了注释吗?如果 Zuul 是资源,现在谁扮演 SSO 客户端...【参考方案4】:

默认情况下,Zuul 代理将删除某些标头,因为它认为它们是敏感的,其他服务器不应接收它们。它有一个需要删除的 cookie 和标头列表。这需要被覆盖。对特定路线执行此操作的方法是:

zuul:
  routes:
    profile-service-chaining:
      sensitiveHeaders:
      stripPrefix: false
      serviceId: profile-service-chaining
      path: /services/profiles

“sensitiveHeaders:”行将清空 cookie 标头。这将导致所有标头都传递到请求将发送到的服务器。这将允许将 Authorization 标头发送到目标服务器。正如 Zuul 文档所说,确保始终删除敏感标头。为此,请添加:

sensitiveHeaders: Cookie

上面会在 Cookie 头被传递到 Zuul 路由指定的下一个服务器之前删除它。

【讨论】:

以上是关于Spring zuul代理不接受不记名令牌的主要内容,如果未能解决你的问题,请参考以下文章

Zuul代理oAuth2在Spring Boot中未经授权

通过提供代理令牌处理来自 webapi 的长不记名令牌

为啥带有 Netflix Zuul 反向代理的 Keycloak OAUTH2 不传递令牌

如何为 Okta 自动刷新不记名令牌

Zuul 代理和 Spring OAuth 重定向问题

为社交登录用户生成不记名令牌 Spring Boot