Securing an application

In a typical monolithic application, when the user logs in, an HTTP session will be created to hold user-specific information, which will be then used until the session expires. The session will be maintained by a common security component on the server side and all the requests are passed through it. So, it is straightforward to handle user authentication and authorization in a monolithic application.

If we want to follow the same pattern for microservice architecture, we need to implement a security component at every microservice level as well as in a central place (the gateway API) from where all the requests are routed. This is because microservices interact over the network, so the approach of applying security constraints is different.

Using Spring Security is a standard practice to meet the security needs of Spring-based Java applications. For microservices, Spring Cloud Security (another component from Spring Cloud) provides a one-stop solution to integrate Spring Security features with various components of the microservice architecture, such as the gateway proxy, a configuration server, load balancers, and so on.

In a microservice environment, security concerns can be addressed through widely used standard security protocols such as OAuth2 and OpenID Connect. In Chapter 4Building a Central Authentication Server, we talked about OAuth2 in detail. Now, we will see how it can be used to meet security needs in a microservice architecture.

Let's see how the OAuth security system works in a microservice architecture. The high-level flow of authorization looks as follows: 

To understand the series of actions, let's take a use case of an order being placed by a user of the online bookstore. The whole process happens in the following steps:

  1. A user tries to access the order page through the Zuul proxy server (API gateway) and does not have a session or an access token.
  2. The Zuul proxy then redirects a user to an authorization server with pre-configured parameters such as grant type, client ID, token URL, and authorization URL. 
  3. If the user does not log in, the authorization server redirects to the login page. 
  4. Once the user does log in with valid credentials, the authorization server generates a token and sends it back to the API gateway.
  5. On receiving the token, the API gateway (the Zuul proxy server) propagates the token downstream to the microservices it is proxying. 
  6. For restricted resources, the system will check whether a valid token exists. If not, the user will be redirected to the login page (or the token will be refreshed based on the grant type configured in the system).

The authentication server will be implemented as a separate microservice and registered in the Eureka discovery server. It can be created as a Spring Boot application with security-specific starter dependencies as follows:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>

Spring Cloud Security has different starters for OAuth and standard Spring Security for the microservice architecture. Next, we will add the required configurations to make this application an authorization server, as follows:

@Configuration
@EnableAuthorizationServer
public class CustomAuthorizationConfig extends AuthorizationServerConfigurerAdapter {

@Autowired
@Qualifier("authenticationManager")
private AuthenticationManager authenticationManager;

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("testClientId")
.secret(new BCryptPasswordEncoder().encode("test123"))
.authorizedGrantTypes("authorization_code", "refresh_token", "implicit", "password", "client_credentials")
.scopes("registeredUser","admin")
.redirectUris("http://localhost:8781/inventory-test/api/inventory/home")
.resourceIds("oauth2-server");
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
return converter;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Override
public void configure(
AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenServices(tokenServices())
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
@Bean("resourceServerTokenServices")
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new
DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(false);
defaultTokenServices.setAccessTokenValiditySeconds(120);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}

}

The @EnableAuthorizationServer annotation is used to declare the component as an authorization server. OAuth can be done with various third-party clients, and Spring Security provides support for Google, Facebook, Okta, and GitHub out of the box. In our case, we will define a custom authorization server.

The configure(ClientDetailsServiceConfigurer clients) method of this class is used to define configuration for the custom authorization client. It initializes the client with various configurations, such as ClientId, secret (a kind of client password), possible authorization grant types you want the client to support, various scopes that can be used to fine-tune access control, and user authority and resourceId

Spring OAuth is flexible enough to allow various mechanisms to generate the access token, and JWT is one of them. The tokenStore() and tokenService() methods are used to apply the required configuration for JWT. The configure(AuthorizationServerEndpointsConfigurer endpoints) method is used to configure tokens, along with the authentication manager. The AuthenticationManager object is injected from the WebSecurityConfig class as follows:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

@Override
@Bean("authenticationManager")
public AuthenticationManager authenticationManagerBean() throws
Exception {
AuthenticationManager authenticationManager =
super.authenticationManagerBean();
return authenticationManager;
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**");
web.ignoring().antMatchers("/css/**");
}
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication()
.withUser("john").password(
encoder().encode("999")).authorities("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/oauth/authorize","/oauth/token","/").permitAll()
.and()
.formLogin().loginPage("/login").permitAll();
}
@Bean("encoder")
public BCryptPasswordEncoder encoder(){
return new BCryptPasswordEncoder();
}
}

This class is responsible for configuring various endpoints, static resources, and the login page, along with the authentication mechanism. This is all about authorization server configuration. As we know, all requests are routed through the Zuul proxy server (an API Gateway), so we must configure it to route the requests for restricted resources to the authorization server.

The authorization server provides an access token that will be routed along with the request (in the header). When other microservices read it, they will verify the access token with the authorization server to allow the user to access restricted resources. In short, access tokens will be routed to various microservices. This requires some sort of SSO implementation, and with Spring Cloud Security, we can do it. 

Additionally, particular functionality (for example, placing an order) initiated by a user will eventually involve interaction with other microservices along with the Zuul proxy server, so they are considered resource servers in OAuth terminology. First, add the @EnableOAuth2Sso annotation to a bootstrap class of the Zuul proxy application, as follows: 

@EnableZuulProxy
@EnableOAuth2Sso
@EnableDiscoveryClient
@SpringBootApplication
public class ZuulApiGatewayApplication {
...
}

This annotation allows the Zuul proxy server to forward the access token generated by the authorization server downstream to other services involved in processing the request. The resource server configuration for the Zuul proxy server, as well as other microservices, should be as follows:

@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{

private static final String RESOURCE_ID = "oauth2-server";
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
.tokenStore(tokenStore())
.resourceId(RESOURCE_ID);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new RequestHeaderRequestMatcher("Authorization"))
.authorizeRequests()
// Microservice specific end point configuration will go here.
.antMatchers("/**").authenticated()
.and().exceptionHandling().accessDeniedHandler(new
OAuth2AccessDeniedHandler());
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
return converter;
}
}

The @EnableResourceServer annotation will enable the component as a resource server. resourceId should be the same as we configured in the authorization server. Also, we are using the same JWT token configuration that we set in the authorization server. The configure method is used to set the configuration for individual microservice endpoints. 

We also need to set certain properties in the application.properties file, which will be used by the resource server to interact with the authorization server as follows:

#security.oauth2.sso.login-path=/login
security.oauth2.client.access-token-uri=http://localhost:9999/oauth/token
security.oauth2.client.user-authorization-uri=http://localhost:9999/oauth/authorize
security.oauth2.client.client-id=testClientId
security.oauth2.client.client-secret=test123
security.oauth2.client.scope=registeredUser,admin,openid
security.oauth2.client.grant-type=implicit
security.oauth2.resource.id=oauth2-server
security.oauth2.resource.jwt.key-value=123

The authorization server is configured to access localhost:9999. The resource server configuration, along with previous properties, need to be placed with every microservice that we want to access securely through OAuth.  

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.184.90