BoomShaka
BoomShaka

Reputation: 1791

How to get JWT claims in a Spring Service or Controller

I have googled the depths of the internet, but can't find a decent answer to this anywhere. How can I access the claims within a JWT in a spring service?

We have a standalone authentication service that issues a JWT. I am building a separate spring service that needs to use this Jwt. I have the public key of the private key that was used to sign the JWT and have pieced together enough tutorials to be able to verify the JWT (with the public key) and allow access to the controllers I want.

In my service, I now need to extract the userId ​claim in the JWT (among others) so that I can call my DB with it, etc.

https://www.baeldung.com/spring-security-oauth-jwt (Section 5.1) seemed to be the most relevant search result:

@GetMapping("/user/info")
public Map<String, Object> getUserInfo(@AuthenticationPrincipal Jwt principal) {
    Map<String, String> map = new Hashtable<String, String>();
    map.put("user_name", principal.getClaimAsString("preferred_username"));
    map.put("organization", principal.getClaimAsString("organization"));
    return Collections.unmodifiableMap(map);
}

However when my code runs, principal is always null. I assume there is some other interface I need to implement somewhere.

All paths in my app require authentication, so I have:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
    // http.antMatcher("/**").authorizeRequests().anyRequest().permitAll();
    http.csrf().disable()
        .authorizeRequests()
        .antMatchers("/**").authenticated();
}

Upvotes: 7

Views: 32672

Answers (2)

Steve Riesenberg
Steve Riesenberg

Reputation: 6043

@EnableResourceServer is part of spring-security-oauth which is end of life, and you should migrate away as it's not recommended for new projects.

Check out the reference for the new oauth2-resource-server support, which should allow @AuthenticationPrincipal Jwt principal to work correctly in your controller. Also, see this repository's SecurityConfiguration, or follow along with the video presentation from SpringOne 2021.

When reading the reference docs, you would be most interested in overriding Spring Boot's configuration to provide your own @Bean of JwtDecoder using your available public key.

You can also optionally provide your own @Bean of JwtAuthenticationConverter or Converter<Jwt, AbstractAuthenticationToken> to get access to the claims and map them into an Authentication such as JwtAuthenticationToken as needed.

Upvotes: 12

K.Nicholas
K.Nicholas

Reputation: 11551

There is good example code from bfwg/angular-spring-starter

You have to add an authentication filter to your HttpSecurity configuration:

 .addFilterBefore(new TokenAuthenticationFilter(tokenHelper, jwtUserDetailsService), BasicAuthenticationFilter.class);

The the TokenAuthenticationFilter class does this work.

@Override
public void doFilterInternal(
        HttpServletRequest request,
        HttpServletResponse response,
        FilterChain chain
) throws IOException, ServletException {

    String username;
    String authToken = tokenHelper.getToken(request);

    if (authToken != null) {
        // get username from token
        username = tokenHelper.getUsernameFromToken(authToken);
        if (username != null) {
            // get user
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            if (tokenHelper.validateToken(authToken, userDetails)) {
                // create authentication
                TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
                authentication.setToken(authToken);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
    }
    chain.doFilter(request, response);
}

And it uses a helper class

public String getToken( HttpServletRequest request ) {
    /**
     *  Getting the token from Authentication header
     *  e.g Bearer your_token
     */
    String authHeader = getAuthHeaderFromHeader( request );
    if ( authHeader != null && authHeader.startsWith("Bearer ")) {
        return authHeader.substring(7);
    }

    return null;
}

and

public String getUsernameFromToken(String token) {
    String username;
    try {
        final Claims claims = this.getAllClaimsFromToken(token);
        username = claims.getSubject();
    } catch (Exception e) {
        username = null;
    }
    return username;
}

and

private Claims getAllClaimsFromToken(String token) {
    Claims claims;
    try {
        claims = Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(token)
                .getBody();
    } catch (Exception e) {
        claims = null;
    }
    return claims;
}+

The important library is the Jwt library for decoding and parsing the JWT. This one uses

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

The latest spring boot version also has.

import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.KeyLengthException;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.SignedJWT;

Probably either one will work since JWT is a standard.

Upvotes: 1

Related Questions