7
votes

I am migrating working XML configuration to Java configuration for Spring Security OAuth2 and using Google as the OAuth provider.

This is how my java configuration looks:

@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private static final List<String> scope;

    static {
        // Permissions to access email and profile
        scope = new ArrayList<>(3);
        scope.add("openid");
        scope.add("email");
        scope.add("profile");
    }

    @Autowired(required = true)
    private UserService userService;

    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http.
            authorizeRequests()
                .antMatchers(HttpMethod.GET, "/","/public/**", "/resources/**","/resources/public/**").permitAll()
                //.antMatchers("/google_oauth2_login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                    .loginPage("/")
                    .loginProcessingUrl("/login")
                    .defaultSuccessUrl("/home")
                    .and()
                    .csrf().disable()
                    .logout()
                        .logoutSuccessUrl("/")
                        .logoutUrl("/logout")
                .and()
                    .requiresChannel().anyRequest().requiresSecure()
                .and()
                    .addFilterAfter(oAuth2ClientContextFilter(),ExceptionTranslationFilter.class)
                    .addFilterAfter(googleOAuth2Filter(),OAuth2ClientContextFilter.class)
                .userDetailsService(userService);
        // @formatter:on
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth)
            throws Exception {
        // @formatter:off
        auth
            .authenticationProvider(googleOauth2AuthProvider())
            .userDetailsService(userService);
        // @formatter:on
    }

    @Bean
    public GoogleOAuth2Filter googleOAuth2Filter() throws Exception {
        GoogleOAuth2Filter filter = new GoogleOAuth2Filter(
                "/google_oauth2_login",
                "https://accounts.google.com/o/oauth2/auth",
                oAuth2RestTemplate(auth2ProtectedResourceDetails()));
        filter.setAuthenticationManager(authenticationManagerBean());
        return filter;
    }

    @Bean
    public GoogleOauth2AuthProvider googleOauth2AuthProvider() {
        GoogleOauth2AuthProvider authProvider = new GoogleOauth2AuthProvider();
        return authProvider;
    }

    @Bean
    public OAuth2ProtectedResourceDetails auth2ProtectedResourceDetails() {
        AuthorizationCodeResourceDetails auth2ProtectedResourceDetails = new AuthorizationCodeResourceDetails();
        auth2ProtectedResourceDetails
                .setClientAuthenticationScheme(AuthenticationScheme.form);
        auth2ProtectedResourceDetails
                .setAuthenticationScheme(AuthenticationScheme.form);
        auth2ProtectedResourceDetails.setGrantType("authorization_code");
        auth2ProtectedResourceDetails
                .setClientId("the-client-id");
        auth2ProtectedResourceDetails
                .setClientSecret("the-client-secret");
        auth2ProtectedResourceDetails
                .setAccessTokenUri("https://accounts.google.com/o/oauth2/token");
        auth2ProtectedResourceDetails.setScope(scope);
        auth2ProtectedResourceDetails
                .setUserAuthorizationUri("https://accounts.google.com/o/oauth2/auth");
        auth2ProtectedResourceDetails.setUseCurrentUri(false);
        auth2ProtectedResourceDetails
                .setPreEstablishedRedirectUri("https://localhost/google_oauth2_login");
        return auth2ProtectedResourceDetails;
    }

    @Bean
    public OAuth2RestTemplate oAuth2RestTemplate(
            OAuth2ProtectedResourceDetails resource) {
        OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(resource);
        return oAuth2RestTemplate;
    }

    @Bean
    public OAuth2ClientContextFilter oAuth2ClientContextFilter() {
        OAuth2ClientContextFilter oAuth2ClientContextFilter = new OAuth2ClientContextFilter();
        return oAuth2ClientContextFilter;
    }

}

Note that I have disabled CSRF. From my login page the user do gets redirected to Google login page

Problem:-

  1. Google Permission Page just asks for "Have offline access".'Email/Profile' access request is missing.

The equivalent 'scope' attibute XML configuration :-

<oauth2:resource id="googleOauth2Resource" type="authorization_code"
        client-id="the-client-id
        client-secret="the-client-secret"
        user-authorization-uri="https://accounts.google.com/o/oauth2/auth"
        scope="openid email profile" use-current-uri="false"
        client-authentication-scheme="form" pre-established-redirect-uri="https://localhost/google_oauth2_login" />

