OAuth2 protocol and Spring Security OAuth2 integration

The authorization protocol of OAuth 2.0 allows third-party applications to access restricted HTTP resources. As usual, when you use Github and Google account to log in to other systems, you use the OAuth 2.0 authorization framework. The following figure is the authorization page diagram of using Github account to log in to Coding system:

 

There are many other similar uses of OAuth 2.0 authorization. This article will introduce the concepts related to OAuth 2.0, such as role, authorization type and so on. Here is a head of OAuth 2.0 authorization that I sorted out, hoping to help you understand the OAuth 2.0 authorization protocol.

 

In this paper, we will expand the OAuth 2.0 protocol based on the content of brain map. Besides OAuth 2.0, we will also cooperate with Spring Security OAuth 2 to build the OAuth 2 client, which is also the purpose of learning OAuth 2.0, directly applied to the actual project, and deepen the understanding of OAuth 2.0 and Spring Security.

OAuth 2.0 role

There are four types of roles in OAuth 2.0: resource Owner, authorization service, client and resource service. These four roles are responsible for different work. To facilitate understanding, a flow chart is given first, and then the details are expanded respectively

OAuth 2.0 authorization process

 

Resource Owner

Resource Owner can be understood as a user. As mentioned earlier in the example of using GitHub to log in to Coding, when a user logs in to Coding using GitHub account, Coding needs to know the user's Avatar, user name, email and other information in GitHub system. These account information belong to the user, so it is not difficult to understand resource Owner. It's not that easy when Coding requests to get the desired user information from GitHub. For the sake of security, GitHub needs at least the consent of the user (resource Owner).

Resource server

After understanding the resource Owner, I believe you already know what the resource server is. In this example, the user account information is stored in the GitHub server, so the resource server here is the GitHub server. GitHub server is responsible for saving and protecting users' resources. Any other third-party system that wants to use these information needs to be authorized by the resource Owner and interact according to the authorization process of OAuth 2.0.

Client

After knowing the resource Owner and resource server, the client role in OAuth is relatively easy to understand. Simply speaking, the client is the system that wants to obtain resources. For example, when using GitHub to log in to Coding, Coding is the client in OAuth. The client is mainly responsible for initiating authorization request, obtaining AccessToken and obtaining user resources.

Licensing server

With resource Owner, resource server and client, if OAuth authorization cannot be completed, authorization server is also required. In OAuth, the authorization server is not only responsible for interaction with users (resource owners) and room end (Coding), but also for generating access token, verifying access token and other functions. It is a very important part of OAuth authorization. In the example, the authorization server is GitHub's server.

Summary

In OAuth, there are four roles: resource Owner, authorization service, client and resource service. In the example of using GitHub to log in to Coding, they are respectively shown as follows:

  • Resource Owner: GitHub user
  • Authorization service: GitHub server
  • Client: Coding system
  • Resource services: GitHub server

The authorized service server and resource server can be set up separately. In the microserver architecture, there can be a single authorization service, and there can be multiple resource services, such as user resources, warehouse resources, etc., which can be divided freely according to needs.

OAuth2 Endpoint

OAuth2 has three important endpoints: the authorized Endpoint, Token Endpoint node in the authorization server, and an optional redirect Endpoint in the client.

Authorize Endpoint redirect Endpoint

Authorization type

Through the four OAuth roles, you should have a general understanding of the OAuth protocol. However, you may not know how the roles in OAuth interact. It's OK to continue to look at the authorization type to know how the roles in OAuth fulfill their responsibilities and further understand OAuth. Four authorization types are defined in OAuth, which are:

  • Authorization code authorization
  • Guest room Certificate Authorization
  • Password authorization of resource Owner
  • Implicit authorization

Different authorization types can be used in different scenarios.

Authorization code authorization

This form is our common form of authorization (such as using GitHub account to log in to Coding). In the whole authorization process, there will be three OAuth roles: resource Owner, authorization server and client. The reason why it is called authorization code authorization is that in the interaction process, the authorization server will issue a code to the guest room, and then the guest room will take the code issued by the authorization server Continue to authorize, such as requesting the authorization server to issue the AccessToken.

 

