CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-keycloak--keycloak-adapter-core

Core functionality for Keycloak OIDC/OAuth2 client adapters enabling Java applications to integrate with Keycloak identity and access management services

Pending
Overview
Eval results
Files

policy-enforcement.mddocs/

Policy Enforcement

Policy Enforcement Point (PEP) integration for authorization policy evaluation with Keycloak's authorization services. This module provides the foundation for fine-grained authorization policies and resource protection in applications.

Capabilities

HttpAuthzRequest

HTTP authorization request wrapper providing access to request information for policy evaluation.

/**
 * HTTP authorization request wrapper providing access to request information for policy evaluation
 */
public class HttpAuthzRequest implements AuthzRequest {
    /**
     * Constructor with OIDC HTTP facade
     * @param oidcFacade OIDC HTTP facade providing request access
     */
    public HttpAuthzRequest(OIDCHttpFacade oidcFacade);
    
    /**
     * Get the relative path of the request
     * @return Relative path string
     */
    public String getRelativePath();
    
    /**
     * Get the HTTP method of the request
     * @return HTTP method (GET, POST, PUT, DELETE, etc.)
     */
    public String getMethod();
    
    /**
     * Get the full URI of the request
     * @return Complete request URI
     */
    public String getURI();
    
    /**
     * Get all headers with the specified name
     * @param name Header name
     * @return List of header values
     */
    public List<String> getHeaders(String name);
    
    /**
     * Get the first parameter value with the specified name
     * @param name Parameter name
     * @return First parameter value or null
     */
    public String getFirstParam(String name);
    
    /**
     * Get cookie value by name
     * @param name Cookie name
     * @return Cookie value or null
     */
    public String getCookieValue(String name);
    
    /**
     * Get remote client address
     * @return Remote IP address
     */
    public String getRemoteAddr();
    
    /**
     * Check if the request is secure (HTTPS)
     * @return true if secure, false otherwise
     */
    public boolean isSecure();
    
    /**
     * Get single header value
     * @param name Header name
     * @return Header value or null
     */
    public String getHeader(String name);
    
    /**
     * Get request input stream
     * @param buffered Whether to buffer the stream
     * @return InputStream for request body
     */
    public InputStream getInputStream(boolean buffered);
    
    /**
     * Get the authenticated token principal
     * @return TokenPrincipal containing token information
     */
    public TokenPrincipal getPrincipal();
}

Usage Examples:

// Create authorization request from HTTP facade
HttpAuthzRequest authzRequest = new HttpAuthzRequest(oidcFacade);

// Extract request information for policy evaluation
String method = authzRequest.getMethod();
String path = authzRequest.getRelativePath();
String uri = authzRequest.getURI();
String clientIp = authzRequest.getRemoteAddr();
boolean isSecure = authzRequest.isSecure();

// Access headers and parameters
String contentType = authzRequest.getHeader("Content-Type");
List<String> acceptHeaders = authzRequest.getHeaders("Accept");
String actionParam = authzRequest.getFirstParam("action");

// Access authentication information
TokenPrincipal principal = authzRequest.getPrincipal();
if (principal != null) {
    AccessToken token = principal.getToken();
    String username = token.getPreferredUsername();
    Set<String> roles = token.getRealmAccess().getRoles();
}

// Read request body if needed
try (InputStream inputStream = authzRequest.getInputStream(true)) {
    // Process request body for policy evaluation
    String requestBody = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
}

HttpAuthzResponse

HTTP authorization response wrapper for sending policy enforcement responses.

/**
 * HTTP authorization response wrapper for sending policy enforcement responses
 */
public class HttpAuthzResponse implements AuthzResponse {
    /**
     * Constructor with OIDC HTTP facade
     * @param oidcFacade OIDC HTTP facade providing response access
     */
    public HttpAuthzResponse(OIDCHttpFacade oidcFacade);
    
    /**
     * Send error response with status code only
     * @param statusCode HTTP status code
     */
    public void sendError(int statusCode);
    
    /**
     * Send error response with status code and reason
     * @param code HTTP status code
     * @param reason Error reason phrase
     */
    public void sendError(int code, String reason);
    
    /**
     * Set response header
     * @param name Header name
     * @param value Header value
     */
    public void setHeader(String name, String value);
}

Usage Examples:

// Create authorization response
HttpAuthzResponse authzResponse = new HttpAuthzResponse(oidcFacade);

// Send forbidden response for denied requests
authzResponse.sendError(403, "Access denied by policy");

// Send unauthorized response for unauthenticated requests
authzResponse.sendError(401, "Authentication required");

