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.