How to Implement Oauth2 Security in Microservices
Implementing Oauth2 Security in microservices distributed systems using Oauth2, Oauth2-Client, Spring Cloud and Netflix components with full example.
Join the DZone community and get the full member experience.
Join For FreePurpose
I wanted a solution where we can easily captured Oauth2 and Oauth2
client for the secure communication with all of the microservices
. Focusing, how to achieve oauth2 full flavor into microservices
architecture. User can’t access API without token. The token will be available when user given basic and authentication details to generate token for access API.
All requests will consider one entry point API-Gateway but, service-to-service can communicate. The API-Gateway will dynamic routing using Zuul Netflix OSS
component. Every request will check authorization when request will arrived into service and service will request authorization server to verify is either authenticate or not. The entire Meta configuration settled into the central configuration on github
(You can manage on any repository).
Goal
- Achieve authentication/authorization, based on
Spring security, Oauth2, Oauth2 client
- Understanding microservices architecture using
Spring Cloud
,Netfllix OSS
. - Demonstration of microservice architecture based on
Java, Spring and Oauth2.
What Are Microservices?
Microservices is a service-oriented architecture pattern where in applications are built as a collection of various smallest independent service units. It is a software engineering approach which focuses on decomposing an application into single-function modules with well-defined interfaces. These modules can be independently deployed and operated by small teams who own the entire life-cycle of the service.
Spring Cloud and Microservice
Firstly, we do not write a microservice. We write a service which eventually will be called microservice when deployed with other services to form an application.
Having said that, Spring cloud just gives you abstractions over some set of tools (eureka, zuul, feign, ribbon
etc.) making it easy for you to integrate with spring applications.
However, you can also achieve microservice architecture without using spring cloud. You can take advantage of tools like Kubernetes, docker swarm, haproxy, Kong, nginx
etc to achieve the same. The advantage of not using spring cloud has its own pros and cons and vice versa.
High Level Microservice Architecture With Authorizations
Here,
- User login into the system using basic authorization and login credentials.
- User will got token if user basic auth and login credentials is matched.
- Next, user send request to access data from service. the API gateway recive the request and check with authorization server.
- Every request have one entry point API Gateway
- Security checking and dynamically routing to the service
- Every service have single database to manipulate data.
Spring Cloud Key Concept and Features
- Spring Cloud works for microservices to manage configuration
- Intelligent routing and services discovery
- Service -to- service call
- Load balancing (It is proper distributed network traffic to the backend server)
- Leadership election (The Application work with another application as a third-party system)
- Global Lock (Two thread are not accessed simultaneously for the same resource same time)
- Distributed configuration and messaging
If you want to avail many services in one application, then the cloud-based application is an easy way. Spring Cloud works is the same way.
Spring Boot Key Concept and Features
- Spring boot works to create microservices
- Spring Application create stand-alone spring application
- Web Application HTTP Embedded (
Tomcat, JTTY or Undertrow
) No need to deploy war file. - Externalized Configuration
- Security (It is secure in built with basic authentication on all http endpoint)
- Application Event and Listener
- Spring Boot works on product-based web application. It used for unit test development and integration test time reduce.
Spring Cloud Advantage:
- It is provided cloud service development
- It is microservice-based architecture and configuration
- It provides inter service communication
- It is based on Spring Boot model.
Spring Cloud Have Main 5 Annotations:
1. @EnableConfigServer
This Annotation converts the application into server in which more application use to get their configuration.
2. @EnableEurekaServer
This annotation is used for Eureka Discovery Services for other application which can be used to locate services using it.
3. @EnableDiscoveryClient
Helping of this annotation application register in the service discovery, it discovers others services using it.
4. @EnableCircuitBreaker
Use the circuit breaker pattern to continue operating when related service fail and prevent cascading failure. This annotation used for Hystrix Circuit Breaker.
5. @HyStrixCommand(fallbackmethod=”MethodName”)
Hystrix is a Latency and Fault Tolerance Library for Distributed Systems.
Overview of Netflix Components
Spring Cloud Netflix provides Netflix OSS
integrations for Spring Boot apps through autoconfiguration and binding to the Spring Environment and other Spring programming model idioms. With a few simple annotations you can quickly enable and configure the common patterns inside your application and build large distributed systems with battle-tested Netflix components. The patterns provided include Service Discovery (Eureka)
, Circuit Breaker (Hystrix)
, Intelligent Routing (Zuul)
and Client-Side Load Balancing (Ribbon)
.
Eureka (Service Registration and Discovery)
- REST service which registers itself at the registry (Eureka Client) and
- Web application, which is consuming the REST service as a registry-aware client (Spring Cloud Netflix Feign Client).
Ribbon (Dynamic Routing and Load Balancer)
- Ribbon primarily provides client-side load balancing algorithms.
- APIs that integrate load balancing, fault tolerance, caching/batching on top of other ribbon modules and Hystrix
- REST client built on top of Apache HttpClient integrated with load balancers (deprecated and being replaced by ribbon module
- Configurable load-balancing rules
Hystrix (Circuit Breaker):
- Hystrix is a fault tolerance java library. This tool is designed to separate points of access to remote services, systems, and 3rd-party libraries in a distributed environment like Microservices. It improves overall system by isolating the failing services and preventing the cascading effect of failures.
Zuul (Edge Server):
- Zuul is the front door for all requests from devices and web sites to the backend of the Netflix streaming application.
- Zuul will serve as our API gateway
- Handle dynamic routing
- Zuul is built to enable dynamic routing, monitoring, resiliency and security.
What Is a Feign Client?
Netflix provides Feign as an abstraction over REST-based calls, by which microservices
can communicate with each other, but developers do not have to bother about REST internal details.
Feign Client, which works on the declarative principle. We must create an interface/contract, then Spring creates the original implementation on the fly, so a REST-based service call is abstracted from developers. Not only that — if you want to customize the call, like encoding your request or decoding the response in a Custom Object, you can do it with Feign in a declarative way. Feign, as a client, is an important tool for microservice
developers to communicate with other microservices
via Rest API.
The Feign client uses a declarative approach for accessing the API. To use it, we must first enable the Spring Cloud support for it on our Spring Boot Application with the @EnableFeignClients
annotation at the class level on a @Configuration
class.
Server Side Load Balancing:
In JavaEE architecture, we deploy our war/ear files into multiple application servers, then we create a pool of server and put a load balancer (Netscaler)
in front of it, which has a public IP. The client makes a request using that public IP, and Netscaler decides in which internal application server it forwards the request by round robin or sticky session algorithm. We call it server side load balancing.
Technology Stack:
- Java 8+
- Spring Latest
- Spring security
- Oauth2, Oauth2 Client
- Spring Cloud
- Netflix OSS
- PostgreSQL
- IntliJ
Create Project Structure:
Step 1: Create Project "central configuration"
for All of the Services
With microservices, we create a central config server where all configurable parameters of micro-services are written version controlled. The benefit of a central config server is that if we change a property for a microservice, it can reflect that on the fly without redeploying the microservice.
You can create project use this link: https://start.spring.io/.
application.properties
xxxxxxxxxx
spring.application.name=ehealth-central-configuration
server.port=8888
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
# available profiles of the application
spring.profiles.active=local,development,production
spring.cloud.config.server.git.uri=https://github.com/amran-bd/cloud-config
spring.cloud.config.server.git.clone-on-start=true
spring.cloud.config.server.git.search-paths=patient-management-service,ehealth-api-gateway,eureka-service-discovery,clinic-management-service
management.security.enabled=false
#To remove WAR - Could not locate PropertySource: None of labels [] found
health.config.enabled=false
# To remove I/O Issue Could not locate PropertySource: I/O error on GET request for
spring.cloud.config.enabled=false
Hints: You can use your git server or local machine, New service name will be add if new service introduce..
If u want you can used my git repository:
https://github.com/amran-bd/cloud-config
EhealthCentralConfigurationApplication.Java
Class example:
xxxxxxxxxx
package com.amran.central.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
public class EhealthCentralConfigurationApplication {
public static void main(String[] args) {
SpringApplication.run(EhealthCentralConfigurationApplication.class, args);
}
}
You must include annotation @EnableConfigServer
Example of create central configuration for services:
Hints: create folder <projectName> / <projectName-development.properties>
Step 2 Create Project "Discovery Server"
for all of the Discoverable Services.
About the discovery server — already discussed in this article.
bootstrap.properties
xxxxxxxxxx
spring.application.name=eureka-service-discovery
spring.profiles.active=development
# ip and port of the config server
spring.cloud.config.uri=http://localhost:8888
# expose actuator endpoints
management.endpoints.web.exposure.include=refresh
management.security.enabled=false
spring.cloud.config.fail-fast=true
Here,
We can enable and disable other actuator endpoints through property files.
If you want to enable all actuator endpoints, then add following property.management.endpoints.web.exposure.include=*
To enable only specific actuator endpoints, provide the list of endpoint id.
management.endpoints.web.exposure.include=health,info,
beans,env
In some cases, it may be desirable to fail startup of a service if it cannot connect to the Config Server. If this is the desired behavior, set the bootstrap configuration property spring.cloud.config.fail.Fast=true
and the client will halt with an Exception.
EurekaServiceDiscoveryApplication.java
Class Example:
xxxxxxxxxx
package com.amran.service.discovery;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
public class EurekaServiceDiscoveryApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServiceDiscoveryApplication.class, args);
}
}
You must include annotation @EnableEurekaServer.
Step 3 Create Project "API Gateway
" for All of the Services Entry Point.
This is the most valuable potion. Here, we write the authorization server in the same project
API Gateway Project Structure
application.yml
xxxxxxxxxx
#hystrix:
# command:
# default:
# execution:
# isolation:
# thread:
# timeoutInMilliseconds: 5000
hystrix:
command:
clinic-management-service:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
patient-management-service:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
Here, Define time-out for every separate services, you can used default. for more information please visit: https://www.cars24.com/blog/hystrix-how-to-handle-cascading-failures-in-microservices/
bootstrap.properties
xxxxxxxxxx
spring.application.name=ehealth-api-gateway
spring.profiles.active=development
# ip and port of the config server
spring.cloud.config.uri=http://localhost:8888
# expose actuator endpoints
management.endpoints.web.exposure.include=refresh
management.security.enabled=false
spring.cloud.config.fail-fast=true
Central Configuration Example:
ehealth-api-gateway-development.properties
x
spring.application.name=ehealth-api-gateway
server.port=8080
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
## PostgreSQL
spring.datasource.url=jdbc:postgresql://localhost:3307/ehealth-security
spring.datasource.username=postgres
spring.datasource.password=test1373
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
# Hikari will use the above plus the following to setup connection pooling
spring.datasource.hikari.minimumIdle=3
spring.datasource.hikari.maximumPoolSize=500
spring.datasource.hikari.idleTimeout=30000
spring.datasource.hikari.poolName=SpringBootJPAHikariCP
spring.datasource.hikari.maxLifetime=2000000
spring.datasource.hikari.connectionTimeout=30000
spring.datasource.pool-prepared-statements=true
spring.datasource.max-open-prepared-statements=250
spring.jpa.hibernate.connection.provider_class=org.hibernate.hikaricp.internal.HikariCPConnectionProvider
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQL82Dialect
#Hibernate Configuration
spring.jpa.generate-ddl = true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
server.error.include-stacktrace=never
#feign.hystrix.enabled=true
#hystrix.shareSecurityContext=true
#All url come with prefix/api will interpret
zuul.prefix=/api
#Dynamic Service Registration in Eureka Server (API Gateway)
zuul.routes.patient-management-service.path=/patient-management-service/**
#zuul.routes.patient-management-service.url=http://localhost:8081
zuul.routes.patient-management-service.sensitive-headers
zuul.routes.patient-management-service.service-id=patient-management-service
zuul.routes.clinic-management-service.path=/clinic-management-service/**
#zuul.routes.patient-management-service.url=http://localhost:8082
zuul.routes.clinic-management-service.sensitive-headers
zuul.routes.clinic-management-service.service-id=clinic-management-service
Zuul filtered 4-types while doing dynamic routing.
Zuul filters
store request and state information in (and share it by means of) the RequestContext. You can use that to get at the HttpServletRequest and then log the HTTP method and URL of the request before it is sent on its way.
error filter, pre filter, post filter, route filter
xxxxxxxxxx
package com.amran.api.gateway.filter;
import com.netflix.zuul.ZuulFilter;
/**
* @Author : Amran Hosssain on 6/27/2020
*/
public class RouteFilter extends ZuulFilter {
public String filterType() {
return "route";
}
public int filterOrder() {
return 1;
}
public boolean shouldFilter() {
return true;
}
public Object run() {
System.out.println("Inside Route Filter");
return null;
}
}
EhealthApiGatewayApplication.java
Class Example:
xxxxxxxxxx
package com.amran.api.gateway;
import com.amran.api.gateway.filter.PostFilter;
import com.amran.api.gateway.filter.PreFilter;
import com.amran.api.gateway.filter.ErrorFilter;
import com.amran.api.gateway.filter.RouteFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
public class EhealthApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(EhealthApiGatewayApplication.class, args);
}
public PreFilter preFilter() {
return new PreFilter();
}
public PostFilter postFilter() {
return new PostFilter();
}
public ErrorFilter errorFilter() {
return new ErrorFilter();
}
public RouteFilter routeFilter() {
return new RouteFilter();
}
}
Already discussed EhealthApiGatewayApplication.java
class Annotation. Please, see above if not clear.
Spring security and Oauth2 implementation in microservices architecture:
You must be expert in spring security and oauth2. I have done Oauth2 implementation base on Spring security. please, visit my github link.
Step 4 Create a Project "patient-management-service"
Patient-related Data will Be Manipulated
POM.xml
xxxxxxxxxx
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.amran.patient.management</groupId>
<artifactId>patient-management-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>patient-management-service</name>
<description>patient-management-service project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR5</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.5.0.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<filtering>true</filtering>
<directory>src/main/resources</directory>
<includes>
<include>*.properties</include>
</includes>
</resource>
</resources>
</build>
</project>
bootstrap.properties
x
server.url = Patient Management Service Working...
spring.application.name=patient-management-service
spring.profiles.active=development
# ip and port of the config server where we can get our central configuration.
spring.cloud.config.uri=http://localhost:8888
# expose actuator endpoints
management.endpoints.web.exposure.include=refresh
management.security.enabled=false
spring.cloud.config.fail-fast=true
##Security parameter for request verification ##
#we consider basic authorization and Token. In auth server verified this token generated by authorization server (Self) based below criteria.
client_id=kidclient
client_credential = kidsecret
check_authorization_url = http://localhost:8080/oauth/check_token
resources_id = ehealth
PatientManagementServiceApplication.java
class example:
xxxxxxxxxx
package com.amran.patient.management;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
public class PatientManagementServiceApplication {
public static void main(String[] args) {
SpringApplication.run(PatientManagementServiceApplication.class, args);
}
}
You must be annotated @EnableDiscoveryClient
in the class. Because of Eureka server will be discovered as service or client..
Security Oauth2-Client:
ResourceServerConfig.java, WebSecurityConfig.Java
class example:
You need a WebSecurityConfigurerAdapter
to secure the /authorize endpoint and to provide a way for users to authenticate. A Spring Boot application would do that for you (by adding its own WebSecurityConfigurerAdapter
with HTTP basic auth). It creates a filter chain with order=0 by default, and protects all resources unless you provide a request marcher.
The @EnableResourceServer
does something similar, but the filter chain it adds is at order=3 by default. WebSecurityConfigurerAdapter
has an @Order(100) annotation. So first the ResourceServer will be checked (authentication) and then your checks in your extension of WebSecurityConfigureAdapter will be checked.
xxxxxxxxxx
package com.amran.patient.management.security;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
/**
* @Author : Amran Hosssain on 6/27/2020
*/
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
"${resources_id}") (
private String resourceId;
public void configure(HttpSecurity http) throws Exception {
http
.headers().frameOptions().disable()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/eureka/**").permitAll()
.anyRequest()
.authenticated();
}
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(resourceId);
}
}
xxxxxxxxxx
package com.amran.patient.management.security;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
/**
* @Author : Amran Hosssain on 6/27/2020
*/
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
"${client_id}") (
private String clientId;
"${client_credential}") (
private String clientSecret;
"${check_authorization_url}") (
private String checkAuthUrl;
public ResourceServerTokenServices tokenServices() {
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId(clientId);
tokenServices.setClientSecret(clientSecret);
tokenServices.setCheckTokenEndpointUrl(checkAuthUrl);
return tokenServices;
}
public AuthenticationManager authenticationManagerBean() throws Exception {
OAuth2AuthenticationManager authenticationManager = new OAuth2AuthenticationManager();
authenticationManager.setTokenServices(tokenServices());
return authenticationManager;
}
}
Let's Start Demo:
Step 1 — Generate Token:
Project Run Sequence: CentralConfigServer->DiscoveryServer->API Gateway Server-> Others Service.
Client Details In Database:
User Record:
Generate Token:
Call Patient Management Service (Zuul Dynamic Routing):
Direct Call Patient Service (Token Verify from Auth Server) without token u can't call anyway.
Call Clinic Management Service (Zuul Dynamic Routing):
Conclusion
I am trying to show Oauth2 implementation in microservice architecture with secure communication, Single entry point, Dynamic Routing, Fail back Solutions, centralized configurations and Oauth2-client implementation in service to secure every API and every request ensure authorization.
Full Source Code Here:
https://github.com/amran-bd/Oauth2Secure-microservices-architecture-apiGateway-springCloud-netflixOSS-PostgreSQL-full-demo
Opinions expressed by DZone contributors are their own.
Comments