1

I am learning the OAuth2 authorization code flow.

  • I have my own Authorization Server (AS) which is OpenAM 7.1.
  • The Client is a simple Spring-Boot web application with a static HTML page, I use Spring-Security to protect the HTML page and control the Oauth2 flow.

I think that my Authorization Server configuration is correct because AS produces the access_token at the end when I simulate the communication with CURL. But somehow Spring-Security does not want to accept the issued and validated access token. So I think that my Spring-Security configuration is not correct.

I tried to configure Spring-Security in many different ways, but unfortunately, none of them was working. Maybe I need to implement the steps that I execute with CURL with Spring-Security, but maybe I just missed a configuration line.

This is the last step of my CURL chain where AS gives me the access token (exchange the authorization code for an access token):

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
}

This is the probe of the token (validate and retrieve information about the token):

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"
}

Then I try to fetch the protected content with this CURL but Spring-Security redirects me again to the Spring Security's provider selection page:

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

As you can see my web-app with Spring-Security does not accept the valid bearer token and redirects the request to the Authorization Server despite I provided the token.

This is my Spring-Security configuration:

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

And the Spring-Security config I use:

@EnableWebSecurity
public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter {

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

What did I miss? Could you please guide me in the right direction?

----> ADDITIONAL INFO <----

When I open the protected page https://web.example.com:8444/user.html in the web browser, then

  1. I am redirected properly to the Authorization Server login page.
  2. Then I log in.
  3. Then the consent approval form appears where I give access to the "public_profile" scope
  4. Then Spring redirects me again to the login page (step 2).

I think that happens because Spring just does not want to accept the issued access token.

Spring log:

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
zappee
  • 20,148
  • 14
  • 73
  • 129
  • Maybe you should also "permitAll" to "/openam/oauth2/tokeninfo" – Kemal Kaplan Sep 29 '21 at 19:51
  • Spring (my webapp) runs on web.example.com, and my Authorization Server runs on openam.example.com, they are different hosts (/etc/hosts). I think that Spring does not see/protect this URL. – zappee Sep 29 '21 at 20:21
  • You are right, they are totally different servers. At some examples, it used like http.authorizeRequests().antMatchers("/").permitAll().anyRequest().authenticated().and().oauth2Login() , maybe this can work... – Kemal Kaplan Sep 29 '21 at 20:35
  • Unfortunately, your suggestion does not work. I think I use the same code except the default parameter: `oauth2Login(withDefaults())`. I guess that I need to write some custom code to handle the OpenAM's OAuth flow, but I am not sure about it. – zappee Sep 29 '21 at 20:53

1 Answers1

2

I notice two issues in the code you have shared.

The first is that you may be confusing an OAuth 2.0 resource server and an OAuth 2.0 client.
The application running on web.example.com:8444 is configured as an OAuth 2.0 client.
However, you are making a request to web.example.com:8444, providing a bearer token and asking for a resource.
The client will not validate the bearer token. In this scenario it seems like you are treating the application as if it were a resource server.

If you are looking to create a resource server application, you can see the full documentation in the Spring Security reference.

The second issue is the behaviour you described when accessing the client in the browser.
The problem here is customising redirect-uri: https://web.example.com:8444/user.html.

When doing this you override the default redirect URI, which is /login/oauth2/callback/{registrationId}.

This URI is special because it prompts the OAuth2LoginAuthenticationFilter to process the request, attempt to authenticate the user and create the OAuth2AuthenticationToken.

When you customise redirect URI, the OAuth2LoginAuthenticationFilter is not invoked and the application does not know if the user is authenticated.

  • Thanks for your answer. It helped a lot. I have gone throw the Spring-Security doc again, especially the [Oauth2 part](https://docs.spring.io/spring-security/site/docs/5.5.2/reference/html5/#oauth2). You were correct, my redirect URL was not correct in the Client app config. It seems that this is the correct one: `https://web.example.com:8444/login/oauth2/code/openam`. – zappee Oct 03 '21 at 22:52
  • 1
    My Spring-Boot Client app does not want to start without specifying this `redirect-uri` in the spring config file. I think that I need to specify this because I use my own Authentication Provider. If I use providers that are known by default by Spring-Security (Google, Okta, etc.) then do not need to specify this because they have default values Spring-Security. – zappee Oct 03 '21 at 22:55
  • After I updated the `redirect-uri` the browser test looks better. I accept the scope and then the oauth2 flow continues properly. Now I have another issue, but this is a different one. I think it is related to the self-sign certificate I use: `An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: I/O error on POST request for "https://openam.example.com:8443/openam/oauth2/access_token": PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target` This is a nice journey :) – zappee Oct 03 '21 at 23:03
  • About the 1st issue that you mentioned: I will build the resource server app soon to see whether it accepts the token or not. thx a lot – zappee Oct 03 '21 at 23:10