Securing Your Spring Boot Applications with Keycloak in Kubernetes

Software Developer
Hey there, fellow developers! 👋
After spending countless hours configuring authentication for microservices in production environments, I've put together this guide to save you from the same headaches I experienced. Today, we're diving into integrating Spring Boot applications with Keycloak for authentication in Kubernetes - a powerful combination that provides robust security while maintaining scalability.
Why This Matters
Before we jump into the technical details, let's quickly address why this setup is worth your time. Modern applications require secure authentication, and managing users, roles, and permissions manually across multiple services is a nightmare. Keycloak centralizes this, while Kubernetes gives us the scalability and resilience we need. It's a match made in DevOps heaven!
Prerequisites
A working Kubernetes cluster (I'm assuming you already have this set up)
Basic knowledge of Spring Boot and OAuth2
Familiarity with Keycloak concepts
kubectl configured to work with your cluster
Helm (optional but recommended)
Part 1: Deploying Keycloak to Kubernetes
Let's start by setting up Keycloak in our Kubernetes cluster:
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
labels:
app: keycloak
spec:
replicas: 1
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
containers:
- name: keycloak
image: quay.io/keycloak/keycloak:20.0.3
args: ["start-dev"]
env:
- name: KEYCLOAK_ADMIN
value: "admin"
- name: KEYCLOAK_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-secret
key: admin-password
- name: KC_PROXY
value: "edge"
- name: KC_DB
value: "postgres"
- name: KC_DB_URL
value: "jdbc:postgresql://postgres:5432/keycloak"
- name: KC_DB_USERNAME
valueFrom:
secretKeyRef:
name: postgres-secret
key: username
- name: KC_DB_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /realms/master
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
resources:
limits:
memory: "1024Mi"
cpu: "1000m"
requests:
memory: "512Mi"
cpu: "500m"
And the corresponding service:
apiVersion: v1
kind: Service
metadata:
name: keycloak
labels:
app: keycloak
spec:
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app: keycloak
type: ClusterIP
Don't forget to create the necessary secrets:
apiVersion: v1
kind: Secret
metadata:
name: keycloak-secret
type: Opaque
data:
admin-password: YWRtaW4xMjM= # base64 encoded 'admin123'
Part 2: Configuring Spring Boot for Keycloak Authentication
Now let's configure our Spring Boot application to work with Keycloak. First, add these dependencies to your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Next, configure your application.yml:
spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: your-client-id
client-secret: your-client-secret
authorization-grant-type: authorization_code
scope: openid
provider:
keycloak:
issuer-uri: http://keycloak:8080/realms/your-realm
user-name-attribute: preferred_username
server:
port: 8081
Create a security configuration class:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.userAuthoritiesMapper(this::mapAuthorities)
)
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
private Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (authority instanceof OidcUserAuthority) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority) authority;
OidcIdToken idToken = oidcUserAuthority.getIdToken();
Map<String, Object> claims = idToken.getClaims();
@SuppressWarnings("unchecked")
Map<String, Object> resourceAccess = (Map<String, Object>) claims.get("resource_access");
if (resourceAccess != null) {
@SuppressWarnings("unchecked")
Map<String, Object> clientResource = (Map<String, Object>) resourceAccess.get("your-client-id");
if (clientResource != null) {
@SuppressWarnings("unchecked")
List<String> roles = (List<String>) clientResource.get("roles");
if (roles != null) {
roles.forEach(role -> mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role)));
}
}
}
}
});
return mappedAuthorities;
}
}
Part 3: Deploying the Spring Boot App to Kubernetes
Create a Deployment for your Spring Boot app:
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-app
labels:
app: spring-app
spec:
replicas: 2
selector:
matchLabels:
app: spring-app
template:
metadata:
labels:
app: spring-app
spec:
containers:
- name: spring-app
image: your-registry/spring-app:latest
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: KEYCLOAK_URL
value: "http://keycloak:8080"
- name: KEYCLOAK_REALM
value: "your-realm"
- name: CLIENT_ID
valueFrom:
secretKeyRef:
name: spring-app-secret
key: client-id
- name: CLIENT_SECRET
valueFrom:
secretKeyRef:
name: spring-app-secret
key: client-secret
ports:
- containerPort: 8081
readinessProbe:
httpGet:
path: /actuator/health
port: 8081
initialDelaySeconds: 30
periodSeconds: 10
resources:
limits:
memory: "512Mi"
cpu: "500m"
requests:
memory: "256Mi"
cpu: "200m"
And the corresponding service:
apiVersion: v1
kind: Service
metadata:
name: spring-app
spec:
selector:
app: spring-app
ports:
- port: 8081
targetPort: 8081
type: ClusterIP
Part 4: Setting Up Ingress for External Access
For external access, we need an Ingress controller:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: auth-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: auth.yourdomain.com
http:
paths:
- path: /auth
pathType: Prefix
backend:
service:
name: keycloak
port:
number: 8080
- path: /app
pathType: Prefix
backend:
service:
name: spring-app
port:
number: 8081
Part 5: Configuring Keycloak Realm and Client
After deploying Keycloak, you'll need to:
Create a new realm (e.g., "your-realm")
Create a new client for your Spring Boot app
Set up Valid Redirect URIs:
https://auth.yourdomain.com/app/*Enable "Authorization Code Flow"
Generate a client secret
Create roles and assign them to users
Create test users
Part 6: Troubleshooting Common Issues
Hey there! 👋 If you're reading this section, chances are you've run into some issues. Don't worry - I've been there! Here are some common problems and solutions:
Network Connectivity Issues
If your Spring Boot app can't connect to Keycloak:
Ensure both pods are in the same namespace
Check if the Keycloak service name is correctly referenced in your Spring Boot config
Verify network policies aren't blocking traffic
Token Validation Failures
If you're getting token validation errors:
Ensure Keycloak's issuer URI matches exactly in your Spring configuration
Check for time synchronization issues between pods
Verify that you're using the correct client secret
CORS Issues
If you're getting CORS errors:
Configure Keycloak's client settings to allow your app's origin
Add appropriate CORS configuration to your Spring Boot application
Conclusion
Phew! That was quite a journey. 🎉 By now, you should have a fully functioning Spring Boot application authenticated through Keycloak, all running smoothly in Kubernetes. This setup not only provides robust security but also scales well as your application grows.
I'd love to hear how this setup works for you! Drop a comment below or reach out if you have any questions. Remember, security is a continuous process, so keep your Keycloak and Spring Boot dependencies updated.
Happy coding! 💻


