Thursday, December 26, 2024

Use OpenShift OAuth server as login option for the SpringBoot Application

OpenShift is a kubernetes cluster with many addition features that are production ready. One such feature is an authorization server, which can be used to authenticate ourself using OAuth2 protocol.

If we have an application deployed in the cluster, then we can use the OpenShift oauth server(authorization server) as a login option for that application. 

So, lets go through the steps that are required to secure our SpringBoot application, using the OpenShift oauth server.

This authorization workflow we gonna use can be better understood by referring the guide rfc6749#section-4.

Moreover, SpringBoot project explained in the spring-boot-oauth2 guide can be used as a starting point to configure the application with OpenShift oauth server, where we will use OpenShift instead GitHub.

1. Obtain OpenShift OAuth Server Properties

First thing we require is the oauth server connection properties, to get them, as explain in the oauth server metadata documentation, a GET request to the endpoint https://openshift.default.svc/.well-known/oauth-authorization-server should be made from within the cluster.

The API would return a payload similar to this

{
  "issuer": "https://<namespace_route>", 
  "authorization_endpoint": "https://<namespace_route>/oauth/authorize", 
  "token_endpoint": "https://<namespace_route>/oauth/token", 
  "scopes_supported": [ 
    "user:full",
    "user:info",
    "user:check-access",
    "user:list-scoped-projects",
    "user:list-projects"
  ],
  "response_types_supported": [ 
    "code",
    "token"
  ],
  "grant_types_supported": [ 
    "authorization_code",
    "implicit"
  ],
  "code_challenge_methods_supported": [ 
    "plain",
    "S256"
  ]
}

where, we will use authorization_endpointtoken_endpoint and optionally scopes from the SpringBoot application for authentication.

Note that oauth server doesn't provide a user info endpoint, which requires to fetch user information, such as username, roles, etc, upon a successful login. Later in the guide an alternative way is mentioned to fetch these information.

2. Register SpringBoot Application as a Client Application

For the oauth server to allow our application as client application to initiate the login request, it should be registered as client application in oauth server.

For that, openshift provides a kubernetes CRD (custom resource definition) OAuthClient, which should be used to register our client with the oauth server as described in registering an additional OAuth client guide.

Pay attention to following properties while registering a client

  • metadata.name
  • secret
  • redirectURIs

where, metadata.name used as the client-id and secret used as client-secret (this should be treated as a password).

Value given for redirectURI will be used to redirect the request when the login is success in oauth server side for further processing the authentication by the client, therefore it should be uri supported by the client, in our case SpringBoot.

By default, SpringBoot uses following redirect template, {baseUrl}/login/oauth2/code/{registrationId}, where the registrationId is what comes under property spring.security.oauth2.client.registration.

Therefore if we use id as openshift then redirect uri becomes {baseUrl}/login/oauth2/code/openshift. Note: remember to replace the {baseUrl} with actual value.

With all that, for a sample demo app deployed under https://demo-app, an OAuthClient resource would look like this

kind: OAuthClient
apiVersion: oauth.openshift.io/v1
metadata:
  name: demo-app
secret: demo-app-secret
redirectURIs:
  - "https://demo-app/login/oauth2/code/openshift"
grantMethod: prompt

Note the grantMethod, prompt means that user need to explicitly approve the scope when login is performed by oauth server, another possible value for that field is auto, in that case no explicit approval required.

Resource is registered in cluster level, therefore, the one who register it should have cluster wide permission to create this resource.

3. Configure SpringBoot Application

Once a client is registerd in oauth server using OAuthClient resource, its properties can be used to register a oauth2 client configuration from SpringBoot app.

For the above OAuthClient configuration, following SpringBoot configuration should be performed. 

spring:
  security:
    oauth2:
      client:
        registration:
          openshift:
            authorization-grant-type: authorization_code
	    client-name: Demo App
            client-id: demo-app
            client-secret: demo-app-secret
            provider: openshift
            redirect-uri: https://demo-app/login/oauth2/code/openshift
            scope: user:info
        provider:
          openshift:
            authorization-uri: <openshift-oauth-server-host>/oauth/authorize
            token-uri: <openshift-oauth-server-host>/oauth/token

where authorizatio-uri and token-uri should be replaced with valued from the response we got for the GET request made for oauth server .well-known/oauth-authorization-server endpoint.

With that we should be able to trigger a login using the path /oauth2/authorization/openshift, which will then redirect the login request to OpenShift oauth server and redirect the request back to app when login success in oauth server side. 

Note that the default template for the login url comes as the /oauth2/authorization/{registrationId} 

4. Fetch User Information

You might have noticed from the last step, even though the login success from oauth server side, after a redirect to the client app, it fails to authenticate further. This is because Spring security now needs user information to set up an authentication (principal). Therefore, some additional configs required to fetch these user information from spring security perspective.

As part of oauth2 workflow, user information is fetched though the uri configured in user-info-uri and user-name-attribute, however, OpenShift oauth server doesn't provide a way to fetch this user information, instead we shall use the OpenShift user api to fetch these information by using the authorization_code we received from oauth server.

OpenShift provide following user apis, among those, we can use GET /apis/user.openshift.io/v1/users/~. Mind the ~ in the end of path, it implies fetching the current logged in user, for the authorization code we pass with the GET request.

The API returns JSON response with the structure explained in the specification. Where username can be obtained from metadata.name field.

With that SpringBoot application need two more additional configs to the openshift provider configuration, those are 

spring:
  security:
    oauth2:
      client:
        provider:
          openshift:
            user-info-uri: <openshift-api-host>/apis/user.openshift.io/v1/users/~
            user-name-attribute: username

Note that we have configured username as the field name to look for username in the response, however, no such field available as explained in the specification. The username actually resides in nested field metadata.name. Therefore, this value should be flatten in spring security side, for it to be able to fetch the value and construct the authentication object.

Spring security provides a way to flatten fields for easy access through the DefaultOAuth2UserService#setAttributesConverter method.

Customization of the user service in kotlin would look similar to this.

http {
  oauth2Login {
    userInfoEndpoint {
      userService = DefaultOAuth2UserService().apply {
        setAttributesConverter { _ ->
          Converter {
            val username = (it["metadata"] as? Map<*, *>)?.get("name") as? String
            if (username != null) {
              it["username"] = username
            }
            it
          }
        }
      }
    }
  }
}

Now we have flatten metada.name field value into username field, that can be accessed by spring security.

With all that, we should be able to use the OpenShift oauth for the authentication of our SpringBoot application upon triggering the login.


No comments:

Post a Comment