Google OAuth with Spring Security as separated Resource Server

There are a lot of great articles out there about OAuth 2.0, Spring Security, REST especially when you are about to have both the Resource and the Authorization server in the same application.

However, there are cases when you want to separate these. This is mainly true when you have microservices and the most straightforward way implementing these two is to use Spring Boot because what you need to do there is mostly configuration.

In this article, I’ll walk you through the implementation of a simple, secured resource server using Google OAuth (Implicit grant flow) with standard Spring. In case you are not familiar with OAuth, make sure you check it out before you continue reading.

First of all, let’s define the responsibilities. We will have a simple stateless backend with an endpoint which we want to secure with Google OAuth. The frontend can be any application which can consume the resources provided by the backend – for example an Angular application. The stateless nature of the backend is especially important when you are creating a REST backend as it’s a constraint. Stateless means that an incoming request have to contain all the necessary information for the server to send back a response. The consequence is that the client application have to send the access token to the backend when querying any resource and the server should validate whether the sent token is valid. This is done by going to the authorization server to a specific endpoint which validates the token.

An additional requirement for a real life application is to associate a google user with our user entity thus we need to query the user information from google (id, email, etc.). I’ll show you how you can create a custom Authentication object for this case.

Let’s start with a simple Spring MVC controller which defines one endpoint.

@RestController
public class SecuredAPIController {
    @RequestMapping(value = "/secure")
    public Response secure() {
        return new Response("example");
    }

    public static class Response {
        private String str;

        public Response(String str) {
            this.str = str;
        }

        public String getStr() {
            return str;
        }

        public void setStr(String str) {
            this.str = str;
        }
    }
}

Now we want to secure all the endpoints – in this case only one – so we have the following configuration:

@Configuration
@EnableResourceServer
@EnableWebSecurity
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private OAuthProperties oAuthProperties;

    @Override
    public void configure(final HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.authorizeRequests().anyRequest().hasRole("USER");
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId(oAuthProperties.getClientId());
    }
}

I’m not gonna go into every detail but what is important here is that we have the @EnableResourceServer annotation on the configuration class and we have the HTTP security configured. The first thing we have to do is to make the security stateless and the second thing is that we want to secure every endpoint on our server to be accessible for users which have USER role. Of course you can customize this and check whether a specific OAuth scope is present and so on.

The only thing left is to define a ResourceServerTokenServices bean. This is the place where you have to return an OAuth2Authentication object based on an access token which is passed in the request’s header.

For remote authorization server, you have the option to use Spring’s RemoteTokenServices class but as OAuth 2.0 is not specifying how to validate the access token with a remote authorization server, this implementation won’t fit in all the cases. Unfortunately the latter is the case for Google, but it’s not that complicated to implement on your own.

First of all, we have to create a custom ResourceServerTokenServices implementation, let’s call it GoogleTokenServices. There are 2 methods which you have to override, loadAuthentication and readAccessToken. For our case, only the first one is important.

public class GoogleTokenServices implements ResourceServerTokenServices, InitializingBean {
    private String userInfoUrl;

    private RestTemplate restTemplate = new RestTemplate();
    private AccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
    private AccessTokenValidator tokenValidator;

    public GoogleTokenServices(AccessTokenValidator tokenValidator) {
        this.tokenValidator = tokenValidator;
    }

    @Override
    public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException {
        AccessTokenValidationResult validationResult = tokenValidator.validate(accessToken);
        if (!validationResult.isValid()) {
            throw new UnapprovedClientAuthenticationException("The token is not intended to be used for this application.");
        }
        Map<String, ?> tokenInfo = validationResult.getTokenInfo();
        OAuth2Authentication authentication = getAuthentication(tokenInfo, accessToken);
        return authentication;
    }

    private OAuth2Authentication getAuthentication(Map<String, ?> tokenInfo, String accessToken) {
        OAuth2Request request = tokenConverter.extractAuthentication(tokenInfo).getOAuth2Request();
        Authentication authentication = getAuthenticationToken(accessToken);
        return new OAuth2Authentication(request, authentication);
    }

    private Authentication getAuthenticationToken(String accessToken) {
        Map<String, ?> userInfo = getUserInfo(accessToken);
        String idStr = (String) userInfo.get("id");
        if (idStr == null) {
            throw new InternalAuthenticationServiceException("Cannot get id from user info");
        }
        return new UsernamePasswordAuthenticationToken(new GooglePrincipal(new BigInteger(idStr)), null, singleton(new SimpleGrantedAuthority("ROLE_USER")));
    }

