OAuth2授权码流程:spring-security不接受下发access_token

OAuth2 authorization code flow: spring-security does not accept the issued access_token

我正在学习 OAuth2 授权代码流程。

我认为我的授权服务器配置是正确的,因为当我模拟与 CURL 的通信时,AS 在最后产生了 access_token。 但不知何故 Spring-Security 不想接受已颁发和验证的访问令牌。所以我认为我的Spring-安全配置不正确。

我尝试以多种不同方式配置 Spring-Security,但不幸的是,其中 none 有效。 也许我需要实施我使用 Spring-Security 使用 CURL 执行的步骤,但也许我只是错过了配置行。

这是我的 CURL 链的最后一步,其中 AS 给我访问令牌(将授权代码交换为访问令牌):

url="$authServerHost/openam/oauth2/realms/root/access_token"

curl \
  --silent \
  --dump-header - \
  --insecur \
  --request POST \
  --data "grant_type=authorization_code" \
  --data "code=$authorizationCode" \
  --data "client_id=$clientId" \
  --data "client_secret=$clientSecret" \
  --data "redirect_uri=$redirectUri" \
  "$url"

-------- response ---------
HTTP/1.1 200 
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Cache-Control: no-store
Pragma: no-cache
Content-Type: application/json;charset=UTF-8
Content-Length: 157
Date: Wed, 29 Sep 2021 17:57:30 GMT

{
    "access_token":"2SiD3moh2iql5j3ocdPOR-W4QRE",
    "refresh_token":"zWSG-fi1J9hUrY0Tw6GHeXnndgM",
    "scope":"public_profile",
    "token_type":"Bearer",
    "expires_in":3599
}

这是令牌的探测(验证和检索有关令牌的信息):

url="$authServerHost/openam/oauth2/tokeninfo"
curl \
  --silent \
  --dump-header - \
  --insecur \
  --request GET \
  --header "Authorization: Bearer $accessToken" \
  "$url"

-------- response ---------
HTTP/1.1 200 
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Cache-Control: no-store
Pragma: no-cache
Content-Type: application/json;charset=UTF-8
Content-Length: 326
Date: Wed, 29 Sep 2021 17:57:30 GMT

{
   "access_token":"2SiD3moh2iql5j3ocdPOR-W4QRE",
   "grant_type":"authorization_code",
   "auth_level":0,
   "auditTrackingId":"45f24ab1-f9a4-43df-bb17-4b4d6c0ffee4-112239",
   "scope":["public_profile"],
   "public_profile":"",
   "realm":"/",
   "token_type":"Bearer",
   "expires_in":3599,
   "authGrantId":"2tg__erKRo_utv4Py_TOt1NOtDo",
   "client_id":"hello-web"
}

然后我尝试使用此 CURL 获取受保护的内容,但 Spring-Security 再次将我重定向到 Spring Security 的提供商选择页面:

url="https://web.example.com:8444/user.html"
curl \
  --silent \
  --insecur \
  --dump-header - \
  --request GET \
  --header "Authorization: Bearer $accessToken" \
  "$url"

-------- response ---------
HTTP/1.1 302 
Cache-Control: private
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
Location: https://web.example.com:8444/oauth2/authorization/openam
Content-Length: 0
Date: Wed, 29 Sep 2021 18:18:40 GMT

如您所见,我的网络应用 Spring-Security 不接受有效的承载令牌并将请求重定向到授权服务器,尽管我提供了令牌。

这是我的Spring-安全配置:

spring:
  security:
    oauth2:
      client:
        registration:
          openam:
            client-id: hello-web
            client-secret: client-secret
            authorization-grant-type: authorization_code
            redirect-uri: https://web.example.com:8444/user.html
            scope: public_profile
        provider:
          openam:
            token-uri: https://openam.example.com:8443/openam/oauth2/access_token
            authorization-uri: https://openam.example.com:8443/openam/oauth2/authorize

以及我使用的 Spring- 安全配置:

@EnableWebSecurity
public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(authorizeRequests -> authorizeRequests
                        .antMatchers("/").permitAll()
                        .anyRequest().authenticated()
                )
                .oauth2Login(withDefaults());
    }
}

我错过了什么? 你能指导我正确的方向吗?

----> 附加信息<----

当我在网络浏览器中打开受保护的页面https://web.example.com:8444/user.html时,

  1. 我被正确重定向到授权服务器登录页面。
  2. 然后我登录。
  3. 然后同意批准表出现在我授予访问“public_profile”范围的地方
  4. 然后 Spring 再次将我重定向到登录页面(第 2 步)。

我认为发生这种情况是因为 Spring 只是不想接受颁发的访问令牌。

Spring 日志:

open http://web.example.com:8081/user.html

