SG_
SG_

Reputation: 496

JWT Authentication with Springboot

I am developing a Rest Backend with microservices architecture using SpringBoot. To secure the endpoints I have used JWT Token Mechanism. I am using Zuul API Gateway.

If the request has required permission (ROLE from JWT) It will be forward to the correct microservice. "WebSecurityConfigurerAdapter" of the Zuul api gateway is as follows.

@Autowired
private JwtAuthenticationConfig config;

@Bean
public JwtAuthenticationConfig jwtConfig() {
    return new JwtAuthenticationConfig();
}

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity
            .csrf().disable()
            .logout().disable()
            .formLogin().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
                .anonymous()
            .and()
                .exceptionHandling().authenticationEntryPoint(
                        (req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
            .and()
                .addFilterAfter(new JwtTokenAuthenticationFilter(config),
                        UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers(config.getUrl()).permitAll()
                .antMatchers("/api/user/**").permitAll()
                .antMatchers("/api/package/**").hasRole("USER")
                .antMatchers("/api/dashboard/**").hasRole("USER")
                .antMatchers("/api/records/**").hasRole("USER");
}

In this way I have to write every request authorization part in this class. So I am hoping to use method level security, with "EnableGlobalMethodSecurity".

Problem is how should I connect this security mechanism with other microservices. Because when I added the spring security dependancy to other microservices they behave as different spring security modules. How should I tell to other microservices that work with zuul server security ?

Upvotes: 0

Views: 1608

Answers (1)

RazvanParautiu
RazvanParautiu

Reputation: 2938

First of all (if i have correctly understood) the security implementation is on proxy? Because the proxy must have only two things to do: filtering and routing...

My microservces application flow, which I have implemented is like in the bellow image: enter image description here

And the flow should be like this: https://www.rfc-editor.org/rfc/rfc6749#page-7

Short brief about flow:

  1. On login you should pass the user credentials
  2. If the request has the context path "/security" (for example) you should redirect the request to AuthServer (you decide the security implementation)
  3. If the user pass available credentials, the AuthServer must return an access_token.
  4. Having the access token the user is able to make request to AccountServices(resource services);

In the AccountServices you must implement a configuration class to decode the access_token and to check if the user has permission to access the resource requested

Also a good doc you can find here about OAuth2 framework implemented in Spring:http://projects.spring.io/spring-security-oauth/docs/oauth2.html

Some pieces of code:

  1. On AuthService

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
    public final static String RESOURCE_ID = "server-resource";
    
    @Value("${jwt.publicKey}")
    private String publicKey;
    
    @Value("${jwt.privateKey}")
    private String privateKey;
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }
    
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey(publicKey);
        converter.setSigningKey(privateKey);
        return converter;
    }
    
    @Bean
    public TokenEnhancer customTokenEnhancer() {
        return new CustomTokenEnhancer();
    }
    
    @Override
    public void configure(ClientDetailsServiceConfigurer client) throws Exception {
        client.inMemory()
            .withClient("client")
            .secret("clientsecret")
            .scopes("read", "write")
            .resourceIds("user")
            .authorizedGrantTypes("password", "refresh_token", "authorization_code")
            .authorities("ROLE_TRUSTED_CLIENT")
            .accessTokenValiditySeconds(tokenExpire) // one day available
            .refreshTokenValiditySeconds(refreshExpire);
    }
    
    @Override
    public void configure(AuthorizationServerSecurityConfigurer server) throws Exception {
        server
            .tokenKeyAccess("hasAuthority('ROLE_TRUSTED_CLIENT')")
            .checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')"); 
    }
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
            .tokenStore(tokenStore())
            .authenticationManager(authenticationManager)
            .accessTokenConverter(accessTokenConverter());
    }
    }
    

About public and private keys: The private key must be known only by AuthServer and the public key must be passed in any service including AuthService. You can generate a public and private key here:http://travistidwell.com/jsencrypt/demo/ and add these keys in application.yml file and pass into the configuration class with @Value.

  1. On Resource server

    @Configuration
    @EnableResourceServer
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class OAuth2ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    
    @Value("${jwt.publicKey}")
    private String publicKey;
    
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    
    @Bean
    protected JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey(publicKey);
        return converter;
    }
    
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources
            .tokenStore(tokenStore())
            .resourceId("user");
    }
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests().antMatchers("/**").permitAll();
    }
    
    }
    

Only thing you must to do is to create a configuration class for resource services (AccountService) to decode the access_token and check if the user has the ROLE to do something... Here you must pass only the public key in the same way application.yml file.

About @EnableGlobalMethodSecurity(prePostEnabled = true) annotation you are able to add @preauthorize annotation on controller methods.

Upvotes: 1

Related Questions