do correctly asks for email and profile permissions. Why?

  1. Continuing anyway with the 'Have offline access' results in this exception:-
    org.springframework.web.client.HttpClientErrorException: 400 Bad Request
        at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
        at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport$AccessTokenErrorHandler.handleError(OAuth2AccessTokenSupport.java:243)
        at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:592)
        at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:550)
        at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:514)
        at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAuthorizationCode(AuthorizationCodeAccessTokenProvider.java:145)
        at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAccessToken(AuthorizationCodeAccessTokenProvider.java:196)
        at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:142)
        at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:118)
        at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221)
        at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173)
        at org.springframework.security.oauth2.client.OAuth2RestTemplate.createRequest(OAuth2RestTemplate.java:105)

while trying to get user profile using this code block:

@Override
public Authentication attemptAuthentication(HttpServletRequest request,
        HttpServletResponse response) throws AuthenticationException,
        IOException, ServletException {
    logger.info("Google Oauth Filter Triggered!!");
    SecurityContext context = SecurityContextHolder.getContext();
    // auth null or not authenticated.
    String code = request.getParameter("code");
    Authentication dummyAuthentication = null;

    if (StringUtils.isEmpty(code)) {
        // Google authentication in progress. will return null.
        logger.debug("Will set dummy user in context ");
        dummyAuthentication = getDummyAuthenticationToken();
        context.setAuthentication(dummyAuthentication);
        // trigger google oauth2.
        oauth2RestTemplate.postForEntity(authURI, null, Object.class);
        return null;
    } else {
        // response from google received !!.
        // remove dummy authentication from context.
        //SecurityContextHolder.clearContext();
        logger.debug("Response from Google Recieved !!");
        // get user profile and prepare the authentication token object.
        ResponseEntity<Object> forEntity = oauth2RestTemplate.getForEntity(
                HTTPS_WWW_GOOGLEAPIS_COM_PLUS_V1_PEOPLE_ME_OPEN_ID_CONNECT,
                Object.class);

        @SuppressWarnings("unchecked")
        Map<String, String> profile = (Map<String, String>) forEntity
                .getBody();

        CustomOAuth2AuthenticationToken authenticationToken = getOAuth2Token(
                profile.get(EMAIL), profile.get(NAME));
        authenticationToken.setAuthenticated(false);
        return getAuthenticationManager().authenticate(authenticationToken);
    }
}

Spring RestTemplate showing this in logs:

o.s.web.client.RestTemplate              : POST request for "https://accounts.google.com/o/oauth2/auth" resulted in 400 (Bad Request); invoking error handler
2014-09-05 21:51:46.870  WARN 5836 --- [ qtp25546756-15] o.eclipse.jetty.servlet.ServletHandler   : /google_oauth2_login

This same piece of code works while using with XML configuration.

UPDATE 1

I was able to fix the 'Offline Access' problem by changing scope to 'https://www.googleapis.com/auth/plus.profile.emails.read' & 'https://www.googleapis.com/auth/plus.login'.

Still getting bad request error while trying to get user profile

Please find source code for the problem here - git clone https://[email protected]/kumarsambhavjain/spring-oauth2-login.git

2
UPDATE: As of now, I managed to get it working (refer the git repository). Only problem is that the Google consent page is asking for "Offline Access" permission.Kumar Sambhav
Tried your bitbucket code, getting "Authentication Failed: No AuthenticationProvider found for com.samsoft.oauth2.filter.CustomOAuth2AuthenticationToken" did you see this anytime while working on this?Anand Rockzz
@Kumar: I have used your code for google authentication provider. But, i'm getting the following exception once i got authenticated. There was an unexpected error (type=Unauthorized, status=401). Authentication Failed: No AuthenticationProvider found for com.samsoft.oauth2.filter.CustomOAuth2AuthenticationToken. Could you please help me to resolve this error ?DIVA
@DIVA I am still trying to resolve that issue, will get back to you in some time if I could fix that.Kumar Sambhav

2 Answers

1
votes

Have you tried change profile URL to

https://www.googleapis.com/plus/v1/people/me/openIdConnect

See more: https://developers.google.com/+/api/openidconnect/getOpenIdConnect

0
votes

I used your code to create a OAuth2 Spring Boot sample, quite similar, and I had same issue once I replaced profile URL to:

https://www.googleapis.com/plus/v1/people/me/openIdConnect

I resolved it by enabling Google+ API in Google Console:

  • Visit the Google API Console here:https://code.google.com/apis/console/?api=plus
  • Under the Services panel, make sure the Google+ API is turned "on".

  • In the APIs console, click API Access in the left menu.

  • Copy the API key presented towards the bottom. Include this API key in your HTTP request.

This process is explained in this question:

How to call https://www.googleapis.com/plus/v1/people/me at google