OAuth2授权码流程:spring-security不接受下发access_token
OAuth2 authorization code flow: spring-security does not accept the issued access_token
我正在学习 OAuth2 授权代码流程。
- 我有自己的
Authorization Server
(AS),即 OpenAM 7.1。
Client
是一个简单的Spring-带有静态HTML页面的启动Web应用程序,我使用Spring-安全来保护HTML页面并控制 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
时,
- 我被正确重定向到授权服务器登录页面。
- 然后我登录。
- 然后同意批准表出现在我授予访问“public_profile”范围的地方
- 然后 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
,应用程序不知道用户是否已通过身份验证。
我正在学习 OAuth2 授权代码流程。
- 我有自己的
Authorization Server
(AS),即 OpenAM 7.1。 Client
是一个简单的Spring-带有静态HTML页面的启动Web应用程序,我使用Spring-安全来保护HTML页面并控制 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
时,
- 我被正确重定向到授权服务器登录页面。
- 然后我登录。
- 然后同意批准表出现在我授予访问“public_profile”范围的地方
- 然后 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
,应用程序不知道用户是否已通过身份验证。