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";