// Add custom headers to response
authzResponse.setHeader("X-Policy-Decision", "DENY");
authzResponse.setHeader("X-Required-Role", "admin");

// Send specific error for resource not found
authzResponse.sendError(404, "Protected resource not found");

Policy Enforcement Patterns

Basic Policy Enforcer Integration

// Policy enforcement filter
public class PolicyEnforcementFilter implements Filter {
    private PolicyEnforcer policyEnforcer;
    private KeycloakDeployment deployment;
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // Initialize deployment and policy enforcer
        InputStream configStream = getClass().getResourceAsStream("/keycloak.json");
        deployment = KeycloakDeploymentBuilder.build(configStream);
        policyEnforcer = deployment.getPolicyEnforcer();
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // Create OIDC facade
        OIDCHttpFacade facade = new ServletOIDCHttpFacade(httpRequest, httpResponse);
        
        // Create authorization request and response
        HttpAuthzRequest authzRequest = new HttpAuthzRequest(facade);
        HttpAuthzResponse authzResponse = new HttpAuthzResponse(facade);
        
        try {
            // Evaluate policy
            AuthzResult result = policyEnforcer.enforce(authzRequest, authzResponse);
            
            if (result.isGranted()) {
                // Access granted - continue with request
                chain.doFilter(request, response);
            } else {
                // Access denied - response already sent by policy enforcer
                logger.warn("Access denied for {} {} by user {}", 
                    authzRequest.getMethod(), 
                    authzRequest.getURI(),
                    authzRequest.getPrincipal() != null ? 
                        authzRequest.getPrincipal().getToken().getPreferredUsername() : "anonymous"
                );
            }
        } catch (Exception e) {
            logger.error("Policy enforcement error", e);
            authzResponse.sendError(500, "Internal server error");
        }
    }
}

Custom Policy Evaluation

// Custom policy evaluator
public class CustomPolicyEvaluator {
    private final KeycloakDeployment deployment;
    
    public CustomPolicyEvaluator(KeycloakDeployment deployment) {
        this.deployment = deployment;
    }
    
    public PolicyDecision evaluate(HttpAuthzRequest request) {
        TokenPrincipal principal = request.getPrincipal();
        if (principal == null) {
            return PolicyDecision.deny("No authentication token");
        }
        
        AccessToken token = principal.getToken();
        String method = request.getMethod();
        String path = request.getRelativePath();
        
        // Time-based access control
        if (isOutsideBusinessHours() && !hasRole(token, "admin")) {
            return PolicyDecision.deny("Access outside business hours requires admin role");
        }
        
        // IP-based access control
        String clientIp = request.getRemoteAddr();
        if (!isAllowedIpAddress(clientIp) && isSensitiveResource(path)) {
            return PolicyDecision.deny("Sensitive resource access not allowed from this IP");
        }
        
        // Resource-specific policies
        if (path.startsWith("/admin")) {
            if (!hasRole(token, "admin")) {
                return PolicyDecision.deny("Admin access required");
            }
        } else if (path.startsWith("/api/sensitive")) {
            if (!hasRole(token, "privileged-user") && !isResourceOwner(token, path)) {
                return PolicyDecision.deny("Insufficient privileges for sensitive API");
            }
        }
        
        // Method-specific policies
        if ("DELETE".equals(method) && !hasRole(token, "data-manager")) {
            return PolicyDecision.deny("Delete operations require data-manager role");
        }
        
        return PolicyDecision.permit("Access granted");
    }
    
    private boolean hasRole(AccessToken token, String role) {
        return token.getRealmAccess() != null && 
               token.getRealmAccess().getRoles().contains(role);
    }
    
    private boolean isOutsideBusinessHours() {
        LocalTime now = LocalTime.now();
        return now.isBefore(LocalTime.of(9, 0)) || now.isAfter(LocalTime.of(17, 0));
    }
    
    private boolean isAllowedIpAddress(String ip) {
        // Check against whitelist of allowed IP ranges
        return ip.startsWith("10.0.") || ip.startsWith("192.168.") || "127.0.0.1".equals(ip);
    }
    
    private boolean isSensitiveResource(String path) {
        return path.contains("/financial") || path.contains("/personal") || path.contains("/confidential");
    }
    
    private boolean isResourceOwner(AccessToken token, String path) {
        // Extract resource owner from path and compare with token subject
        String userId = extractUserIdFromPath(path);
        return token.getSubject().equals(userId);
    }
    
    private String extractUserIdFromPath(String path) {
        // Extract user ID from path like /api/users/{userId}/profile
        String[] segments = path.split("/");
        for (int i = 0; i < segments.length - 1; i++) {
            if ("users".equals(segments[i]) && i + 1 < segments.length) {
                return segments[i + 1];
            }
        }
        return null;
    }
    
