1

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:

Principal

Can any one help me how to do? Thanks

Scream
  • 11
  • 1
  • 4

2 Answers2

3

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": "leonid.vygovsky@gmail.com",
    "displayName": "Leonid Vygovskiy",
    "avatar": "",
    "roles": [
        "ROLE_USER"
    ],
    "domain": "gmail.com"
}
leonidv
  • 1,332
  • 13
  • 20
1

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:

Gary Archer
  • 22,534
  • 2
  • 12
  • 24