Get principal in Spring Boot resource server from JWT token

13,547

Solution 1

This is what I did, for anyone who's stubbles across this (this method uses JWKs to validate JWTs).

You first need to setup a config bean that extends the WebSecurityConfigurerAdapter.class. Within the HttpSecurity object, you can use the oauth2ResourceServer.jet() method, which is pretty nice. It'll look for JWTs for incoming requests and set the Principal objects (and other objects as well) for incoming requests. It should look something like this:

@Configuration
@EnableWebSecurity(debug = true) //optional - helps with debugging your security filters.
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .csrf()
            .disable()
            .antMatcher("/cars/**")
            .oauth2ResourceServer().jwt(); // validates the jwt token and also sets the authentication and principal

}

In the application.yml/.properties, you need to point to the JWK endpoint:

spring.security.oauth2.resourceserver.jwt.jwk-set-uri: <JWK_ENDPOINT>

In your controller, you can take the Principal and Authentication.

@RequestMapping(method = RequestMethod.GET, value = "/cars/{id}")
@ResponseBody
public Car getCar(@PathVariable("id") String id, Principal principal, Authentication auth) { 

    return new Car(id, "Ford", Color.BLUE, new Owner(principal.getName().toString(), 90120)); //the principal's name is a GUID

}

Alternatively, you can parse the JWT (because at this point, the BearerTokenAuthenticationFilter will have validated the JWT - in terms of expiration and other things) for reading the claims. This only works if you have a claim you're comfortable using.

Solution 2

Could i suggest another way?

You need to implement your own token generation, and wrap into the token user data. All you need it put necessary user information into Claims object. And then on resource server unwrap it back.

private Map<String, Object> getClaims(UserEntity user) {
    Map<String, Object> claims = new HashMap<>();
    OffsetDateTime now = OffsetDateTime.now(clock);
    OffsetDateTime exp = now.plusMinutes(1);

    //Standard claims
    claims(Claims.ISSUED_AT, Date.from(now.toInstant()).getTime());
    claims(Claims.EXPIRATION, Date.from(exp.toInstant()).getTime());
    claims(Claims.ID, UUID.randomUUID());

    //Private claims
    claims("user.auth", getRoles(user.getRoles()));

    //Clear fields that should not be stored in the token
    claims("user.data", stripUserFields(getApiUser(user)));

    return claims;
}

private String buildJwtToken(Claims claims, String secretKey) throws ApplicationException {
    DefaultJwsHeader header = new DefaultJwsHeader();
    if (secretKey != null) {
        header.setAlgorithm(ALGORITHM);
    }
    String base64EncodedJsonHeader = Base64Util.encode(toJson("header", header), true);
    String base64EncodedJsonBody = Base64Util.encode(toJson("body", claims), true);
    String base64UrlEncodedBaseToken = base64EncodedJsonHeader + "." + base64EncodedJsonBody;
    String hexDigest = createHexDigest(base64UrlEncodedBaseToken, secretKey);
    return base64UrlEncodedBaseToken + "." + Base64Util.encode(hexDigest, true);
}

We are using io.jsonwebtoken library.

Share:
13,547
Smajl
Author by

Smajl

Software developer with focus on Java and cloud technologies.

Updated on July 14, 2022

Comments

  • Smajl
    Smajl almost 2 years

    I have a separate auth server and a resource server - both Spring Boot applications. I am using OAuth2 with JWT tokens for authentication and authorisation.

    I can get a token by accessing the auth server:

    curl -X POST --user 'client:secret' -d 'grant_type=password&username=john&password=123' http://localhost:9000/auth-server/oauth/token
    

    and use it when getting a resource from the resource server (running on different server) by attaching the token to the request header.

    What is unclear to me though, is to determine which user is logged in in the resource server.

    In the auth server, I can do something like this:

    @RequestMapping("/user")
    public Principal user(Principal user) {
        return user;
    }
    

    and this endpoint will get me the current user based on the token used. If I try to do the same thing in the resource server though, I only get empty response.

    I need to get the id of the authenticated user somehow in the resource server to decide whether I should return some data to him (I have some resources which can be accessed only by their owner).

    How could I do that? I thought that by using JWT, I don't need to share the same token store.

    I can provide code samples if my question is not clear.. I am just looking for a confirmation that my assumption is correct and this behaviour can be achieved - this is my first time experimenting with spring security.

    The resource server is linked to the auth server in the application.properties:

    security.oauth2.resource.userInfoUri: http://localhost:9000/auth-server/user