To facilitate understanding and then bring the content of the above figure into the real scene, express the whole process in words:

  • A.1. The user accesses the Coding landing page (https://coding.net/login) and clicks the Github login button;
  • A.2. The Coding server redirects the browser to Github's authorization page (https://Github.com/login/oauth/authorize? Client_id = a5ce5a6c7e8c39567ca0 & scope = user: email & redirect_uri = https://Coding.net/api/oauth/Github/callback & response_type = code), and the URL takes the client_id and redirect_uri parameters;
  • B.1. The user enters the user name and password to log in Github;
  • B.2. The user clicks the authorization button to approve the authorization;
  • C.1. Github authorization server returns code;
  • C.2. Github redirects the browser to the redirect \ u URI address passed in step A.2 (https://coding.net/api/oauth/Github/callback & response \ U type = code);
  • D. After Coding gets the code, it calls the GitHub authorization server API to obtain the access token. Because this step cannot be captured in the browser done in the background of the Coding server, it is basically to use code to access the access_token node of GitHub to obtain the AccessToken;

The above is the general authorization code authorization process, most of which is the interaction between the client and the authorization server. In the whole process, there are several parameters as follows:

  • Client ABCD ID: Appid registered in Github, used to mark the client
  • Redirect uuri: you can understand a callback. After the authorized server verifies the client and user name, it redirects the browser to this address with the code parameter
  • code: a certificate returned by the authorization server to obtain the AccessToken
  • state: passed from the client to the authorization server. Generally, the authorization server returns as is when it calls the redirect \ u URI

Authorization code authorization request

In the authorization mode using authorization code, when the client requests authorization, it needs to request according to the specification. The following are the parameters needed when using authorization code authorization to initiate authorization:

 

For example, use Github to log in to the https://Github.com/login/oauth/authorize? Client_id = a5ce5a6c7e8c39567ca0 & scope = user: email & redirect_uri = https://Coding.net/api/oauth/Github/callback & response_type = code authorization request URL. There are client_id and redirect_uri parameters. As for why there is no response_type, I guess it is because Github has been saved.

Authorization code authorization response

If the user agrees to authorize, the authorization server will also return the standard OAuth authorization response:

 

For example, https://Coding.net/api/oauth/Github/callback & response'u type = code in Coding login, after the user agrees to authorize, Github authorizes the server to call back the callback address of Coding, and returns the code and state parameters.

Client Certificate Authorization

In the process of authorization, only the interaction between the client and the authorization server is involved. Compared with the other three authorization types, it is relatively simple. In general, this authorization mode is used for authorization between services. For example, in AWS, two servers are application server (A) and data server (b). If server A needs to access server B, it needs to authorize through the authorization server, and then access server B to obtain data.

 

In a simple two-step process, the authorization of guest room certificate can be completed. However, when using guest room Certificate Authorization, the client obtains the AccessToken interface from the authorization server directly accessed.

Client certificate authorization request

In the guest room Certificate Authorization, the client will directly request to obtain the AccessToken Endpoint of the authorization server. The request parameters are as follows:

 

Note: in OAuth, the AccessToken Endpoint uses HTTP Basic authentication, and the Authorization request header needs to be carried when requesting. For example, when using postman to test the request:

 

The username and password parameters are generated by the authorization server for the client ID and client secret in OAuth protocol.

Client certificate authorization response

After the authorization server verifies the client ID and client secret, it returns the token:

{   "access_token":"2YotnFZFEjr1zCsicMWpAA",   "token_type":"example",   "expires_in":3600,   "example_parameter":"example_value" }

User credential authorization

The authorization of user credentials is similar to the authorization of client credentials. The difference is that the user name and password should be provided for authorization.

 

The basic process is as follows:

  • A. The client needs to know the user's credentials first
  • B. Use user credentials to get AccessToken
  • C. The authorization server verifies the client and user credentials and returns the AccessToken

User credential authorization request

There are more username and pwsword parameters for user credential authorization request than for client credential authorization:

 

Note: when obtaining Token, HTTP Basic authentication is used, just like client certificate authorization.

User credential authorization response

The response of user certificate authorization is similar to that of Client Certificate Authorization:

{       "access_token":"2YotnFZFEjr1zCsicMWpAA",       "token_type":"example",       "expires_in":3600,       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",       "example_parameter":"example_value"     }

Implicit authorization

Implicit authorization is used to obtain the AccessToken, but it is different from user credential authorization and client authorization in that it will obtain the AccessToken instead of the access token Endpoint when it accesses the authorization Endpoint, and the AccessToken will be returned as the Segment of the redirect URI.

 

  • A.1. A.2. Browser access to the authorized Endpoint of the server supporting implicit authorization;
  • B.1. The user enters the account password;
  • B.2. The user clicks the authorization button to approve the authorization;
  • C. The authorization server returns the AccessToken using the redirect \ URI;
  • D. The authorization server redirects the browser to the redirect uuri and carries the access token, such as: http://example.com/cb ා access ﹐ token = 2yotnfzfejr1zcsicmwpaa & state = XYZ & token ﹐ type = example & expires ﹐ in = 3600;
  • D. The address of redirect \ u URI is to a Web resource client
  • E. Web resource client returns a script
  • F. Browser execution script
  • D. Client obtains AccessToken

Implicit authorization is not easy to understand, but a careful comparison of client certificate authorization and user certificate authorization will show that implicit authorization does not need to know the user certificate or client certificate, which is relatively safer.

Implicit authorization request

When using implicit authorization again, the required request parameters are as follows:

 

Implicit authorization response

The implicit authorization response parameter is returned through the redirect ﹐ URI callback, such as http://example.com/cb ﹐ access ﹐ token = 2yotnfzfejr1zcsicmwpaa & state = XYZ & token ﹐ type = example & expires ﹐ in = 3600, which means that the response parameter is in the form of Segment, not the normal URL parameter.

 

OAuth2 client

As mentioned earlier, there are four roles in the OAuth protocol. In this section, Spring Boot is used to implement an OAuth client for logging in to GitHub. To log in to GitHub using OAuth2 protocol, first apply in the cloud GitHub:

Apply for OAuth App

 

Fill in the required information

 

The Authorization callback URL in the above figure is that after the user agrees to authorize, GitHub will redirect the browser to this address. Therefore, you need to add an interface in the local OAuth client service to respond to GitHub's redirection request.

Configure OAuthClient

After being familiar with OAuth2 protocol, we use Spring Security OAuth2 to configure a GitHub authorization client and use the authentication code authorization process (you can first look at the authentication code authorization flow chart). The example project depends on:

<dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-oauth2-client</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>

Spring Security OAuth2 integrates Github, Google and other commonly used authorization servers by default, because the configuration information of these commonly used authorization services is public. Spring Security OAuth2 has been configured for us, and only the necessary information needs to be specified for development, such as clientId and clientSecret.

Spring Security OAuth2 uses Registration as the configuration entity of the client:

public static class Registration {    //Authorization server provider name private String provider; / / client ID private string ClientID; / / client credential private String clientSecret

Here is the information of the GitHub OAuth App registered before:

spring.security.oauth2.client.registration.github.clientId=5fefca2daccf85bede32spring.security.oauth2.client.registration.github.clientSecret=01dde7a7239bd18bd8a83de67f99dde864fb6524``

Configure redirect URI

Spring Security OAuth2 has a built-in redirect \ URI template: {baseUrl}/login/oauth2/code/{registrationId}, where the registrationId is extracted from the configuration:

spring.security.oauth2.client.registration.[registrationId].clientId=xxxxx

As shown in the above configuration of github client, since the specified registration ID is github, the redirection uri address is:

{baseUrl}/login/oauth2/code/github

Start server

After the OAuth2 client and redirection Uri are configured, start the server, and then open the browser to enter: http://localhost:8080 /. Open for the first time because no authentication will redirect the browser to the authorized Endpoint of GitHub:

 

Common authorization server (CommonOAuth2Provider)

Spring Security OAuth2 has built-in configurations of some commonly used authorization servers. These configurations are in the commonoauth2 provider:

public enum CommonOAuth2Provider {    GOOGLE {        @Override        public Builder getBuilder(String registrationId) {            ClientRegistration.Builder builder = getBuilder(registrationId,                    ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);            builder.scope("openid", "profile", "email");            builder.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth");            builder.tokenUri("https://www.googleapis.com/oauth2/v4/token");            builder.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs");            builder.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo");            builder.userNameAttributeName(IdTokenClaimNames.SUB);            builder.clientName("Google");            return builder;        }    },    GITHUB {        @Override        public Builder getBuilder(String registrationId) {            ClientRegistration.Builder builder = getBuilder(registrationId,                    ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);            builder.scope("read:user");            builder.authorizationUri("https://github.com/login/oauth/authorize");            builder.tokenUri("https://github.com/login/oauth/access_token");            builder.userInfoUri("https://api.github.com/user");            builder.userNameAttributeName("id");            builder.clientName("GitHub");            return builder;        }    },    FACEBOOK {        @Override        public Builder getBuilder(String registrationId) {            ClientRegistration.Builder builder = getBuilder(registrationId,                    ClientAuthenticationMethod.POST, DEFAULT_REDIRECT_URL);            builder.scope("public_profile", "email");            builder.authorizationUri("https://www.facebook.com/v2.8/dialog/oauth");            builder.tokenUri("https://graph.facebook.com/v2.8/oauth/access_token");            builder.userInfoUri("https://graph.facebook.com/me?fields=id,name,email");            builder.userNameAttributeName("id");            builder.clientName("Facebook");            return builder;        }    },    OKTA {        @Override        public Builder getBuilder(String registrationId) {            ClientRegistration.Builder builder = getBuilder(registrationId,                    ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);            builder.scope("openid", "profile", "email");            builder.userNameAttributeName(IdTokenClaimNames.SUB);            builder.clientName("Okta");            return builder;        }    };    private static final String DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}";}

There are four authorization server configurations in CommonOAuth2Provider: OKTA, FACEBOOK, GITHUB and GOOGLE. The configuration items in OAuth2 protocol, such as redirect \ u URI, Token Endpoint, authorization Endpoint and scope, will be configured here:

GITHUB {        @Override        public Builder getBuilder(String registrationId) {            ClientRegistration.Builder builder = getBuilder(registrationId,                    ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);            builder.scope("read:user");            builder.authorizationUri("https://github.com/login/oauth/authorize");            builder.tokenUri("https://github.com/login/oauth/access_token");            builder.userInfoUri("https://api.github.com/user");            builder.userNameAttributeName("id");            builder.clientName("GitHub");            return builder;        }    }

Redirect Uri intercept

The brain melon seeds are a bit confused. I feel that I have configured a client named clientid and clientSecret, and an OAuth2 client has been completed. Some of the reasons have not been figured out yet... , most curious is how the redirection Uri is handled.

Spring Security OAuth2 is based on spring Security. I have read the spring Security article before and know that its processing principle is based on filter. If you don't know, I recommend this article: Spring Security architecture. Find a suspicious Security filter in the source code:

  • OAuth2LoginAuthenticationFilter: a filter that handles OAuth2 authorization

This Security filter has a constant:

public static final String DEFAULT_FILTER_PROCESSES_URI = "/login/oauth2/code/*";

It's a matcher. As mentioned before, there is a default redirect uuri template in Spring Security OAuth2: {baseUrl}/{action}/oauth2/code/{registrationId}, / login/oauth2/code / * just matches the redirect uuri template successfully. Therefore, OAuth2LoginAuthenticationFilter will execute after the user agrees to authorize. Its construction method is as follows:

public OAuth2LoginAuthenticationFilter(ClientRegistrationRepository clientRegistrationRepository,                                        OAuth2AuthorizedClientService authorizedClientService) {    this(clientRegistrationRepository, authorizedClientService, DEFAULT_FILTER_PROCESSES_URI);}

OAuth2LoginAuthenticationFilter takes out the code returned by the authorization server, and then authenticates (obtains the AccessToken) through the authentication manager. The following is the source code after removing part of the code:

@Override    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)            throws AuthenticationException {        MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());        //Check if there is no code and state if (! Oauth2authorizationresponseutils. Isauthorizationresponse (params)) {oauth2error oauth2error = new oauth2error (oauth2errorcodes. Invalid_request); throw new oauth2authenticationexception (oauth2error, oauth2error. Tostring());} / / get oauth2authorizationrequest oauth2auth orizationRequest authorizationRequest =                this.authorizationRequestRepository.removeAuthorizationRequest(request, response);        if (authorizationRequest == null) {            OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);            throw new OAuth2AuthenticationException(oauth2Error, oauth2 Error. Tostring());} / / get clientregistration string registration id = authorizationrequest. Getattribute (oauth2parameternames. Registration? ID); clientregistration clientregistration = this. Clientregistrationrepository. Findbyregistration ID (Registration ID); if (clientregistration = = null) {oauther ror oauth2Error = new OAuth2Error(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE,                    "Client Registration not found with Id: " + registrationId, null);            throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());        }        String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl (request)). Replacequery (null). Build(). Touristring(); / / obtain accesstoken oauth2authorizationresponse authorizationresponse = oauth2authorizationresponseutils. Convert (params, redirecturi); object authenticationdetails = this.authenticationdetailssource.bui ldDetails(request);        OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(                clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));        authenticationRequest.setDetails(authenticationDetails);        OAuth2LoginAuthenticationToken authenticationR esult =            (OAuth2LoginAuthenticationToken) this.getAuthenticationManager().authenticate(authenticationRequest);        ...        return oauth2Authentication;    }

Get AccessToken

As mentioned earlier, OAuth2LoginAuthenticationFilter uses authentication manager for OAuth2 authentication. Generally, in Spring Security, authentication manager uses provider manager for authentication. Therefore, there is an OAuth2LoginAuthenticationProvider in Spring Security OAuth2 for access token:

public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider {    private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;    private final OAuth2UserService<OAuth2UserRequest, OAuth2User> userService;    private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);    ....        @Override    public Authentication authenticate(Authentication authentication) throws AuthenticationException {        OAuth2LoginAuthenticationToken authorizationCodeAuthentication =            (OAuth2LoginAuthenticationToken) authentication;        // Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest        // scope        //      REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.        if (authorizationCodeAuthentication.getAuthorizationExchange()            .getAuthorizationRequest().getScopes().contains("openid") ) {            // This is an OpenID Connect Authentication Request so return null            // and let OidcAuthorizationCodeAuthenticationProvider handle it instead            return null;        }        OAuth2AccessTokenResponse accessTokenResponse;        try {            OAuth2AuthorizationExchangeValidator.validate(                    author Igationcodeauthentication. Getauthorizationexchange()); / / visit GitHub TokenEndpoint to get token accesstokenresponse = this.accesstokenresponseclient.gettokenresponse (New oauth2authorizationcodegrantrequest (authorizationcodeauthentication. Getclientregistration() ,                            authorizationCodeAuthentication.getAuthorizationExchange()));        } catch (OAuth2AuthorizationException ex) {            OAuth2Error oauth2Error = ex.getError();            throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());        }         ...        return authenticationResult;    }    @ Override    public boolean supports(Class<?> authentication) {        return OAuth2LoginAuthenticationToken.class.isAssignableFrom(authentication);    }}

Reference material

  • OAuth 2 Developers Guide
  • draft-ietf-oauth-v

Tags: Programming github Spring Google AWS

Posted on Sun, 19 Jan 2020 01:58:18 -0500 by tanita