Skip to content

Instantly share code, notes, and snippets.

@jimmyjames
Last active June 29, 2022 12:57
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jimmyjames/e370e0eddb7c8767488a45fd1ba74014 to your computer and use it in GitHub Desktop.
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
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