Build J2EE Microservices Architecture
This post will cover the following aspects: Keycloak setup, Eureka service registration and discovery, Spring Cloud API gateway, and more!
Join the DZone community and get the full member experience.
Join For FreeI posted an article in regards to a single-page application(UI), but in this post, I'm going to introduce how to build microservice architecture for the J2EE application with Spring framework and open-source SSO framework Keycloak. This post will cover the following aspects:
- Keycloak setup
- Eureka service registration and discovery
- Spring Cloud API gateway
- Spring Security (OAuth2 login) and the integration with Keycloak
- Microservices
The code is available in my Github and please check the docker-compose.yml at first so that you can read the rest of the post easier. One thing I need to mention here is you need to replace the IP address of the keycloak server URL with your own before running the docker containers.
xxxxxxxxxx
version'3.4'
services
api-gateway
build
context ./api-gateway
ports
"8080:8080"
restart on-failure
environment
#overriding spring application.properties
eureka.client.serviceUrl.defaultZone=http://eureka-server:9091/eureka/
# use host name or ip of the host machine keycloak-client.server-url=http://10.0.0.17:18080/auth
depends_on
eureka-server
eureka-server
build
context ./eureka-server
ports
"9091:9091"
restart on-failure
microservice-consumer
build
context ./microservice-consumer
ports
"9080:9080"
restart on-failure
environment
#overriding spring application.properties
eureka.client.serviceUrl.defaultZone=http://eureka-server:9091/eureka/
# use host name or ip of the host machine keycloak-client.server-url=http://10.0.0.17:18080/auth
depends_on
eureka-server
microservice-producer
build
context ./microservice-producer
ports
"9081:9081"
restart on-failure
environment
#overriding spring application.properties
eureka.client.serviceUrl.defaultZone=http://eureka-server:9091/eureka/
# use host name or ip of the host machine keycloak-client.server-url=http://10.0.0.17:18080/auth
depends_on
eureka-server
keycloak
image jboss/keycloak11.0.0
volumes
./keycloak-server/realm-export.json:/tmp/keycloak/config/realm-export.json
environment
KEYCLOAK_USER admin
KEYCLOAK_PASSWORD admin
KEYCLOAK_IMPORT /tmp/keycloak/config/realm-export.json
DB_VENDOR POSTGRES
DB_ADDR postgres
DB_DATABASE keycloak
DB_USER keycloak
DB_SCHEMA public
DB_PASSWORD password
ports
"18080:18080"
command
"-b"
"0.0.0.0"
"-Djboss.socket.binding.port-offset=10000"
restart on-failure
depends_on
postgres
postgres
image postgres
volumes
postgres_data:/var/lib/postgresql/data
environment
POSTGRES_DB keycloak
POSTGRES_USER keycloak
POSTGRES_PASSWORD password
volumes
postgres_data
name keycloak_postgres_data
driver local
Then, you can run the following cmd to start up docker containers.
xxxxxxxxxx
docker-compose up --build
1. Keycloak
x
docker-compose up --build keycloak
1.1 Create a Realm
Login http://localhost:18080/ with admin/admin, then create a realm as below, leaving all the fields as default.
1.2 Create Clients Under the Above Realm
As shown above, we created three clients that represent API-gateway, microservice consumer, microservice producer. There are two things necessary to mention.
First, fill mandatory field(Valid Redirect URIs) as * so that we can redirect back to our URI configured in our application.
Then, choose client id and secrete as Client Authenticator, which is suitable for the integration with Spring Security OAuth2 client.
1.3 Create a User
As shown in the screenshot below, we need to fill some necessary fields like first name, last name, and email, etc.
2. Eureka Sever — Service Registration
It's quite straight forward to build a service registration server based on Spring Cloud Eureka, and we just need annotation @EnableEurekaServer to enable that.
3. API Gateway
Spring framework 5 releases Spring Cloud gateway to replace the previous gateway — Spring Cloud Zuul. Spring Cloud Gateway is a new generation nonblocking gateway built on top of Project Reactor, Spring WebFlux, and Spring Boot 2.0, while Zuul is a blocking gateway. So, in this post, I use Spring Cloud Gateway as an API gateway for the microservices.
First of all, I need to show the application.yml configuration profile.
xxxxxxxxxx
logging
level
root WARN
org.springframework.web INFO
org.springframework.security DEBUG
org.springframework.security.oauth2 DEBUG
server
port8080
keycloak-client
server-url http //localhost 18080/auth
realm spring-micro-main
spring
application
name api-gateway
security
oauth2
client
registration
keycloak
provider keycloak
client-id spring-micro-gateway
client-secret 756b0558-018b-4809-b478-bd5b4995d325
authorization-grant-type authorization_code
redirect-uri http //localhost 8080/login/oauth2/code/keycloak
scope openid
provider
keycloak
authorization-uri $ keycloak-client.server-url /realms/$ keycloak-client.realm /protocol/openid-connect/auth
token-uri $ keycloak-client.server-url /realms/$ keycloak-client.realm /protocol/openid-connect/token
user-info-uri $ keycloak-client.server-url /realms/$ keycloak-client.realm /protocol/openid-connect/userinfo
jwk-set-uri $ keycloak-client.server-url /realms/$ keycloak-client.realm /protocol/openid-connect/certs
user-name-attribute name
user-info-authentication-method header
resourceserver
jwt
jwk-set-uri $ keycloak-client.server-url /realms/$ keycloak-client.realm /protocol/openid-connect/certs
cloud
gateway
routes
id microservice-consumer
uri lb //microservice-consumer
predicates
Path=/api/consume/**
filters
TokenRelay=
RemoveRequestHeader=Cookie
id microservice-producer
uri lb //microservice-producer
predicates
Path=/api/produce/**
filters
TokenRelay=
RemoveRequestHeader=Cookie
eureka
client
serviceUrl
defaultZone http //localhost 9091/eureka/
3.1 Route Definition in Spring Cloud Gateway
We defined two routes for two microservices: consumer and producer. The lb://microservice-consumer means it points to microservice-consumer based on service registration server - Eureka. We use TokenRelay so that the access_token from the request can be passed through to the microservice resource server which is secured by Spring Security OAuth2 along with Keycloak as well.
3.2 Eureka Client Configuration
The API gateway also acts as a Eureka client so that it can work with microservice replicas. We just need an annotation @EnableEurekaClient and configure the eureka server URL in the properties.
xxxxxxxxxx
eureka:
client:
serviceUrl:
defaultZone: http://localhost:9091/eureka/
3.3 Spring Security OAuth2 and the Integration With Keycloak Server
3.3.1 Spring Security OAuth2
One of the key features of Spring Security 5 is the native support for OAuth2 and OIDC, instead of the legacy client support in the old Spring Security OAuth subproject, integrating with IAM(Identity and Access Management) providers gets super easy. We need to add the following dependencies:
x
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
3.3.2 OAuth2 Client Registration
The properties for OAuth 2 clients are prefixed with spring.security.oauth2.client.registration andspring.security.
oauth2.client.provider
. For Keycloak specifically, we have to configure the details of the OAuth2 provider and provides the details of client registration as below.
x
spring
security
oauth2
client
registration
keycloak
provider keycloak
client-id spring-micro-consumer
client-secret b2678444-3e56-466d-b035-a6109ca686ca
authorization-grant-type authorization_code
redirect-uri http //localhost 9080/login/oauth2/code/keycloak
scope openid
provider
keycloak
authorization-uri $ keycloak-client.server-url /realms/$ keycloak-client.realm /protocol/openid-connect/auth
token-uri $ keycloak-client.server-url /realms/$ keycloak-client.realm /protocol/openid-connect/token
user-info-uri $ keycloak-client.server-url /realms/$ keycloak-client.realm /protocol/openid-connect/userinfo
jwk-set-uri $ keycloak-client.server-url /realms/$ keycloak-client.realm /protocol/openid-connect/certs
user-name-attribute name
user-info-authentication-method header
resourceserver
jwt
jwk-set-uri $ keycloak-client.server-url /realms/$ keycloak-client.realm /protocol/openid-connect/certs
3.3.3 Spring Security OAuth2 Resource Server
Keycloak uses a public/private key pair to issue and verify the JWT(JSON Web Token). In particular, it uses a private key to sign the access token, and the OAuth2 client uses the public key to verify the token.
We need to protect the API gateway as an OAuth2 resource server so that when a request coming in with a bearer token under the Authorization header, it can also verify the token rather than redirect to the SSO server for authentication, then the request can eventually go to downstream microservices. For example, we can use an HTTP client to issue a request as below.
xxxxxxxxxx
GET localhost:8080/api/produce/
Authorization: Bearer <your access token>
3.3.4 Enable OAuth2 Login and Resource Server
To enable Spring Security OAuth2 login as well as a resource server, we need to leverage DSL methods.
Spring Security’s OAuth 2.0 Login support is enabled via the Spring Security oauth2Login()
DSL method.
Spring Security’s Resource Server support is enabled via the Spring Security oauth2ResourceServer
DSL method.
Illustrated as below:
xxxxxxxxxx
public class SpringSecurityConfig {
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2Login()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
// @formatter:on
}
}
4. Microservices
Microservices should be protected by Keycloak and enabled as the OAuth2 resource server as well. So the configuration is similar to the API gateway. One key difference is that Spring Cloud gateway is built on top of Spring WebFlux while our microservices are built on top of Spring MVC. So the security configuration has a slight difference, illustrated as below.
xxxxxxxxxx
public class OAuth2ResourceServerConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2Login()
.and()
.oauth2ResourceServer()
.jwt();
}
}
That's it and you are good to go. Please check the source code from my Github.
Published at DZone with permission of Kunkka Li. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments