WSO2 Identity Server is a powerful, open-source IAM solution that provides identity federation, SSO, adaptive authentication, and API security. This guide covers complete integration with Java applications using OIDC, SAML, and SCIM.
Architecture Overview
Java Applications → WSO2 IS → (Identity Providers) ↑ (OIDC/SAML/SCIM Integrations)
Step 1: WSO2 Identity Server Setup
Docker Deployment
# docker-compose.yml version: '3.8' services: wso2-is: image: wso2/wso2is:6.1.0 ports: - "9443:9443" - "9763:9763" environment: - CARBON_HOST=localhost - CARBON_HTTPS_PORT=9443 volumes: - ./repository/conf:/home/wso2carbon/wso2is-6.1.0/repository/conf - ./repository/deployment:/home/wso2carbon/wso2is-6.1.0/repository/deployment networks: - wso2-network wso2-is-db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: wso2is MYSQL_USER: wso2carbon MYSQL_PASSWORD: wso2carbon volumes: - mysql_data:/var/lib/mysql networks: - wso2-network volumes: mysql_data: networks: wso2-network:
Programmatic Service Provider Configuration
// src/main/java/com/company/wso2/WSO2Initializer.java
package com.company.wso2;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Base64;
/**
* Programmatic configuration of WSO2 Identity Server
*/
@Component
public class WSO2Initializer {
@Value("${wso2.is.base-url:https://localhost:9443}")
private String baseUrl;
@Value("${wso2.is.admin-username:admin}")
private String adminUsername;
@Value("${wso2.is.admin-password:admin}")
private String adminPassword;
public void createServiceProvider(String spName, String callbackUrl) {
try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpPost post = new HttpPost(baseUrl + "/t/carbon.super/api/server/v1/service-providers");
// Basic Authentication
String auth = adminUsername + ":" + adminPassword;
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
post.setHeader("Authorization", "Basic " + encodedAuth);
post.setHeader("Content-Type", "application/json");
JSONObject spConfig = new JSONObject();
spConfig.put("name", spName);
spConfig.put("description", "Java Application Service Provider");
JSONObject inboundConfig = new JSONObject();
JSONObject oidcConfig = new JSONObject();
// OIDC Configuration
oidcConfig.put("clientId", spName + "_client");
oidcConfig.put("clientSecret", generateClientSecret());
oidcConfig.put("grantTypes", new String[]{"authorization_code", "refresh_token", "password"});
oidcConfig.put("callbackUrl", callbackUrl);
inboundConfig.put("oidc", oidcConfig);
spConfig.put("inboundConfiguration", inboundConfig);
StringEntity entity = new StringEntity(spConfig.toString());
post.setEntity(entity);
try (CloseableHttpResponse response = client.execute(post)) {
if (response.getStatusLine().getStatusCode() == 201) {
System.out.println("Service Provider created successfully: " + spName);
} else {
System.err.println("Failed to create Service Provider: " + response.getStatusLine().getStatusCode());
}
}
} catch (Exception e) {
throw new RuntimeException("Failed to create WSO2 Service Provider", e);
}
}
private String generateClientSecret() {
return java.util.UUID.randomUUID().toString().replace("-", "").substring(0, 16);
}
}
Step 2: Spring Boot OIDC Integration
Dependencies
<!-- pom.xml --> <dependencies> <!-- Spring Security OAuth2 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency> <!-- WSO2 Identity Server --> <dependency> <groupId>org.wso2.is</groupId> <artifactId>org.wso2.carbon.identity.oauth.stub</artifactId> <version>6.1.0</version> </dependency> <!-- HTTP Client --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> </dependencies>
Application Configuration
# application.yml
spring:
security:
oauth2:
client:
registration:
wso2:
client-id: ${WSO2_CLIENT_ID:java_app_client}
client-secret: ${WSO2_CLIENT_SECRET:secret}
scope: openid,profile,email
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/wso2"
provider:
wso2:
issuer-uri: ${WSO2_ISSUER_URI:https://localhost:9443/oauth2/token}
authorization-uri: ${WSO2_AUTHORIZATION_URI:https://localhost:9443/oauth2/authorize}
token-uri: ${WSO2_TOKEN_URI:https://localhost:9443/oauth2/token}
user-info-uri: ${WSO2_USERINFO_URI:https://localhost:9443/oauth2/userinfo}
jwk-set-uri: ${WSO2_JWKS_URI:https://localhost:9443/oauth2/jwks}
user-name-attribute: sub
wso2:
identity-server:
base-url: https://localhost:9443
tenant: carbon.super
admin-username: admin
admin-password: admin
server:
port: 8080
ssl:
trust-store: classpath:wso2truststore.jks
trust-store-password: wso2carbon
Security Configuration
// src/main/java/com/company/config/WSO2SecurityConfig.java
package com.company.config;
import com.company.wso2.WSO2UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class WSO2SecurityConfig {
private final WSO2UserService wso2UserService;
private final WSO2JwtFilter wso2JwtFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.disable())
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// Authorization rules
.authorizeHttpRequests(authz -> authz
.requestMatchers("/", "/public/**", "/login**", "/error").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
.requestMatchers("/api/**").authenticated()
.anyRequest().authenticated()
)
// OAuth2 Login configuration
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.defaultSuccessUrl("/dashboard")
.failureUrl("/login?error=true")
.userInfoEndpoint(userInfo ->
userInfo.oidcUserService(wso2UserService))
)
// OAuth2 Resource Server for JWT
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
)
// Add custom JWT filter
.addFilterBefore(wso2JwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
}
Step 3: WSO2 OIDC User Service
// src/main/java/com/company/wso2/WSO2UserService.java
package com.company.wso2;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@Slf4j
@RequiredArgsConstructor
public class WSO2UserService extends OidcUserService {
private final WSO2AdminClient wso2AdminClient;
@Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OidcUser oidcUser = super.loadUser(userRequest);
try {
return processOidcUser(oidcUser, userRequest);
} catch (Exception e) {
log.error("Failed to process OIDC user", e);
throw new OAuth2AuthenticationException("User processing failed");
}
}
private OidcUser processOidcUser(OidcUser oidcUser, OidcUserRequest userRequest) {
String username = oidcUser.getPreferredUsername();
Map<String, Object> claims = oidcUser.getClaims();
// Extract roles from WSO2 claims
Collection<GrantedAuthority> authorities = extractAuthorities(claims);
// Create custom user with additional claims
return new WSO2OidcUser(oidcUser, authorities, username);
}
@SuppressWarnings("unchecked")
private Collection<GrantedAuthority> extractAuthorities(Map<String, Object> claims) {
try {
// WSO2 roles are typically in the "groups" claim or custom claims
List<String> roles = (List<String>) claims.get("groups");
if (roles == null) {
roles = Collections.emptyList();
}
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
.collect(Collectors.toList());
} catch (Exception e) {
log.warn("Failed to extract authorities from claims", e);
return Collections.emptyList();
}
}
/**
* Custom OIDC User implementation for WSO2
*/
public static class WSO2OidcUser implements OidcUser {
private final OidcUser delegate;
private final Collection<GrantedAuthority> authorities;
private final String username;
public WSO2OidcUser(OidcUser delegate, Collection<GrantedAuthority> authorities, String username) {
this.delegate = delegate;
this.authorities = authorities;
this.username = username;
}
@Override
public Map<String, Object> getClaims() {
return delegate.getClaims();
}
@Override
public OidcUserInfo getUserInfo() {
return delegate.getUserInfo();
}
@Override
public OidcIdToken getIdToken() {
return delegate.getIdToken();
}
@Override
public String getName() {
return username;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public Map<String, Object> getAttributes() {
return delegate.getAttributes();
}
}
}
Step 4: JWT Token Processing
// src/main/java/com/company/wso2/WSO2JwtFilter.java
package com.company.wso2;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.List;
import java.util.stream.Collectors;
@Component
@Slf4j
@RequiredArgsConstructor
public class WSO2JwtFilter extends OncePerRequestFilter {
private final ObjectMapper objectMapper;
@Value("${wso2.identity-server.base-url}")
private String wso2BaseUrl;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
try {
WSO2User user = validateAndParseToken(token);
setAuthenticationContext(user, token);
} catch (Exception e) {
log.error("JWT validation failed", e);
sendErrorResponse(response, "Invalid token");
return;
}
}
filterChain.doFilter(request, response);
}
private WSO2User validateAndParseToken(String token) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKeyResolver(new WSO2SigningKeyResolver(wso2BaseUrl))
.build()
.parseClaimsJws(token)
.getBody();
return extractUserFromClaims(claims);
} catch (Exception e) {
throw new RuntimeException("Token validation failed", e);
}
}
@SuppressWarnings("unchecked")
private WSO2User extractUserFromClaims(Claims claims) {
String username = claims.getSubject();
String email = claims.get("email", String.class);
List<String> roles = claims.get("groups", List.class);
if (roles == null) {
roles = List.of("USER"); // Default role
}
List<SimpleGrantedAuthority> authorities = roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
.collect(Collectors.toList());
return WSO2User.builder()
.username(username)
.email(email)
.roles(roles)
.authorities(authorities)
.claims(claims)
.build();
}
private void setAuthenticationContext(WSO2User user, String token) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(user, token, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
private void sendErrorResponse(HttpServletResponse response, String message) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
String errorResponse = objectMapper.writeValueAsString(
java.util.Map.of("error", "UNAUTHORIZED", "message", message)
);
response.getWriter().write(errorResponse);
}
@Data
@Builder
public static class WSO2User {
private String username;
private String email;
private List<String> roles;
private List<SimpleGrantedAuthority> authorities;
private Claims claims;
public boolean hasRole(String role) {
return roles.stream()
.anyMatch(r -> r.equalsIgnoreCase(role));
}
}
}
Step 5: WSO2 Admin Client
// src/main/java/com/company/wso2/WSO2AdminClient.java
package com.company.wso2;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@Component
@Slf4j
public class WSO2AdminClient {
@Value("${wso2.identity-server.base-url}")
private String baseUrl;
@Value("${wso2.identity-server.admin-username}")
private String adminUsername;
@Value("${wso2.identity-server.admin-password}")
private String adminPassword;
@Value("${wso2.identity-server.tenant:carbon.super}")
private String tenant;
private String getAuthHeader() {
String auth = adminUsername + ":" + adminPassword;
return "Basic " + Base64.getEncoder().encodeToString(auth.getBytes());
}
public List<String> getUserRoles(String username) {
try (CloseableHttpClient client = HttpClients.createDefault()) {
String url = baseUrl + "/t/" + tenant + "/scim2/Users?filter=userNameEq" + username;
HttpGet get = new HttpGet(url);
get.setHeader("Authorization", getAuthHeader());
try (CloseableHttpResponse response = client.execute(get)) {
if (response.getStatusLine().getStatusCode() == 200) {
String responseBody = new String(response.getEntity().getContent().readAllBytes());
JSONObject jsonResponse = new JSONObject(responseBody);
return extractRolesFromSCIMResponse(jsonResponse);
}
}
} catch (Exception e) {
log.error("Failed to get user roles", e);
}
return Collections.emptyList();
}
private List<String> extractRolesFromSCIMResponse(JSONObject scimResponse) {
try {
JSONArray resources = scimResponse.getJSONArray("Resources");
if (resources.length() > 0) {
JSONObject user = resources.getJSONObject(0);
JSONArray groups = user.optJSONArray("groups");
if (groups != null) {
return IntStream.range(0, groups.length())
.mapToObj(groups::getJSONObject)
.map(group -> group.getString("display"))
.collect(Collectors.toList());
}
}
} catch (Exception e) {
log.warn("Failed to extract roles from SCIM response", e);
}
return Collections.emptyList();
}
public boolean createUser(String username, String password, String email, List<String> roles) {
try (CloseableHttpClient client = HttpClients.createDefault()) {
String url = baseUrl + "/t/" + tenant + "/scim2/Users";
HttpPost post = new HttpPost(url);
post.setHeader("Authorization", getAuthHeader());
post.setHeader("Content-Type", "application/json");
JSONObject user = new JSONObject();
user.put("userName", username);
user.put("password", password);
JSONArray emails = new JSONArray();
JSONObject emailObj = new JSONObject();
emailObj.put("value", email);
emailObj.put("primary", true);
emails.put(emailObj);
user.put("emails", emails);
// Add roles as groups
if (roles != null && !roles.isEmpty()) {
JSONArray groups = new JSONArray();
for (String role : roles) {
JSONObject group = new JSONObject();
group.put("value", getRoleId(role)); // You need to map role names to IDs
group.put("display", role);
groups.put(group);
}
user.put("groups", groups);
}
StringEntity entity = new StringEntity(user.toString());
post.setEntity(entity);
try (CloseableHttpResponse response = client.execute(post)) {
return response.getStatusLine().getStatusCode() == 201;
}
} catch (Exception e) {
log.error("Failed to create user", e);
return false;
}
}
private String getRoleId(String roleName) {
// This should be implemented to fetch actual role IDs from WSO2
// For now, returning a placeholder
return roleName.toLowerCase().replace(" ", "-") + "-id";
}
}
Step 6: REST API Controllers
// src/main/java/com/company/controller/AuthController.java
package com.company.controller;
import com.company.wso2.WSO2JwtFilter.WSO2User;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequiredArgsConstructor
@Slf4j
public class AuthController {
@GetMapping("/login")
public String loginPage() {
return "login";
}
@GetMapping("/dashboard")
public String dashboard(@AuthenticationPrincipal OidcUser oidcUser, Model model) {
if (oidcUser != null) {
model.addAttribute("user", oidcUser.getAttributes());
model.addAttribute("username", oidcUser.getPreferredUsername());
model.addAttribute("roles", oidcUser.getAuthorities());
}
return "dashboard";
}
@GetMapping("/api/user/profile")
@ResponseBody
public Map<String, Object> getUserProfile(@AuthenticationPrincipal OidcUser oidcUser) {
Map<String, Object> profile = new HashMap<>();
if (oidcUser != null) {
profile.put("username", oidcUser.getPreferredUsername());
profile.put("email", oidcUser.getEmail());
profile.put("name", oidcUser.getFullName());
profile.put("roles", oidcUser.getAuthorities().stream()
.map(auth -> auth.getAuthority().replace("ROLE_", ""))
.toList());
}
return profile;
}
}
// Protected API Controller
@RestController
@RequestMapping("/api")
public class ApiController {
@GetMapping("/public/info")
public Map<String, String> publicInfo() {
return Map.of("message", "This is public information");
}
@GetMapping("/user/data")
public Map<String, String> userData(@AuthenticationPrincipal WSO2JwtFilter.WSO2User user) {
return Map.of(
"message", "Hello " + user.getUsername(),
"role", user.getRoles().get(0)
);
}
@GetMapping("/admin/data")
public Map<String, String> adminData(@AuthenticationPrincipal WSO2JwtFilter.WSO2User user) {
if (!user.hasRole("ADMIN")) {
throw new RuntimeException("Access denied");
}
return Map.of(
"message", "Admin access granted",
"user", user.getUsername()
);
}
}
Step 7: SAML Integration (Alternative to OIDC)
// src/main/java/com/company/wso2/WSO2SAMLConfig.java
package com.company.wso2;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class WSO2SAMLConfig {
// SAML Configuration would go here
// This is a simplified example
/*
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.anyRequest().authenticated()
)
.saml2Login(saml2 -> saml2
.loginProcessingUrl("/saml2/login")
.idpLoginProcessingUrl("/saml2/SSO")
)
.saml2Logout(saml2 -> saml2
.logoutRequest(req -> req.logoutUrl("/saml2/logout"))
);
return http.build();
}
*/
}
// SAML User Details Service
@Component
public class SAMLUserDetailsServiceImpl implements SAMLUserDetailsService {
@Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
String username = credential.getNameID().getValue();
List<GrantedAuthority> authorities = new ArrayList<>();
// Extract attributes from SAML assertion
Attribute roleAttribute = credential.getAttribute("Role");
if (roleAttribute != null) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + roleAttribute.getAttributeValues()[0].toString()));
}
return new User(username, "", authorities);
}
}
Step 8: Adaptive Authentication
// src/main/java/com/company/wso2/adaptive/AdaptiveAuthService.java
package com.company.wso2.adaptive;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@Service
@Slf4j
public class AdaptiveAuthService {
@Value("${wso2.identity-server.base-url}")
private String wso2BaseUrl;
public Map<String, Object> evaluateAuthentication(HttpServletRequest request,
String username,
Map<String, String> context) {
Map<String, Object> authContext = new HashMap<>();
authContext.put("username", username);
authContext.put("ipAddress", getClientIpAddress(request));
authContext.put("userAgent", request.getHeader("User-Agent"));
authContext.put("timestamp", System.currentTimeMillis());
authContext.putAll(context);
// Risk assessment
int riskScore = calculateRiskScore(authContext);
authContext.put("riskScore", riskScore);
// Determine authentication level
String authLevel = determineAuthLevel(riskScore);
authContext.put("requiredAuthLevel", authLevel);
log.info("Adaptive auth evaluation - User: {}, Risk: {}, Auth Level: {}",
username, riskScore, authLevel);
return authContext;
}
private int calculateRiskScore(Map<String, Object> context) {
int score = 0;
// Example risk factors
String ipAddress = (String) context.get("ipAddress");
if (isSuspiciousIp(ipAddress)) {
score += 30;
}
String userAgent = (String) context.get("userAgent");
if (isSuspiciousUserAgent(userAgent)) {
score += 20;
}
// Add more risk factors as needed
return Math.min(score, 100);
}
private String determineAuthLevel(int riskScore) {
if (riskScore >= 70) {
return "HIGH"; // Require MFA
} else if (riskScore >= 30) {
return "MEDIUM"; // Additional verification
} else {
return "LOW"; // Standard authentication
}
}
private boolean isSuspiciousIp(String ipAddress) {
// Implement IP reputation check
return ipAddress.startsWith("192.168.") ||
ipAddress.startsWith("10.") ||
ipAddress.equals("127.0.0.1");
}
private boolean isSuspiciousUserAgent(String userAgent) {
// Implement user agent analysis
return userAgent == null || userAgent.isEmpty() ||
userAgent.contains("bot") || userAgent.contains("crawler");
}
private String getClientIpAddress(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0];
}
return request.getRemoteAddr();
}
}
Step 9: Testing
Test Configuration
// src/test/java/com/company/config/TestWSO2Config.java
@Configuration
@TestPropertySource(properties = {
"wso2.identity-server.base-url=http://localhost:9443",
"wso2.identity-server.admin-username=admin",
"wso2.identity-server.admin-password=admin"
})
public class TestWSO2Config {
@Bean
@Primary
public WSO2AdminClient mockWSO2AdminClient() {
WSO2AdminClient client = mock(WSO2AdminClient.class);
when(client.getUserRoles(anyString())).thenReturn(List.of("USER", "ADMIN"));
return client;
}
}
// src/test/java/com/company/controller/ApiControllerTest.java
@WebMvcTest(ApiController.class)
@Import(TestWSO2Config.class)
class ApiControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
@WithMockUser(roles = "USER")
void testUserEndpoint() throws Exception {
mockMvc.perform(get("/api/user/data")
.header("Authorization", "Bearer mock-token"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").exists());
}
@Test
@WithMockUser(roles = "ADMIN")
void testAdminEndpoint() throws Exception {
mockMvc.perform(get("/api/admin/data"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("Admin access granted"));
}
@Test
void testPublicEndpoint() throws Exception {
mockMvc.perform(get("/api/public/info"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.message").value("This is public information"));
}
}
Configuration for Production
application-prod.yml
spring:
security:
oauth2:
client:
registration:
wso2:
client-id: ${WSO2_CLIENT_ID}
client-secret: ${WSO2_CLIENT_SECRET}
scope: openid,profile,email,address,phone
provider:
wso2:
issuer-uri: ${WSO2_ISSUER_URI}
authorization-uri: ${WSO2_AUTHORIZATION_URI}
token-uri: ${WSO2_TOKEN_URI}
user-info-uri: ${WSO2_USERINFO_URI}
wso2:
identity-server:
base-url: ${WSO2_BASE_URL}
tenant: ${WSO2_TENANT:carbon.super}
admin-username: ${WSO2_ADMIN_USERNAME}
admin-password: ${WSO2_ADMIN_PASSWORD}
logging:
level:
com.company.wso2: DEBUG
org.springframework.security: INFO
Best Practices
- Security
- Use HTTPS in production
- Validate JWT signatures properly
- Implement proper CORS configuration
- Use secure cookie settings for sessions
- Performance
- Cache WSO2 public keys
- Use connection pooling for admin API calls
- Implement rate limiting
- Error Handling
- Graceful handling of WSO2 downtime
- Proper error messages for users
- Comprehensive logging
- Monitoring
- Log authentication events
- Monitor token expiration
- Track failed authentication attempts
Conclusion
WSO2 Identity Server integration provides:
- Enterprise-grade IAM: Robust identity and access management
- Multiple Protocols: OIDC, SAML, OAuth2 support
- Extensibility: Custom authentication flows and extensions
- Security: Comprehensive security features
Implementation steps:
- Set up WSO2 Identity Server
- Configure OIDC client in WSO2
- Integrate Spring Security OAuth2
- Implement JWT validation
- Add admin client for user management
- Implement adaptive authentication
- Add testing and monitoring
This setup provides a secure, scalable identity management solution suitable for enterprise Java applications.