PHPFixing
  • Privacy Policy
  • TOS
  • Ask Question
  • Contact Us
  • Home
  • PHP
  • Programming
  • SQL Injection
  • Web3.0

Saturday, September 3, 2022

[FIXED] How to set a custom principal object during or after JWT authentication?

 September 03, 2022     authentication, jwt, spring, spring-boot, spring-security     No comments   

Issue

I've changed the way a user is authenticated in my backend. From now on I am receiving JWT tokens from Firebase which are then validated on my Spring Boot server.

This is working fine so far but there's one change which I am not too happy about and it's that the principal-object is now a org.springframework.security.oauth2.jwt.Jwt and not a AppUserEntity, the user-model, like before.

// Note: "authentication" is a JwtAuthenticationToken

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Jwt jwt = (Jwt) authentication.getPrincipal();

So, after some reading and debugging I found that the BearerTokenAuthenticationFilter essentially sets the Authentication object like so:

// BearerTokenAuthenticationFilter.java

AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);

// Note: authenticationResult is our JwtAuthenticationToken
Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);  

SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authenticationResult);
SecurityContextHolder.setContext(context);

and as we can see, this on the other hand comes from the authenticationManager which is a org.springframework.security.authentication.ProviderManager and so on. The rabbit hole goes deep.

I didn't find anything that would allow me to somehow replace the Authentication.

So what's the plan?

Since Firebase is now taking care of user authentication, a user can be created without my backend knowing about it yet. I don't know if this is the best way to do it but I intend to simply create a user record in my database once I discover a valid JWT-token of a user which does not exist yet.

Further, a lot of my business logic currently relies on the principal being a user-entity business object. I could change this code but it's tedious work and who doesn't want to look back on a few lines of legacy code?


Solution

I did it a bit different than Julian Echkard.

In my WebSecurityConfigurerAdapter I am setting a Customizer like so:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.oauth2ResourceServer()
            .jwt(new JwtResourceServerCustomizer(this.customAuthenticationProvider));
}

The customAuthenticationProvider is a JwtResourceServerCustomizer which I implemented like this:

public class JwtResourceServerCustomizer implements Customizer<OAuth2ResourceServerConfigurer<HttpSecurity>.JwtConfigurer> {

    private final JwtAuthenticationProvider customAuthenticationProvider;

    public JwtResourceServerCustomizer(JwtAuthenticationProvider customAuthenticationProvider) {
        this.customAuthenticationProvider = customAuthenticationProvider;
    }

    @Override
    public void customize(OAuth2ResourceServerConfigurer<HttpSecurity>.JwtConfigurer jwtConfigurer) {
        String key = UUID.randomUUID().toString();
        AnonymousAuthenticationProvider anonymousAuthenticationProvider = new AnonymousAuthenticationProvider(key);
        ProviderManager providerManager = new ProviderManager(this.customAuthenticationProvider, anonymousAuthenticationProvider);
        jwtConfigurer.authenticationManager(providerManager);
    }
}

I'm configuring the NimbusJwtDecoder like so:

@Component
public class JwtConfig {

    @Bean
    public JwtDecoder jwtDecoder() {
        String jwkUri = "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com";
        return NimbusJwtDecoder.withJwkSetUri(jwkUri)
                .build();
    }

}

And finally, we need a custom AuthenticationProvider which will return the Authentication object we desire:

@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {

    private final JwtDecoder jwtDecoder;

    @Autowired
    public JwtAuthenticationProvider(JwtDecoder jwtDecoder) {
        this.jwtDecoder = jwtDecoder;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        BearerTokenAuthenticationToken token = (BearerTokenAuthenticationToken) authentication;

        Jwt jwt;
        try {
            jwt = this.jwtDecoder.decode(token.getToken());
        } catch (JwtValidationException ex) {
            return null;
        }

        List<GrantedAuthority> authorities = new ArrayList<>();

        if (jwt.hasClaim("roles")) {
            List<String> rolesClaim = jwt.getClaim("roles");
            List<RoleEntity.RoleType> collect = rolesClaim
                    .stream()
                    .map(RoleEntity.RoleType::valueOf)
                    .collect(Collectors.toList());

            for (RoleEntity.RoleType role : collect) {
                authorities.add(new SimpleGrantedAuthority(role.toString()));
            }
        }

        return new JwtAuthenticationToken(jwt, authorities);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(BearerTokenAuthenticationToken.class);
    }

}


Answered By - Stefan Falk
Answer Checked By - Pedro (PHPFixing Volunteer)
  • Share This:  
  •  Facebook
  •  Twitter
  •  Stumble
  •  Digg
Newer Post Older Post Home

0 Comments:

Post a Comment

Note: Only a member of this blog may post a comment.

Total Pageviews

Featured Post

Why Learn PHP Programming

Why Learn PHP Programming A widely-used open source scripting language PHP is one of the most popular programming languages in the world. It...

Subscribe To

Posts
Atom
Posts
Comments
Atom
Comments

Copyright © PHPFixing