'Spring Security - Custom Principal from JWT
Im using oauth2 and sso in my angular application. My rest backend verify the authentication token which is send by every request. Now I wanna use the oid claim to load the user from my database and save it in the principal. Also I wanna add the user authorities in the "GrantedAuthorities".
FooService
[...]
public Foo getFooByOid(String oid) throws FooNotFoundException {
return fooRepository.findByOid(oid)
.orElseThrow(() -> new FooNotFoundException("Foo with oid: " + oid + " not found"));
}
SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(auth -> auth
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> {
oauth2.jwt();
});
}
}
The spring.security.oauth2.resourceserver.jwt.jwk-set-uri and spring.security.oauth2.resourceserver.jwt.issuer-uri are defined in application.properties
After that my principal contains all the claims, headers and the token:
Can any one help me how to do? Thanks
Solution 1:[1]
The answer from Gary Archer is correct, but it has some pitfalls.
A full simple example in Kotlin:
The user interface, which is specific to your application:
interface User {
val id : String
val email : String
val displayName : String
val avatar : String
val roles : Set<String>
val domain : String
get() = this.email.split('@').last()
}
A principal should implement this interface:
class ClaimsNotFull(claim: String) : Exception("Claims not full, not found $claim")
data class IdelPrincipal(
override val id: String,
override val email: String,
override val displayName: String,
override val avatar: String,
override val roles: Set<String>,
) : User {
companion object {
fun fromJwt(jwt: Jwt): IdelPrincipal {
fun loadClaim(key: String): String = jwt.claims.getOrElse(key) {throw ClaimsNotFull(key)} as String
return IdelPrincipal(
id = jwt.subject,
email = loadClaim("email"),
displayName = loadClaim("displayName"),
avatar = loadClaim("avatar"),
roles = loadClaim("roles").split(",").toSet()
)
}
fun copyToClaims(user : User, jwt: JWTClaimsSet.Builder) : JWTClaimsSet.Builder {
return jwt.subject(user.id)
.claim("email", user.email)
.claim("displayName",user.displayName)
.claim("avatar",user.avatar)
.claim("roles",user.roles.joinToString(","))
}
}
}
Custom token and JwtConvertor:
class IdelAuthenticationToken(val jwt: Jwt, val user: IdelPrincipal) :
AbstractAuthenticationToken(IdelAuthorities.from(user.roles)) {
override fun getCredentials() = jwt // borrowed from JwtAuthenticationToken
override fun getPrincipal() = user
override fun isAuthenticated() = true // decoding of jwt is authentication
}
class IdelPrincipalJwtConvertor : Converter<Jwt, IdelAuthenticationToken> {
override fun convert(jwt: Jwt): IdelAuthenticationToken {
val principal = IdelPrincipal.fromJwt(jwt)
return IdelAuthenticationToken(jwt,principal)
}
}
Configure Spring Security:
override fun configure(http: HttpSecurity) {
http.authorizeRequests {
it.anyRequest().authenticated()
}
.csrf{it.ignoringAntMatchers("/token")}
.oauth2ResourceServer {it.jwt()}
.oauth2ResourceServer().jwt {cstm ->
cstm.jwtAuthenticationConverter(IdelPrincipalJwtConvertor())
}
http.exceptionHandling {
it
.authenticationEntryPoint(BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(BearerTokenAccessDeniedHandler())
}
http .sessionManagement {it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)}
http.anonymous().disable()
}
And a method in your controller for testing:
@GetMapping("/me2")
fun me2(@AuthenticationPrincipal user : User) : User {
val result = MeResult(
id = user.id,
domain = user.domain,
displayName = user.displayName,
avatar = user.avatar,
email = user.email,
authorities = user.roles.toList()
)
return user
}
That's return:
{
"id": "10777",
"email": "[email protected]",
"displayName": "Leonid Vygovskiy",
"avatar": "",
"roles": [
"ROLE_USER"
],
"domain": "gmail.com"
}
Solution 2:[2]
You can do this via a custom AuthenticationManager, if you write code such as this:
.oauth2ResourceServer.authenticationManagerResolver(request -> new CustomAuthenticationManager(request));
The properties you set on oauth2ResourceServer influence the behaviour of Spring's BearerTokenAuthenticationFilter class
You will then need to validate the JWT yourself, plus add your custom claims handling on top, then cache results for subsequent requests with the same token, which is tricky.
EXAMPLE OF MINE
I have a fairly complete sample that behaves like this, which can run on your PC and which you can maybe borrow some ideas from - it is quite an advanced sample though:
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | leonidv |
| Solution 2 |
