-
-
Save jimmyjames/e370e0eddb7c8767488a45fd1ba74014 to your computer and use it in GitHub Desktop.
Demonstrate Spring Security custom authorities extractor to create authorities from non-standard claims
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.auth0.example.security; | |
import org.springframework.beans.factory.annotation.Value; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.core.convert.converter.Converter; | |
import org.springframework.security.config.annotation.web.builders.HttpSecurity; | |
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | |
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; | |
import org.springframework.security.core.GrantedAuthority; | |
import org.springframework.security.core.authority.SimpleGrantedAuthority; | |
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; | |
import org.springframework.security.oauth2.core.OAuth2TokenValidator; | |
import org.springframework.security.oauth2.jwt.*; | |
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; | |
import org.springframework.stereotype.Component; | |
import org.springframework.util.StringUtils; | |
import java.util.*; | |
import java.util.stream.Collectors; | |
/** | |
* Configures our application with Spring Security to restrict access to our API endpoints. | |
*/ | |
@EnableWebSecurity | |
@Component | |
public class SecurityConfig extends WebSecurityConfigurerAdapter { | |
@Value( "${auth0.audience}" ) | |
private String audience; | |
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") | |
private String issuer; | |
@Override | |
public void configure(HttpSecurity http) throws Exception { | |
/* | |
This is where we configure the security required for our endpoints and setup our app to serve as | |
an OAuth2 Resource Server, using JWT validation. | |
*/ | |
http.authorizeRequests() | |
.mvcMatchers("/api/public").permitAll() | |
.mvcMatchers("/api/private").authenticated() | |
.mvcMatchers("/api/private-scoped").hasAuthority("SCOPE_read:messages") | |
// require these endpoints to have authorities for roles or groups | |
.mvcMatchers("/api/private-role").hasAuthority("ROLE_test-role") | |
.mvcMatchers("api/private-group").hasAuthority("GROUP_group-1") | |
.and() | |
// configure custom authentication converter to customize authority extraction | |
.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter()); | |
} | |
@Bean | |
JwtDecoder jwtDecoder() { | |
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder) | |
JwtDecoders.fromOidcIssuerLocation(issuer); | |
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience); | |
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer); | |
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator); | |
jwtDecoder.setJwtValidator(withAudience); | |
return jwtDecoder; | |
} | |
JwtAuthenticationConverter jwtAuthenticationConverter() { | |
CustomAuthoritiesConverter customAuthoritiesConverter = new CustomAuthoritiesConverter(); | |
JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter(); | |
authenticationConverter.setJwtGrantedAuthoritiesConverter(customAuthoritiesConverter); | |
return authenticationConverter; | |
} | |
static class CustomAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> { | |
// extract authorities from "scope", "https://example.com/role", "https://example.com/group", and "permissions" claims. | |
private static final Map<String, String> CLAIMS_TO_AUTHORITY_PREFIX_MAP = new HashMap<String, String>() {{ | |
put("scope", "SCOPE_"); | |
put("https://example.com/role", "ROLE_"); | |
put("https://example.com/group", "GROUP_"); | |
put("permissions", "PERMISSION_"); | |
}}; | |
@Override | |
public Collection<GrantedAuthority> convert(Jwt jwt) { | |
return CLAIMS_TO_AUTHORITY_PREFIX_MAP.entrySet().stream() | |
.map(entry -> getAuthorities(jwt, entry.getKey(), entry.getValue())) | |
.flatMap(Collection::stream) | |
.collect(Collectors.toList()); | |
} | |
private Collection<GrantedAuthority> getAuthorities(Jwt jwt, String authorityClaimName, String authorityPrefix) { | |
Object authorities = jwt.getClaim(authorityClaimName); | |
if (authorities instanceof String) { | |
if (StringUtils.hasText((String) authorities)) { | |
List<String> claims = Arrays.asList(((String) authorities).split(" ")); | |
return claims.stream() | |
.map(claim -> new SimpleGrantedAuthority(authorityPrefix + claim)) | |
.collect(Collectors.toList()); | |
} else { | |
return Collections.emptyList(); | |
} | |
} else if (authorities instanceof Collection) { | |
Collection<String> authoritiesCollection = (Collection<String>) authorities; | |
return authoritiesCollection.stream() | |
.map(authority -> new SimpleGrantedAuthority(authorityPrefix + authority)) | |
.collect(Collectors.toList()); | |
} | |
return Collections.emptyList(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment