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 ?
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:
And the flow should be like this: https://www.rfc-editor.org/rfc/rfc6749#page-7
Short brief about flow:
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:
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
.
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.