    public static class PolicyDecision {
        private final boolean granted;
        private final String reason;
        
        private PolicyDecision(boolean granted, String reason) {
            this.granted = granted;
            this.reason = reason;
        }
        
        public static PolicyDecision permit(String reason) {
            return new PolicyDecision(true, reason);
        }
        
        public static PolicyDecision deny(String reason) {
            return new PolicyDecision(false, reason);
        }
        
        public boolean isGranted() { return granted; }
        public String getReason() { return reason; }
    }
}

Resource-Based Authorization

// Resource-based authorization manager
public class ResourceAuthorizationManager {
    private final PolicyEnforcer policyEnforcer;
    private final Map<String, ResourceDescriptor> resourceMap;
    
    public ResourceAuthorizationManager(PolicyEnforcer policyEnforcer) {
        this.policyEnforcer = policyEnforcer;
        this.resourceMap = buildResourceMap();
    }
    
    public boolean isAuthorized(HttpAuthzRequest request, HttpAuthzResponse response) {
        String path = request.getRelativePath();
        String method = request.getMethod();
        
        ResourceDescriptor resource = findResource(path);
        if (resource != null) {
            return evaluateResourceAccess(request, response, resource, method);
        }
        
        // Default policy for unmapped resources
        return evaluateDefaultPolicy(request, response);
    }
    
    private ResourceDescriptor findResource(String path) {
        // Find best matching resource pattern
        return resourceMap.entrySet().stream()
            .filter(entry -> pathMatches(path, entry.getKey()))
            .map(Map.Entry::getValue)
            .findFirst()
            .orElse(null);
    }
    
    private boolean evaluateResourceAccess(HttpAuthzRequest request, HttpAuthzResponse response,
                                         ResourceDescriptor resource, String method) {
        TokenPrincipal principal = request.getPrincipal();
        if (principal == null) {
            response.sendError(401, "Authentication required");
            return false;
        }
        
        AccessToken token = principal.getToken();
        
        // Check required scopes for the method
        Set<String> requiredScopes = resource.getRequiredScopes(method);
        if (!hasRequiredScopes(token, requiredScopes)) {
            response.sendError(403, "Insufficient scopes for " + method + " on " + resource.getName());
            return false;
        }
        
        // Check resource-specific policies
        if (resource.hasCustomPolicy()) {
            PolicyDecision decision = resource.evaluateCustomPolicy(request);
            if (!decision.isGranted()) {
                response.sendError(403, decision.getReason());
                return false;
            }
        }
        
        return true;
    }
    
    private boolean hasRequiredScopes(AccessToken token, Set<String> requiredScopes) {
        Set<String> tokenScopes = extractScopes(token);
        return tokenScopes.containsAll(requiredScopes);
    }
    
    private Set<String> extractScopes(AccessToken token) {
        String scopeClaim = (String) token.getOtherClaims().get("scope");
        if (scopeClaim != null) {
            return Set.of(scopeClaim.split(" "));
        }
        return Collections.emptySet();
    }
    
    private Map<String, ResourceDescriptor> buildResourceMap() {
        Map<String, ResourceDescriptor> map = new HashMap<>();
        
        // Define resources and their access requirements
        map.put("/api/users/**", ResourceDescriptor.builder()
            .name("User Management")
            .scope("GET", "read:users")
            .scope("POST", "create:users")
            .scope("PUT", "update:users")
            .scope("DELETE", "delete:users")
            .customPolicy((request) -> {
                // Custom policy: users can only access their own data
                String path = request.getRelativePath();
                String tokenSubject = request.getPrincipal().getToken().getSubject();
                return path.contains("/" + tokenSubject + "/") || 
                       hasRole(request.getPrincipal().getToken(), "admin");
            })
            .build());
            
        map.put("/api/documents/**", ResourceDescriptor.builder()
            .name("Document Management")
            .scope("GET", "read:documents")
            .scope("POST", "create:documents")
            .scope("PUT", "update:documents")
            .scope("DELETE", "delete:documents")
            .customPolicy((request) -> {
                // Check document ownership or shared access
                return checkDocumentAccess(request);
            })
            .build());
            
        return map;
    }
    
    private boolean pathMatches(String path, String pattern) {
        // Simple pattern matching (could use more sophisticated matching)
        if (pattern.endsWith("/**")) {
            String prefix = pattern.substring(0, pattern.length() - 3);
            return path.startsWith(prefix);
        }
        return path.equals(pattern);
    }
    