    private Map<String, ?> getUserInfo(String accessToken) {
        HttpHeaders headers = getHttpHeaders(accessToken);
        Map map = restTemplate.exchange(userInfoUrl, HttpMethod.GET, new HttpEntity<>(headers), Map.class).getBody();
        return (Map<String, Object>) map;
    }

    private HttpHeaders getHttpHeaders(String accessToken) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + accessToken);
        return headers;
    }

    @Override
    public OAuth2AccessToken readAccessToken(String accessToken) {
        throw new UnsupportedOperationException("Not supported: read access token");
    }

    public void setUserInfoUrl(String userInfoUrl) {
        this.userInfoUrl = userInfoUrl;
    }
}

Let’s go through the loadAuthentication method. First of all, according to Google, validating the token is a mandatory step so that’s what we have to do through an AccessTokenValidator. This is a custom interface I introduced. I’ll get back to the Google implementation of this class in a few lines below.

After we are certain that the token is ready for use – meaning it’s valid – we have to construct the OAuth2Authentication object. This is done by the getAuthentication method which basically uses a DefaultAccessTokenConverter to extract a basic OAuth2Authentication. This implementation is enough if you don’t want to have any information about the user which the token belongs to. If you want to have the Google userId, email of the token owner, you have to send another request to Google and this is how this implementation looks like. The getAuthenticationToken returns an Authentication object with a GooglePrincipal – custom class – which contains only the Google id of the user plus we are giving a default USER role. If you need for example the email of the Google user, you can put that info into the GooglePrincipal as well as any other necessary data.

Now let’s get back to our AccessTokenValidator.

public class GoogleAccessTokenValidator implements AccessTokenValidator, InitializingBean {
    private String clientId;
    private String checkTokenUrl;

    private RestTemplate restTemplate = new RestTemplate();

    public GoogleAccessTokenValidator() {
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
            @Override
            public void handleError(ClientHttpResponse response) throws IOException {
                if (response.getRawStatusCode() == 400) {
                    throw new InvalidTokenException("The provided token is invalid");
                }
            }
        });
    }

    @Override
    public AccessTokenValidationResult validate(String accessToken) {
        Map<String, ?> response = getGoogleResponse(accessToken);
        boolean validationResult = validateResponse(response);
        return new AccessTokenValidationResult(validationResult, response);
    }

    private boolean validateResponse(Map<String, ?> response) throws AuthenticationException {
        String aud = (String) response.get("aud");
        if (!StringUtils.equals(aud, clientId)) {
            return false;
        }
        return true;
    }

    private Map<String, ?> getGoogleResponse(String accessToken) {
        HttpEntity<Object> requestEntity = new HttpEntity<>(new HttpHeaders());
        Map<String, String> variables = ImmutableMap.of("accessToken", accessToken);
        Map map = restTemplate.exchange(checkTokenUrl, HttpMethod.GET, requestEntity, Map.class, variables).getBody();
        return (Map<String, Object>) map;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    public void setCheckTokenUrl(String checkTokenUrl) {
        this.checkTokenUrl = checkTokenUrl;
    }
}

This validator is written based on Google’s suggestion. That’s why we have a specialized ErrorHandler for the RestTemplate which checks for 400 status as it means that the token is expired, the permissions are revoked or something is not OK with the token. The core idea here is to check whether the returned JSON contains the application’s clientId as audiance or the token is intended to be used for other application.

The source can be found on GitHub. Feel free to reach me out in case of questions in the comments or on Twitter.

22 Replies to “Google OAuth with Spring Security as separated Resource Server”

  1. Visitor says:
    1. Arnold Galovics says:
  2. Corentin says:
    1. Arnold Galovics says:
  3. Krishna says:
    1. Krishna says:
      1. Dhaval says:
        1. Hari says:
      2. Ricky says:
  4. Mick Knutson says:
  5. tom kast says:
  6. YASIK says:
      1. Madalin says:
  7. DuyHuynh says:
  8. Saurav Prakash says:
    1. Arnold Galovics says:

Leave a Reply

Your email address will not be published. Required fields are marked *