How to restrict to a specific domain to login with Spring-Boot and OAuth2

I have successfully done a OAuth2 login with spring boot and Google, but I’d like to restrict logins to a specific domain (we’re using Google Apps for Work).

I think that I should handle by extending class OAuth2ClientAuthenticationProcessingFilter (as specified in this thread), but I’m not sure how to do that.

Basically, I’d like to use Google OAuth 2.0 as the identity provider, but only company users (@company.com) must be accepted.

Answer

According to Stéphane suggestion, I came to this tutorial, and finally implemented this, which works for me with a Google+ profile:

@Configuration
@EnableOAuth2Sso
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private static final String GOOGLE_PLUS_DOMAIN_ATTRIBUTE = "domain";
    private static final String CSRF_COOKIE_NAME = "XSRF-TOKEN";
    private static final String CSRF_HEADER_NAME = "X-XSRF-TOKEN";

    @Bean
    public AuthoritiesExtractor authoritiesExtractor(
            @Value("#{'${security.allowed-domains}'.split(',')}") final List<String> allowedDomains) {

        return new AuthoritiesExtractor() {
            @Override
            public List<GrantedAuthority> extractAuthorities(final Map<String, Object> map) {
                if (map != null && map.containsKey(GOOGLE_PLUS_DOMAIN_ATTRIBUTE)) {
                    final String domain = (String) map.get(GOOGLE_PLUS_DOMAIN_ATTRIBUTE);
                    if (!allowedDomains.contains(domain)) {
                        throw new BadCredentialsException("Not an allowed domain");
                    }
                    return AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
                }
                return null;
            }
        };
    }

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        // @formatter:off
        http.antMatcher("/**")
        .authorizeRequests()
        .antMatchers("/logout", "/api/mappings/**", "/public/**").permitAll()
        .anyRequest().hasAuthority("ROLE_USER")
        .and().logout().logoutUrl("/api/logout").logoutSuccessUrl("/logout")
        .and().csrf().csrfTokenRepository(csrfTokenRepository()).ignoringAntMatchers("/api/mappings/**")
        .and().addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
        // @formatter:on
    }

    private Filter csrfHeaderFilter() {
        return new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
                    final FilterChain filterChain) throws ServletException, IOException {

                final CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
                if (csrf != null) {
                    Cookie cookie = WebUtils.getCookie(request, CSRF_COOKIE_NAME);
                    final String token = csrf.getToken();
                    if (cookie == null || token != null && !token.equals(cookie.getValue())) {
                        cookie = new Cookie(CSRF_COOKIE_NAME, token);
                        cookie.setPath("/");
                        response.addCookie(cookie);
                    }
                }
                filterChain.doFilter(request, response);
            }
        };
    }

    private CsrfTokenRepository csrfTokenRepository() {
        final HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName(CSRF_HEADER_NAME);
        return repository;
    }
}

My application.yml file contains the following entries regarding oauth:

security:
     oauth2:
         client:
                access-token-uri: https://www.googleapis.com/oauth2/v3/token
                user-authorization-uri: https://accounts.google.com/o/oauth2/auth
                client-authentication-scheme: form
                scope: profile,email
         resource:
                user-info-uri: https://www.googleapis.com/plus/v1/people/me
                prefer-token-info: false

When working with a Google+ profile, the resource server response provided in the map, contains an entry for domain. I just compared this value with configured allowed domains.

Hope this helps.

Update: On March 7th 2019, Google is deprecating Google+ APIs. If you’re like me, you’ll have received an email from Google suggesting to update your software. In our case, the url https://www.googleapis.com/plus/v1/people/me, will be deprecated. So, I’m posting here my updated configuration (build with Spring Boot 1.3.5).

security:
 oauth2:
     client:
            clientId: *your client id from Google*
            clientSecret: *your client secret from Google*                
            accessTokenUri: https://www.googleapis.com/oauth2/v4/token
            userAuthorizationUri: https://accounts.google.com/o/oauth2/v2/auth
            clientAuthenticationScheme: form
            scope: 
              - email
              - profile                
     resource:
            userInfoUri: https://www.googleapis.com/oauth2/v3/userinfo
            preferTokenInfo: false

 # Comma-separated list of domains                
 allowed-domains: *your allowed domains*

Please note that minor change must be done in you WebSecurityConfigurerAdapter as the attribute domain has changed its name. So you’ll need to replace the line:

private static final String GOOGLE_PLUS_DOMAIN_ATTRIBUTE = “domain”;

with

private static final String HOSTED_DOMAIN_ATTRIBUTE = “hd”;

Leave a Reply

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