    // Resource descriptor class
    public static class ResourceDescriptor {
        private final String name;
        private final Map<String, Set<String>> methodScopes;
        private final Function<HttpAuthzRequest, Boolean> customPolicy;
        
        // Builder pattern implementation...
        
        public Set<String> getRequiredScopes(String method) {
            return methodScopes.getOrDefault(method.toUpperCase(), Collections.emptySet());
        }
        
        public boolean hasCustomPolicy() {
            return customPolicy != null;
        }
        
        public PolicyDecision evaluateCustomPolicy(HttpAuthzRequest request) {
            if (customPolicy != null) {
                boolean allowed = customPolicy.apply(request);
                return allowed ? 
                    PolicyDecision.permit("Custom policy allows access") :
                    PolicyDecision.deny("Custom policy denies access");
            }
            return PolicyDecision.permit("No custom policy");
        }
    }
}

Claim-Based Authorization

// Claim-based authorization processor
public class ClaimBasedAuthorizationProcessor {
    
    public AuthorizationDecision evaluate(HttpAuthzRequest request) {
        TokenPrincipal principal = request.getPrincipal();
        if (principal == null) {
            return AuthorizationDecision.deny("No authentication token");
        }
        
        AccessToken token = principal.getToken();
        Map<String, Object> claims = token.getOtherClaims();
        
        // Department-based access control
        String department = (String) claims.get("department");
        String resource = request.getRelativePath();
        
        if (resource.startsWith("/hr/") && !"HR".equals(department)) {
            return AuthorizationDecision.deny("HR resources require HR department");
        }
        
        if (resource.startsWith("/finance/") && !"Finance".equals(department)) {
            return AuthorizationDecision.deny("Finance resources require Finance department");
        }
        
        // Location-based access control
        String userLocation = (String) claims.get("office_location");
        String clientIp = request.getRemoteAddr();
        
        if (!isLocationAllowed(userLocation, clientIp, resource)) {
            return AuthorizationDecision.deny("Resource not accessible from current location");
        }
        
        // Time-based access control with user-specific rules
        Integer maxHours = (Integer) claims.get("max_access_hours");
        if (maxHours != null && isOutsideAllowedHours(maxHours)) {
            return AuthorizationDecision.deny("Access outside allowed hours for user");
        }
        
        // Data classification access control
        String clearanceLevel = (String) claims.get("clearance_level");
        String resourceClassification = extractResourceClassification(resource);
        
        if (!hasSufficientClearance(clearanceLevel, resourceClassification)) {
            return AuthorizationDecision.deny("Insufficient clearance for resource classification");
        }
        
        return AuthorizationDecision.permit("All claim-based policies satisfied");
    }
    
    private boolean isLocationAllowed(String userLocation, String clientIp, String resource) {
        // Implement location-based access logic
        if ("REMOTE".equals(userLocation)) {
            // Remote users have limited access
            return !resource.contains("/sensitive/");
        }
        return true; // Office users have full access
    }
    
    private boolean isOutsideAllowedHours(int maxHours) {
        int currentHour = LocalTime.now().getHour();
        return currentHour >= maxHours;
    }
    
    private String extractResourceClassification(String resource) {
        if (resource.contains("/classified/")) return "SECRET";
        if (resource.contains("/confidential/")) return "CONFIDENTIAL";
        if (resource.contains("/internal/")) return "INTERNAL";
        return "PUBLIC";
    }
    
    private boolean hasSufficientClearance(String userClearance, String resourceClassification) {
        Map<String, Integer> clearanceLevels = Map.of(
            "PUBLIC", 0,
            "INTERNAL", 1,
            "CONFIDENTIAL", 2,
            "SECRET", 3
        );
        
        int userLevel = clearanceLevels.getOrDefault(userClearance, 0);
        int requiredLevel = clearanceLevels.getOrDefault(resourceClassification, 0);
        
        return userLevel >= requiredLevel;
    }
    
    public static class AuthorizationDecision {
        private final boolean granted;
        private final String reason;
        
        private AuthorizationDecision(boolean granted, String reason) {
            this.granted = granted;
            this.reason = reason;
        }
        
        public static AuthorizationDecision permit(String reason) {
            return new AuthorizationDecision(true, reason);
        }
        
        public static AuthorizationDecision deny(String reason) {
            return new AuthorizationDecision(false, reason);
        }
        
        public boolean isGranted() { return granted; }
        public String getReason() { return reason; }
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-org-keycloak--keycloak-adapter-core

docs

authentication.md

core-adapters.md

http-operations.md

index.md

jaas-integration.md

key-rotation.md

policy-enforcement.md

token-storage.md

utility-operations.md

tile.json