Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Advanced Microservices Security with Spring and OAuth2

DZone's Guide to

Advanced Microservices Security with Spring and OAuth2

The main purpose of this article is to show a sample security architecture for microservices and an authorization server behind API gateways.

· Security Zone
Free Resource

Address your unique security needs at every stage of the software development life cycle. Brought to you in partnership with Synopsys.

Preface

A basic sample and theoretical introduction on how Spring Cloud Security and OAuth2 works are available in my blog entry Microservices security with OAuth2. Below you can see a picture illustrating the architecture of our solution. We have two microservices, OAuth2 authentication server, and Eureka discovery service behind Zuul gateway. 

Image title

Gateway

Let's start from an API gateway as an entry point into our system. Following Spring Cloud Security Documentation we add  @EnableOAuth2Sso into module main class. Thus, the gateway is forwarding OAuth2 access tokens downstream to the services it is proxying. It also sets form-based protection of our resources. All requests are redirecting to a login page if the user is not authenticated. Here's our login page.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Login page</title>
    <meta charset="utf-8" />
  </head>
  <body>
    <h1>Login page</h1>
    <p>Example user: user / password</p>
    <p th:if="${loginError}" class="error">Wrong user or password</p>
    <form th:action="@{/login}" method="post">
      <label for="username">Username</label>:
      <input type="text" id="username" name="username" autofocus="autofocus" /> <br />
      <label for="password">Password</label>:
      <input type="password" id="password" name="password" /> <br />
      <input type="submit" value="Log in" />
    </form>
    <p><a href="/index" th:href="@{/index}">Back to home page</a></p>
  </body>
</html>

We also have to initialize MVC view resolver for login and index views.

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/login").setViewName("login");
    registry.addViewController("/").setViewName("index");
  }

}

We are also using Thymeleaf for our view template engine and Thymeleaf - Spring Security integration modules in order to utilize the sec:authentication and sec:authorize attributes. That library has some other useful features. It will automatically add the CSRF token to our login form and helps us resolve templates from /src/main/resource/templates directory. Surprisingly, that is not all - we also have to provide security configuration to allow the login page to display and disable basic security. In addition, we need to change default in-memory based authentication to JDBC-based. In this sample, we use a MySQL database:

@Configuration
@EnableWebSecurity
@Order(-10)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private DataSource dataSource;

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
      .anyRequest().authenticated()
      .and()
      .formLogin()
      .loginPage("/login")
      .permitAll()
      .and().httpBasic().disable();
  }

  @Override
  public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.jdbcAuthentication().dataSource(dataSource);
  }

}

Authorization

We initialize our authorization server using @EnableAuthorizationServer . Now it has basic security by default. But we have already configured form-based security on the gateway. The user is logged in there, so we would like to force him to login to the authorization server as well. The problem is that without authentication on our authorization server we will not be able to get an OAuth2 token. Even if we force him to login into the authorization server it won't authenticate the session started on the gateway. We can try it calling this URL in our web browser:

http://localhost:8765/uaa/oauth/authorize?response_type=token&client_id=acme&redirect_uri=http://example.com&scope=openid&state=48532

Generally, I spend some hours working on a solution to this problem. I checked out some samples  - here's a smart solution with JWT tokens: UAA Behind Zuul. But all of them, in my opinion, are some kind of workaround. So, I decided to use Spring Session project. It provides an API and implementations for managing a user’s session information. With this framework HTTP session started on Zuul gateway is stored in a database. We only have to annotate our gateway and authorization server main classes with  @EnableJdbcHttpSession  and provide Datasource connection properties with @Bean . Other steps are managed by Spring Boot out of the box. In this sample, MySQL was used. Spring Session manages the storing and getting of HTTP sessions using the two tables below:

CREATE TABLE SPRING_SESSION (
  SESSION_ID CHAR(36),
  CREATION_TIME BIGINT NOT NULL,
  LAST_ACCESS_TIME BIGINT NOT NULL,
  MAX_INACTIVE_INTERVAL INT NOT NULL,
  PRINCIPAL_NAME VARCHAR(100),
  CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (SESSION_ID)
) ENGINE=InnoDB;

CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES (
  SESSION_ID CHAR(36),
  ATTRIBUTE_NAME VARCHAR(200),
  ATTRIBUTE_BYTES BLOB,
  CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_ID, ATTRIBUTE_NAME),
  CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_ID) REFERENCES SPRING_SESSION(SESSION_ID) ON DELETE CASCADE
) ENGINE=InnoDB;

CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_ID);

Security configuration on the authorization server is really simple. All requests need to be authenticated.

@Configuration
@EnableWebSecurity
@Order(-10)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().authenticated();
  }

}

We need to add a dependency to Spring Session both on the gateway and authorization modules. After login has been performed on Zuul gateway, an HTTP session is stored in the MySQL database. After that, when we are calling OAuth2 with gateway's JSESSIONID authorization server, find it in the database and successfully authenticate it. A generated token is returned to the caller. Here's a complete list of dependencies from pom.xml.

<dependencies>
  <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>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
  <dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session</artifactId>
  </dependency>
</dependencies>

Services

There are two microservices defined in account-service and customer-service modules. Sample application source code is available on GitHub (branch advanced). Here's a fragment of the application.yml configuration on gateway. Zuul has to route to Account, Customer services, and Authorization server.

zuul:
  routes:  
    uaa:
      path: /uaa/**
      sensitiveHeaders:
      serviceId: auth-server
    account:
      path: /account/**
      sensitiveHeaders: 
      serviceId: account-service
    customer:
      path: /customer/**
      sensitiveHeaders: 
      serviceId: customer-service

eureka:
  client:
    registerWithEureka: false
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

security:
  user:
    name: root
    password: password
  oauth2:
    client:
      accessTokenUri: http://localhost:8765/uua/oauth/token
      userAuthorizationUri: http://localhost:8765/uua/oauth/authorize
      clientAuthenticationScheme: form
    resource:
      userInfoUri: http://localhost:8765/uaa/user
      preferTokenInfo: false  
  sessions: ALWAYS 

After calling http://localhost:8765 in the web browser you will see a Login page. Enter your username, and the password you inserted in the database users table. 

You should now be logged in. Now you can send a token request to the authorization server by calling the address below. Then you should see an OAuth Approval page and after approval, the authorization server sends you a response with the requested OAuth2 token.

 http://localhost:8765/uaa/oauth/authorize?response_type=token&client_id=acme&redirect_uri=http://example.com&scope=openid&state=48532

Image title

The final response is visible below:

http://example.com/#access_token=60519c8b-42fa-4995-8192-866e266f9cec&token_type=bearer&state=48532&expires_in=43199

And finally, you can call your endpoint in your service with HTTP header Authorization:"Bearer 60519c8b-42fa-4995-8192-866e266f9cec".

 

Find out how Synopsys can help you build security and quality into your SDLC and supply chain. We offer application testing and remediation expertise, guidance for structuring a software security initiative, training, and professional services for a proactive approach to application security.

Topics:
oauth2 ,java ,mysql ,security

Published at DZone with permission of Piotr Mińkowski. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}