o.s.security.web.FilterChainProxy        : Securing GET /user.html
s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
o.s.s.w.a.i.FilterSecurityInterceptor    : Failed to authorize filter invocation [GET /user.html] with attributes [authenticated]
o.s.s.w.s.HttpSessionRequestCache        : Saved request https://web.example.com:8444/user.html to session
s.w.a.DelegatingAuthenticationEntryPoint : Trying to match using And [Not [RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], Not [And [Or [Ant [pattern='/login'], Ant [pattern='/favicon.ico']], And [Not [RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f2273f8, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]]]]]
s.w.a.DelegatingAuthenticationEntryPoint : Match found! Executing org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint@23fec83b
o.s.s.web.DefaultRedirectStrategy        : Redirecting to https://web.example.com:8444/oauth2/authorization/openam
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
o.s.security.web.FilterChainProxy        : Securing GET /oauth2/authorization/openam
s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
o.s.s.web.DefaultRedirectStrategy        : Redirecting to https://openam.example.com:8443/openam/oauth2/authorize?response_type=code&client_id=example-web&scope=public_profile&state=HYbQUtdrCuQ5dKUtGI6bBoBbLvScCoELGcundKpNGoI%3D&redirect_uri=https://web.example.com:8444/user.html
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request

then the Authorization Server's login page appears
then I allow access to the "public_profile" scope

then this happens on the Spring side:

o.s.security.web.FilterChainProxy        : Securing GET /user.html?code=1aC6OC44B03k37pACmmD8n-Ol38&iss=https%3A%2F%2Fopenam.example.com%3A8443%2Fopenam%2Foauth2&state=HYbQUtdrCuQ5dKUtGI6bBoBbLvScCoELGcundKpNGoI%3D&client_id=example-web
s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
o.s.s.w.a.i.FilterSecurityInterceptor    : Failed to authorize filter invocation [GET /user.html?code=1aC6OC44B03k37pACmmD8n-Ol38&iss=https%3A%2F%2Fopenam.example.com%3A8443%2Fopenam%2Foauth2&state=HYbQUtdrCuQ5dKUtGI6bBoBbLvScCoELGcundKpNGoI%3D&client_id=example-web] with attributes [authenticated]
o.s.s.w.s.HttpSessionRequestCache        : Saved request https://web.example.com:8444/user.html?code=1aC6OC44B03k37pACmmD8n-Ol38&iss=https%3A%2F%2Fopenam.example.com%3A8443%2Fopenam%2Foauth2&state=HYbQUtdrCuQ5dKUtGI6bBoBbLvScCoELGcundKpNGoI%3D&client_id=example-web to session
s.w.a.DelegatingAuthenticationEntryPoint : Trying to match using And [Not [RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], Not [And [Or [Ant [pattern='/login'], Ant [pattern='/favicon.ico']], And [Not [RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]], MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@6f2273f8, matchingMediaTypes=[application/xhtml+xml, image/*, text/html, text/plain], useEquals=false, ignoredMediaTypes=[*/*]]]]]]
s.w.a.DelegatingAuthenticationEntryPoint : Match found! Executing org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint@23fec83b
o.s.s.web.DefaultRedirectStrategy        : Redirecting to https://web.example.com:8444/oauth2/authorization/openam
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
o.s.security.web.FilterChainProxy        : Securing GET /oauth2/authorization/openam
s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
o.s.s.web.DefaultRedirectStrategy        : Redirecting to https://openam.example.com:8443/openam/oauth2/authorize?response_type=code&client_id=example-web&scope=public_profile&state=SRHzhN7krlfUhJ50m9lsvaWgLUHglgMOA0wMZHrAmYo%3D&redirect_uri=https://web.example.com:8444/user.html
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request

我注意到您共享的代码中存在两个问题。

首先,您可能混淆了 OAuth 2.0 资源服务器和 OAuth 2.0 客户端。
web.example.com:8444 上的应用程序 运行 配置为 OAuth 2.0 客户端。
但是,您正在向 web.example.com:8444 发出请求,提供不记名令牌并请求资源。
客户端不会验证不记名令牌。在这种情况下,您似乎将应用程序视为资源服务器。

如果您要创建资源服务器应用程序,可以在 Spring 安全参考中查看完整的 documentation

第二个问题是您描述的在浏览器中访问客户端时的行为。
这里的问题是自定义 redirect-uri: https://web.example.com:8444/user.html.

执行此操作时,您将覆盖默认的重定向 URI,即 /login/oauth2/callback/{registrationId}

这个 URI 很特殊,因为它提示 OAuth2LoginAuthenticationFilter 处理请求,尝试对用户进行身份验证并创建 OAuth2AuthenticationToken

自定义重定向 URI 时,不会调用 OAuth2LoginAuthenticationFilter,应用程序不知道用户是否已通过身份验证。