The traditional perimeter-based security model—where everything inside the corporate network is trusted—is obsolete. In today's world of cloud applications, mobile devices, and remote work, Zero Trust has emerged as the dominant security paradigm. But how do you apply Zero Trust principles to OAuth 2.0, the foundation of modern API security? Zero Trust OAuth extends standard OAuth flows with continuous verification, context-aware access control, and fine-grained session management. For Java developers, implementing Zero Trust OAuth means building applications that are secure by design, regardless of where users or resources reside.
What is Zero Trust OAuth?
Zero Trust OAuth applies the core principles of Zero Trust to OAuth 2.0 and OpenID Connect:
- Never trust, always verify: Every request is authenticated and authorized, regardless of source
- Assume breach: Design systems to minimize blast radius
- Least privilege access: Grant minimal required permissions
- Continuous verification: Re-evaluate trust throughout the session
- Micro-segmentation: Isolate resources and enforce fine-grained access control
Traditional Model: ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Inside │────▶│ Trust │────▶│ Access │ │ Network │ │ Implicit│ │ Granted │ └──────────┘ └──────────┘ └──────────┘ Zero Trust Model: ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Request │────▶│ Verify │────▶│ Assess │────▶│ Access │ │ │ │ Identity │ │ Context │ │ Decision │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────┐ │ Continuous Monitoring │ └─────────────────────────────────┘
Zero Trust OAuth Architecture
A Zero Trust OAuth implementation consists of several key components:
| Component | Purpose | Zero Trust Enhancement |
|---|---|---|
| Identity Provider | Authenticate users | Risk-based authentication, step-up |
| Authorization Server | Issue tokens | Short-lived, bound tokens |
| Policy Engine | Make access decisions | Context-aware, dynamic policies |
| Policy Enforcement Point | Enforce decisions | Per-request validation |
| Continuous Monitoring | Detect anomalies | Real-time risk assessment |
Implementing Zero Trust OAuth in Java
1. Dependencies
<dependencies> <!-- Spring Security OAuth2 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency> <!-- JWT handling --> <dependency> <groupId>com.nimbusds</groupId> <artifactId>nimbus-jose-jwt</artifactId> <version>9.37.3</version> </dependency> <!-- Risk assessment --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-math3</artifactId> <version>3.6.1</version> </dependency> <!-- Device fingerprinting --> <dependency> <groupId>com.github.ua-parser</groupId> <artifactId>uap-java</artifactId> <version>1.5.4</version> </dependency> </dependencies>
2. Zero Trust Token with Rich Claims
public class ZeroTrustToken {
/**
* Create a Zero Trust token with rich claims for continuous verification
*/
public static JWTClaimsSet.Builder createZeroTrustToken(
String subject,
String clientId,
Map<String, Object> context) {
Instant now = Instant.now();
String sessionId = UUID.randomUUID().toString();
return new JWTClaimsSet.Builder()
.subject(subject)
.issuer("https://auth.example.com")
.audience("https://api.example.com")
.issueTime(Date.from(now))
.expirationTime(Date.from(now.plusSeconds(300))) // 5 minutes (short-lived)
.notBeforeTime(Date.from(now))
.jwtID(generateJwtId())
.claim("client_id", clientId)
.claim("session_id", sessionId)
.claim("auth_time", now.getEpochSecond())
// Zero Trust context claims
.claim("zt_risk_score", calculateRiskScore(context))
.claim("zt_auth_method", context.get("auth_method"))
.claim("zt_confidence", context.get("confidence_level"))
.claim("zt_device_id", context.get("device_id"))
.claim("zt_geo_location", context.get("geo_location"))
// Network context
.claim("zt_ip_address", context.get("ip_address"))
.claim("zt_ip_risk", assessIpRisk((String) context.get("ip_address")))
// Behavioral context
.claim("zt_behavior_profile", context.get("behavior_profile"))
.claim("zt_anomaly_score", context.get("anomaly_score"))
// Token binding (prevents replay)
.claim("cnf", createTokenBinding(context));
}
private static String generateJwtId() {
return "zt-" + UUID.randomUUID().toString();
}
private static int calculateRiskScore(Map<String, Object> context) {
// Calculate risk score based on multiple factors
int score = 0;
// IP reputation
String ip = (String) context.get("ip_address");
if (isKnownVpnIp(ip)) score += 30;
if (isTorExitNode(ip)) score += 50;
// Device trust
if (!isDeviceTrusted((String) context.get("device_id"))) score += 40;
// Behavioral anomalies
Integer anomalyScore = (Integer) context.get("anomaly_score");
if (anomalyScore != null) score += anomalyScore;
return Math.min(score, 100);
}
private static Map<String, Object> createTokenBinding(Map<String, Object> context) {
Map<String, Object> cnf = new HashMap<>();
// Certificate thumbprint (if using MTLS)
String certThumbprint = (String) context.get("cert_thumbprint");
if (certThumbprint != null) {
cnf.put("x5t#S256", certThumbprint);
}
// Device fingerprint
String deviceFingerprint = (String) context.get("device_fingerprint");
if (deviceFingerprint != null) {
cnf.put("jkt", deviceFingerprint);
}
return cnf;
}
}
3. Zero Trust Authentication Filter
@Component
public class ZeroTrustAuthenticationFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(ZeroTrustAuthenticationFilter.class);
@Autowired
private TokenValidator tokenValidator;
@Autowired
private RiskEngine riskEngine;
@Autowired
private PolicyEnforcementPoint pep;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
// Step 1: Extract request context for Zero Trust evaluation
RequestContext context = extractRequestContext(request);
// Step 2: Validate token (if present)
String token = extractBearerToken(request);
TokenValidationResult tokenResult = null;
if (token != null) {
tokenResult = tokenValidator.validateToken(token, context);
if (!tokenResult.isValid()) {
sendZeroTrustError(response,
HttpStatus.UNAUTHORIZED,
"invalid_token",
tokenResult.getFailureReason());
return;
}
}
// Step 3: Evaluate Zero Trust policy (continuous verification)
ZeroTrustDecision decision = pep.evaluate(
tokenResult,
context,
request.getRequestURI(),
request.getMethod()
);
if (!decision.isAllowed()) {
logger.warn("Zero Trust policy denied access: {}", decision.getReason());
// Log for audit
auditLogSecurityEvent("ACCESS_DENIED", decision, context);
sendZeroTrustError(response,
decision.getHttpStatus(),
"access_denied",
decision.getReason());
return;
}
// Step 4: Create Zero Trust authentication token
ZeroTrustAuthentication auth = new ZeroTrustAuthentication(
tokenResult,
decision,
context
);
SecurityContextHolder.getContext().setAuthentication(auth);
// Step 5: Add Zero Trust headers for downstream services
response.setHeader("X-Zero-Trust-Session", auth.getSessionId());
response.setHeader("X-Zero-Trust-Risk", String.valueOf(auth.getRiskScore()));
// Step 6: Continue request processing
try {
chain.doFilter(request, response);
} finally {
// Step 7: Post-request monitoring
monitorRequestCompletion(auth, context, response.getStatus());
}
}
private RequestContext extractRequestContext(HttpServletRequest request) {
return RequestContext.builder()
.ipAddress(request.getRemoteAddr())
.userAgent(request.getHeader("User-Agent"))
.deviceFingerprint(calculateDeviceFingerprint(request))
.geoLocation(geoLocateIp(request.getRemoteAddr()))
.requestTime(Instant.now())
.httpMethod(request.getMethod())
.path(request.getRequestURI())
.headers(extractSecurityHeaders(request))
.tlsVersion((String) request.getAttribute("javax.servlet.request.ssl_session"))
.build();
}
private String calculateDeviceFingerprint(HttpServletRequest request) {
// Combine multiple factors for device fingerprinting
String userAgent = request.getHeader("User-Agent");
String acceptLanguage = request.getHeader("Accept-Language");
String acceptEncoding = request.getHeader("Accept-Encoding");
String fingerprint = String.format("%s|%s|%s",
userAgent != null ? userAgent : "",
acceptLanguage != null ? acceptLanguage : "",
acceptEncoding != null ? acceptEncoding : ""
);
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(fingerprint.getBytes(StandardCharsets.UTF_8));
return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
return fingerprint.substring(0, Math.min(32, fingerprint.length()));
}
}
@Data
@Builder
public static class RequestContext {
private String ipAddress;
private String userAgent;
private String deviceFingerprint;
private String geoLocation;
private Instant requestTime;
private String httpMethod;
private String path;
private Map<String, String> headers;
private String tlsVersion;
}
@Data
public static class ZeroTrustDecision {
private final boolean allowed;
private final String reason;
private final HttpStatus httpStatus;
private final int riskThreshold;
private final Map<String, Object> context;
public static ZeroTrustDecision allow() {
return new ZeroTrustDecision(true, "Allowed", HttpStatus.OK, 0, Map.of());
}
public static ZeroTrustDecision deny(String reason, HttpStatus status) {
return new ZeroTrustDecision(false, reason, status, 0, Map.of());
}
public static ZeroTrustDecision requireStepUp(String reason) {
return new ZeroTrustDecision(false, reason, HttpStatus.UNAUTHORIZED, 0,
Map.of("required_auth", "step_up"));
}
}
}
4. Policy Enforcement Point with Dynamic Policies
@Component
public class PolicyEnforcementPoint {
private static final Logger logger = LoggerFactory.getLogger(PolicyEnforcementPoint.class);
@Autowired
private RiskEngine riskEngine;
@Autowired
private PolicyRepository policyRepository;
@Autowired
private UserBehaviorAnalytics behaviorAnalytics;
public ZeroTrustDecision evaluate(
TokenValidationResult token,
ZeroTrustAuthenticationFilter.RequestContext context,
String resource,
String action) {
// Step 1: Load applicable policies
List<Policy> policies = policyRepository.findPoliciesForResource(resource);
// Step 2: Calculate current risk score
int riskScore = riskEngine.calculateRisk(token, context);
// Step 3: Check behavioral anomalies
double anomalyScore = behaviorAnalytics.detectAnomalies(
token != null ? token.getSubject() : "anonymous",
context
);
// Step 4: Evaluate each policy
for (Policy policy : policies) {
PolicyEvaluationResult result = evaluatePolicy(policy, token, context, riskScore, anomalyScore);
if (!result.isAllowed()) {
return ZeroTrustDecision.deny(
result.getReason(),
result.getRequiredHttpStatus()
);
}
}
// Step 5: Additional Zero Trust checks
if (!validateTokenBinding(token, context)) {
return ZeroTrustDecision.deny(
"Token binding validation failed",
HttpStatus.UNAUTHORIZED
);
}
if (isSessionHijackingDetected(token, context)) {
return ZeroTrustDecision.deny(
"Possible session hijacking detected",
HttpStatus.UNAUTHORIZED
);
}
if (riskScore > 80) {
return ZeroTrustDecision.deny(
"Risk score too high: " + riskScore,
HttpStatus.FORBIDDEN
);
} else if (riskScore > 60) {
// Require step-up authentication
return ZeroTrustDecision.requireStepUp("Additional verification required");
}
// Log access decision for audit
auditLogAccess(token, context, resource, action, riskScore, anomalyScore);
return ZeroTrustDecision.allow();
}
private PolicyEvaluationResult evaluatePolicy(
Policy policy,
TokenValidationResult token,
ZeroTrustAuthenticationFilter.RequestContext context,
int riskScore,
double anomalyScore) {
// Policy conditions
boolean conditionsMet = true;
List<String> failedConditions = new ArrayList<>();
// Time-based access
if (policy.hasTimeRestriction()) {
if (!isWithinAllowedHours(context.getRequestTime())) {
conditionsMet = false;
failedConditions.add("time_restriction");
}
}
// Location-based access
if (policy.hasLocationRestriction()) {
if (!isAllowedLocation(context.getGeoLocation())) {
conditionsMet = false;
failedConditions.add("location_restriction");
}
}
// Device trust
if (policy.requiresTrustedDevice()) {
if (!isDeviceTrusted(context.getDeviceFingerprint())) {
conditionsMet = false;
failedConditions.add("device_trust");
}
}
// Risk threshold
if (policy.getMaxRiskScore() < riskScore) {
conditionsMet = false;
failedConditions.add("risk_threshold");
}
// Role-based access
if (token != null && !token.hasRequiredRole(policy.getRequiredRole())) {
conditionsMet = false;
failedConditions.add("insufficient_role");
}
return new PolicyEvaluationResult(
conditionsMet,
failedConditions.isEmpty() ? null : String.join(", ", failedConditions),
conditionsMet ? HttpStatus.OK : policy.getDenyStatus()
);
}
private boolean validateTokenBinding(
TokenValidationResult token,
ZeroTrustAuthenticationFilter.RequestContext context) {
if (token == null) return true; // Anonymous access
Map<String, Object> cnf = token.getClaim("cnf");
if (cnf == null) return true; // No binding requirement
// Validate certificate binding
if (cnf.containsKey("x5t#S256")) {
String expectedThumbprint = (String) cnf.get("x5t#S256");
String actualThumbprint = context.getHeaders().get("X-Client-Cert-Thumbprint");
if (!expectedThumbprint.equals(actualThumbprint)) {
logger.warn("Certificate binding mismatch for token: {}", token.getJti());
return false;
}
}
// Validate device binding
if (cnf.containsKey("jkt")) {
String expectedFingerprint = (String) cnf.get("jkt");
if (!expectedFingerprint.equals(context.getDeviceFingerprint())) {
logger.warn("Device binding mismatch for token: {}", token.getJti());
return false;
}
}
return true;
}
private boolean isSessionHijackingDetected(
TokenValidationResult token,
ZeroTrustAuthenticationFilter.RequestContext context) {
if (token == null) return false;
// Check for IP address changes
String previousIp = token.getClaim("zt_ip_address");
if (previousIp != null && !previousIp.equals(context.getIpAddress())) {
// IP changed - might be legitimate (mobile user) or hijacking
// Check if the change is suspicious
String previousGeo = token.getClaim("zt_geo_location");
String currentGeo = context.getGeoLocation();
if (previousGeo != null && currentGeo != null) {
// Impossible travel detection
double distance = calculateDistance(previousGeo, currentGeo);
double timeDiff = calculateTimeDifference(
token.getIssueTime(),
context.getRequestTime()
);
// If distance/time ratio exceeds reasonable travel speed
if (distance / timeDiff > 1000) { // >1000 km/h
logger.warn("Impossible travel detected: {} km in {} hours",
distance, timeDiff);
return true;
}
}
}
return false;
}
@Data
public static class Policy {
private String id;
private String resource;
private String requiredRole;
private int maxRiskScore = 100;
private boolean requireTrustedDevice;
private List<String> allowedLocations;
private List<String> allowedTimeRanges;
private HttpStatus denyStatus = HttpStatus.FORBIDDEN;
public boolean hasTimeRestriction() {
return allowedTimeRanges != null && !allowedTimeRanges.isEmpty();
}
public boolean hasLocationRestriction() {
return allowedLocations != null && !allowedLocations.isEmpty();
}
}
}
5. Risk Engine for Continuous Assessment
@Component
public class RiskEngine {
private static final Logger logger = LoggerFactory.getLogger(RiskEngine.class);
@Autowired
private IpReputationService ipReputationService;
@Autowired
private DeviceTrustService deviceTrustService;
@Autowired
private UserBehaviorAnalytics behaviorAnalytics;
public int calculateRisk(
TokenValidationResult token,
ZeroTrustAuthenticationFilter.RequestContext context) {
RiskAssessment assessment = new RiskAssessment();
// Factor 1: IP reputation (0-30)
int ipRisk = assessIpRisk(context.getIpAddress());
assessment.addFactor("ip_reputation", ipRisk, 0.3);
// Factor 2: Device trust (0-25)
int deviceRisk = assessDeviceRisk(context.getDeviceFingerprint());
assessment.addFactor("device_trust", deviceRisk, 0.25);
// Factor 3: Behavioral anomalies (0-20)
double anomalyScore = behaviorAnalytics.detectAnomalies(
token != null ? token.getSubject() : "anonymous",
context
);
assessment.addFactor("behavioral", (int) (anomalyScore * 20), 0.2);
// Factor 4: Session age (0-15)
if (token != null) {
int sessionAgeRisk = assessSessionAgeRisk(token);
assessment.addFactor("session_age", sessionAgeRisk, 0.15);
}
// Factor 5: Request pattern (0-10)
int patternRisk = assessRequestPattern(context);
assessment.addFactor("request_pattern", patternRisk, 0.1);
int totalRisk = assessment.calculateTotal();
logger.debug("Risk assessment: {} (details: {})", totalRisk, assessment.getFactors());
return totalRisk;
}
private int assessIpRisk(String ipAddress) {
IpReputation rep = ipReputationService.getReputation(ipAddress);
if (rep.isTorExitNode()) return 30;
if (rep.isKnownVpn()) return 25;
if (rep.isProxy()) return 20;
if (rep.isDatacenter()) return 15;
if (rep.isSuspicious()) return 10;
return 0;
}
private int assessDeviceRisk(String deviceFingerprint) {
DeviceTrust trust = deviceTrustService.getDeviceTrust(deviceFingerprint);
if (trust == null) return 25; // Unknown device
if (!trust.isTrusted()) return 20; // Untrusted device
// Trusted but check freshness
if (trust.isStale()) return 10;
return 0; // Trusted, current device
}
private int assessSessionAgeRisk(TokenValidationResult token) {
Instant issueTime = token.getIssueTime();
Instant now = Instant.now();
long sessionAgeMinutes = Duration.between(issueTime, now).toMinutes();
if (sessionAgeMinutes > 60) return 15; // >1 hour
if (sessionAgeMinutes > 30) return 10; // 30-60 minutes
if (sessionAgeMinutes > 15) return 5; // 15-30 minutes
return 0; // <15 minutes
}
private int assessRequestPattern(ZeroTrustAuthenticationFilter.RequestContext context) {
// Check for suspicious request patterns
int risk = 0;
// Unusual HTTP method
if (context.getHttpMethod().equals("DELETE") ||
context.getHttpMethod().equals("PUT")) {
// Check if user normally does these operations
if (!isNormalOperationForUser(context.getUserAgent(), context.getHttpMethod())) {
risk += 5;
}
}
// Accessing sensitive endpoints
if (context.getPath().contains("/admin") ||
context.getPath().contains("/config")) {
risk += 5;
}
return risk;
}
@Data
private static class RiskAssessment {
private final List<RiskFactor> factors = new ArrayList<>();
private double totalWeight = 0;
public void addFactor(String name, int value, double weight) {
factors.add(new RiskFactor(name, value, weight));
totalWeight += weight;
}
public int calculateTotal() {
if (totalWeight == 0) return 0;
double weightedSum = factors.stream()
.mapToDouble(f -> f.value * f.weight)
.sum();
return (int) Math.min(weightedSum / totalWeight, 100);
}
public Map<String, Object> getFactors() {
return factors.stream()
.collect(Collectors.toMap(
RiskFactor::getName,
f -> Map.of("value", f.value, "weight", f.weight)
));
}
}
@Data
@AllArgsConstructor
private static class RiskFactor {
private String name;
private int value;
private double weight;
}
}
6. Zero Trust Authentication Object
public class ZeroTrustAuthentication extends AbstractAuthenticationToken {
private final TokenValidationResult token;
private final PolicyEnforcementPoint.ZeroTrustDecision decision;
private final ZeroTrustAuthenticationFilter.RequestContext context;
private final Instant authenticatedAt;
public ZeroTrustAuthentication(
TokenValidationResult token,
PolicyEnforcementPoint.ZeroTrustDecision decision,
ZeroTrustAuthenticationFilter.RequestContext context) {
super(token != null ? token.getAuthorities() : Collections.emptyList());
this.token = token;
this.decision = decision;
this.context = context;
this.authenticatedAt = Instant.now();
setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null; // Credentials already validated
}
@Override
public Object getPrincipal() {
return token != null ? token.getSubject() : "anonymous";
}
public String getSessionId() {
return token != null ? token.getClaim("session_id") : "anonymous-session";
}
public int getRiskScore() {
return token != null ?
token.getClaim("zt_risk_score") : 0;
}
public boolean hasScope(String scope) {
return token != null && token.hasScope(scope);
}
public Map<String, Object> getZeroTrustContext() {
Map<String, Object> ztContext = new HashMap<>();
if (token != null) {
ztContext.put("risk_score", token.getClaim("zt_risk_score"));
ztContext.put("auth_method", token.getClaim("zt_auth_method"));
ztContext.put("confidence", token.getClaim("zt_confidence"));
ztContext.put("device_id", token.getClaim("zt_device_id"));
}
ztContext.put("request_ip", context.getIpAddress());
ztContext.put("request_geo", context.getGeoLocation());
ztContext.put("request_time", context.getRequestTime());
ztContext.put("decision_risk_threshold", decision.getRiskThreshold());
return ztContext;
}
public boolean requiresStepUp() {
return "step_up".equals(decision.getContext().get("required_auth"));
}
}
7. Zero Trust Authorization Annotations
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ZeroTrustPolicy {
String resource() default "";
String[] requiredScopes() default {};
String[] requiredRoles() default {};
int maxRiskScore() default 80;
boolean requireTrustedDevice() default false;
boolean requireStepUpForHighRisk() default true;
String[] allowedLocations() default {};
}
@Component
public class ZeroTrustMethodSecurityInterceptor {
@Autowired
private PolicyEnforcementPoint pep;
@Around("@annotation(zeroTrustPolicy)")
public Object enforceZeroTrustPolicy(
ProceedingJoinPoint pjp,
ZeroTrustPolicy zeroTrustPolicy) throws Throwable {
// Get current authentication
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (!(auth instanceof ZeroTrustAuthentication)) {
throw new AccessDeniedException("Zero Trust authentication required");
}
ZeroTrustAuthentication ztAuth = (ZeroTrustAuthentication) auth;
// Build context for policy evaluation
ZeroTrustAuthenticationFilter.RequestContext context =
extractContextFromRequest();
// Create evaluation request
PolicyEnforcementPoint.EvaluationRequest request =
new PolicyEnforcementPoint.EvaluationRequest(
ztAuth,
context,
zeroTrustPolicy.resource(),
determineAction(pjp)
);
// Evaluate policy
ZeroTrustDecision decision = pep.evaluate(request);
if (!decision.isAllowed()) {
if (decision.getContext().containsKey("required_auth") &&
"step_up".equals(decision.getContext().get("required_auth"))) {
throw new StepUpAuthenticationRequiredException(
"Step-up authentication required due to risk level"
);
}
throw new AccessDeniedException(decision.getReason());
}
return pjp.proceed();
}
}
8. Continuous Monitoring and Session Management
@Component
public class ZeroTrustSessionManager {
private static final Logger logger = LoggerFactory.getLogger(ZeroTrustSessionManager.class);
@Autowired
private RiskEngine riskEngine;
@Autowired
private PolicyEnforcementPoint pep;
@Autowired
private RedisTemplate<String, ZeroTrustSession> sessionStore;
private final ScheduledExecutorService monitorExecutor =
Executors.newScheduledThreadPool(2);
@PostConstruct
public void startSessionMonitoring() {
// Monitor active sessions every minute
monitorExecutor.scheduleAtFixedRate(
this::monitorActiveSessions,
1, 1, TimeUnit.MINUTES
);
}
public ZeroTrustSession createSession(
ZeroTrustAuthentication auth,
ZeroTrustAuthenticationFilter.RequestContext context) {
String sessionId = auth.getSessionId();
ZeroTrustSession session = ZeroTrustSession.builder()
.sessionId(sessionId)
.userId(auth.getName())
.deviceFingerprint(context.getDeviceFingerprint())
.ipAddress(context.getIpAddress())
.geoLocation(context.getGeoLocation())
.userAgent(context.getUserAgent())
.initialRiskScore(auth.getRiskScore())
.currentRiskScore(auth.getRiskScore())
.createdAt(Instant.now())
.lastActivityAt(Instant.now())
.activityCount(0)
.status(SessionStatus.ACTIVE)
.build();
// Store session with TTL
sessionStore.opsForValue().set(
"session:" + sessionId,
session,
Duration.ofHours(24)
);
logger.info("Created Zero Trust session: {}", sessionId);
return session;
}
public void recordActivity(String sessionId, ZeroTrustAuthenticationFilter.RequestContext context) {
String key = "session:" + sessionId;
ZeroTrustSession session = sessionStore.opsForValue().get(key);
if (session == null) {
logger.warn("Activity for unknown session: {}", sessionId);
return;
}
// Update session
session.setLastActivityAt(Instant.now());
session.setActivityCount(session.getActivityCount() + 1);
// Recalculate risk score
int newRiskScore = riskEngine.calculateRiskForSession(session, context);
session.setCurrentRiskScore(newRiskScore);
// Check for risk score increase
if (newRiskScore > session.getInitialRiskScore() + 30) {
logger.warn("Significant risk increase for session {}: {} -> {}",
sessionId, session.getInitialRiskScore(), newRiskScore);
// Trigger step-up authentication
triggerStepUpAuthentication(session);
}
// Check for device change
if (!session.getDeviceFingerprint().equals(context.getDeviceFingerprint())) {
logger.warn("Device change detected for session {}: {} -> {}",
sessionId, session.getDeviceFingerprint(), context.getDeviceFingerprint());
// Mark for re-authentication
session.setStatus(SessionStatus.REAUTH_REQUIRED);
}
sessionStore.opsForValue().set(key, session, Duration.ofHours(24));
}
private void monitorActiveSessions() {
// Get all active sessions from Redis (simplified - would use scan in production)
Set<String> sessionKeys = sessionStore.keys("session:*");
for (String key : sessionKeys) {
ZeroTrustSession session = sessionStore.opsForValue().get(key);
if (session == null) continue;
// Check session age
Duration sessionAge = Duration.between(session.getCreatedAt(), Instant.now());
if (sessionAge.toHours() > 8) {
// Force re-authentication after 8 hours
logger.info("Session {} expired due to max lifetime", session.getSessionId());
sessionStore.delete(key);
continue;
}
// Check inactivity
Duration inactivity = Duration.between(session.getLastActivityAt(), Instant.now());
if (inactivity.toMinutes() > 30) {
// Mark as idle
session.setStatus(SessionStatus.IDLE);
sessionStore.opsForValue().set(key, session, Duration.ofHours(24));
logger.debug("Session {} idle for {} minutes",
session.getSessionId(), inactivity.toMinutes());
}
// Periodic risk reassessment
if (inactivity.toMinutes() % 5 == 0) { // Every 5 minutes
reassessSessionRisk(session);
}
}
}
@Data
@Builder
public static class ZeroTrustSession {
private String sessionId;
private String userId;
private String deviceFingerprint;
private String ipAddress;
private String geoLocation;
private String userAgent;
private int initialRiskScore;
private int currentRiskScore;
private Instant createdAt;
private Instant lastActivityAt;
private int activityCount;
private SessionStatus status;
}
public enum SessionStatus {
ACTIVE,
IDLE,
REAUTH_REQUIRED,
TERMINATED
}
}
9. Step-Up Authentication
@RestController
@RequestMapping("/auth/step-up")
public class StepUpAuthenticationController {
@Autowired
private StepUpAuthService stepUpService;
@Autowired
private ZeroTrustSessionManager sessionManager;
@PostMapping("/initiate")
public ResponseEntity<StepUpResponse> initiateStepUp(
@RequestHeader("X-Session-Id") String sessionId,
HttpServletRequest request) {
// Determine required auth method based on risk
ZeroTrustSessionManager.ZeroTrustSession session =
sessionManager.getSession(sessionId);
if (session == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
StepUpRequirement requirement = determineRequirement(session);
// Generate step-up challenge
String challengeId = stepUpService.createChallenge(
session.getUserId(),
requirement
);
return ResponseEntity.ok(new StepUpResponse(
challengeId,
requirement.getMethod(),
requirement.getMessage()
));
}
@PostMapping("/verify")
public ResponseEntity<StepUpVerificationResult> verifyStepUp(
@RequestBody StepUpVerificationRequest request,
HttpServletRequest servletRequest) {
// Verify step-up authentication
boolean verified = stepUpService.verifyChallenge(
request.getChallengeId(),
request.getCode(),
extractContext(servletRequest)
);
if (!verified) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(new StepUpVerificationResult(false, "Invalid verification code"));
}
// Issue short-lived step-up token
String stepUpToken = stepUpService.issueStepUpToken(
request.getSessionId(),
Duration.ofMinutes(5)
);
// Update session risk score
sessionManager.reduceRiskScore(request.getSessionId(), 30);
return ResponseEntity.ok(new StepUpVerificationResult(
true,
"Step-up authentication successful",
stepUpToken
));
}
private StepUpRequirement determineRequirement(
ZeroTrustSessionManager.ZeroTrustSession session) {
if (session.getCurrentRiskScore() > 80) {
return new StepUpRequirement(
"FIDO2",
"High-risk activity detected. Please verify with security key."
);
} else if (session.getCurrentRiskScore() > 60) {
return new StepUpRequirement(
"OTP",
"Please enter the verification code sent to your registered device."
);
} else {
return new StepUpRequirement(
"PASSWORD",
"Please re-enter your password to continue."
);
}
}
}
10. Audit and Compliance Logging
@Component
public class ZeroTrustAuditLogger {
private static final Logger auditLog = LoggerFactory.getLogger("ZERO-TRUST-AUDIT");
@Autowired
private AuditRepository auditRepository;
public void logAccessDecision(
ZeroTrustDecision decision,
ZeroTrustAuthentication auth,
ZeroTrustAuthenticationFilter.RequestContext context,
String resource,
String action) {
ZeroTrustAuditEvent event = ZeroTrustAuditEvent.builder()
.timestamp(Instant.now())
.eventType("ACCESS_DECISION")
.userId(auth != null ? auth.getName() : "anonymous")
.sessionId(auth != null ? auth.getSessionId() : null)
.decision(decision.isAllowed() ? "ALLOW" : "DENY")
.reason(decision.getReason())
.riskScore(auth != null ? auth.getRiskScore() : 0)
.ipAddress(context.getIpAddress())
.geoLocation(context.getGeoLocation())
.deviceFingerprint(context.getDeviceFingerprint())
.userAgent(context.getUserAgent())
.resource(resource)
.action(action)
.build();
// Log to file
auditLog.info(event.toJson());
// Store in database for compliance
auditRepository.save(event);
// If high-risk denial, alert security team
if (!decision.isAllowed() && decision.getRiskThreshold() > 70) {
alertSecurityTeam(event);
}
}
public void logRiskScoreChange(
String sessionId,
String userId,
int oldScore,
int newScore,
String reason) {
ZeroTrustAuditEvent event = ZeroTrustAuditEvent.builder()
.timestamp(Instant.now())
.eventType("RISK_CHANGE")
.userId(userId)
.sessionId(sessionId)
.oldRiskScore(oldScore)
.newRiskScore(newScore)
.reason(reason)
.build();
auditLog.info(event.toJson());
// Trigger alerts for significant changes
if (newScore - oldScore > 30) {
alertSecurityTeam(event);
}
}
@Data
@Builder
public static class ZeroTrustAuditEvent {
private Instant timestamp;
private String eventType;
private String userId;
private String sessionId;
private String decision;
private String reason;
private int riskScore;
private int oldRiskScore;
private int newRiskScore;
private String ipAddress;
private String geoLocation;
private String deviceFingerprint;
private String userAgent;
private String resource;
private String action;
public String toJson() {
try {
return new ObjectMapper()
.writeValueAsString(this);
} catch (JsonProcessingException e) {
return toString();
}
}
}
}
Zero Trust OAuth Configuration
# application-zero-trust.yml zero-trust: enabled: true session: max-lifetime: 8h idle-timeout: 30m require-step-up: true risk: thresholds: low: 30 medium: 60 high: 80 factors: ip-reputation: 0.3 device-trust: 0.25 behavioral: 0.2 session-age: 0.15 request-pattern: 0.1 policies: - resource: "/api/accounts/*" max-risk-score: 60 require-trusted-device: true - resource: "/api/payments/*" max-risk-score: 40 require-trusted-device: true allowed-locations: ["US", "CA", "UK"] - resource: "/api/admin/*" max-risk-score: 30 require-trusted-device: true require-step-up: true monitoring: continuous-assessment: true anomaly-detection: true impossible-travel-detection: true
Zero Trust OAuth Testing
@SpringBootTest
@AutoConfigureMockMvc
public class ZeroTrustOAuthTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testLowRiskAccess() throws Exception {
// Simulate request from trusted location/device
mockMvc.perform(get("/api/accounts")
.header("Authorization", "Bearer " + getLowRiskToken())
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
.with(request -> {
request.setRemoteAddr("192.168.1.100");
return request;
}))
.andExpect(status().isOk())
.andExpect(header().exists("X-Zero-Trust-Risk"))
.andExpect(header().string("X-Zero-Trust-Risk", "25"));
}
@Test
public void testHighRiskAccess() throws Exception {
// Simulate request from suspicious location
mockMvc.perform(get("/api/accounts")
.header("Authorization", "Bearer " + getLowRiskToken())
.header("User-Agent", "Unknown-Client")
.with(request -> {
request.setRemoteAddr("45.33.22.11"); // Known suspicious IP
return request;
}))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.error").value("access_denied"))
.andExpect(jsonPath("$.error_description").value(containsString("risk score")));
}
@Test
public void testStepUpRequired() throws Exception {
// Simulate medium risk that requires step-up
mockMvc.perform(get("/api/payments")
.header("Authorization", "Bearer " + getMediumRiskToken())
.with(request -> {
request.setRemoteAddr("128.0.0.1"); // New IP
return request;
}))
.andExpect(status().isUnauthorized())
.andExpect(header().string("X-Step-Up-Required", "true"))
.andExpect(header().exists("X-Step-Up-Challenge"));
}
@Test
public void testImpossibleTravel() throws Exception {
String token = getTokenWithLocation("US", 10);
// Wait a few seconds
Thread.sleep(2000);
// Request from different continent
mockMvc.perform(get("/api/accounts")
.header("Authorization", "Bearer " + token)
.with(request -> {
request.setRemoteAddr("203.0.113.1"); // Australian IP
return request;
}))
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.error_description")
.value(containsString("impossible travel")));
}
}
Conclusion
Zero Trust OAuth represents a fundamental shift in how we approach API security. By moving beyond static tokens and perimeter-based trust, Zero Trust OAuth provides continuous verification, context-aware access control, and real-time risk assessment.
For Java applications, implementing Zero Trust OAuth requires:
- Rich tokens with context and binding information
- Dynamic policy enforcement based on risk and context
- Continuous monitoring of sessions and behavior
- Step-up authentication for high-risk activities
- Comprehensive audit logging for compliance
The benefits of Zero Trust OAuth are substantial:
- Reduced blast radius through token binding and short lifetimes
- Protection against token theft via continuous verification
- Compliance readiness with detailed audit trails
- Adaptive security that responds to changing risk levels
As cyber threats become more sophisticated, the traditional trust-once model of OAuth is no longer sufficient. Zero Trust OAuth provides the framework for building resilient, secure Java applications that can withstand modern attacks while providing a seamless user experience. The future of API security is Zero Trust, and Java developers who embrace these patterns today will be prepared for the security challenges of tomorrow.
Java Programming Basics – Variables, Loops, Methods, Classes, Files & Exception Handling (Related to Java Programming)
Variables and Data Types in Java:
This topic explains how variables store data in Java and how data types define the kind of values a variable can hold, such as numbers, characters, or text. Java includes primitive types like int, double, and boolean, which are essential for storing and managing data in programs. (GeeksforGeeks)
Read more: https://macronepal.com/blog/variables-and-data-types-in-java/
Basic Input and Output in Java:
This lesson covers how Java programs receive input from users and display output using tools like Scanner for input and System.out.println() for output. These operations allow interaction between the program and the user.
Read more: https://macronepal.com/blog/basic-input-output-in-java/
Arithmetic Operations in Java:
This guide explains mathematical operations such as addition, subtraction, multiplication, and division using operators like +, -, *, and /. These operations are used to perform calculations in Java programs.
Read more: https://macronepal.com/blog/arithmetic-operations-in-java/
If-Else Statement in Java:
The if-else statement allows programs to make decisions based on conditions. It helps control program flow by executing different blocks of code depending on whether a condition is true or false.
Read more: https://macronepal.com/blog/if-else-statement-in-java/
For Loop in Java:
A for loop is used to repeat a block of code a specific number of times. It is commonly used when the number of repetitions is known in advance.
Read more: https://macronepal.com/blog/for-loop-in-java/
Method Overloading in Java:
Method overloading allows multiple methods to have the same name but different parameters. It improves code readability and flexibility by allowing similar tasks to be handled using one method name.
Read more: https://macronepal.com/blog/method-overloading-in-java-a-complete-guide/
Basic Inheritance in Java:
Inheritance is an object-oriented concept that allows one class to inherit properties and methods from another class. It promotes code reuse and helps build hierarchical class structures.
Read more: https://macronepal.com/blog/basic-inheritance-in-java-a-complete-guide/
File Writing in Java:
This topic explains how to create and write data into files using Java. File writing is commonly used to store program data permanently.
Read more: https://macronepal.com/blog/file-writing-in-java-a-complete-guide/
File Reading in Java:
File reading allows Java programs to read stored data from files. It is useful for retrieving saved information and processing it inside applications.
Read more: https://macronepal.com/blog/file-reading-in-java-a-complete-guide/
Exception Handling in Java:
Exception handling helps manage runtime errors using tools like try, catch, and finally. It prevents programs from crashing and allows safe error handling.
Read more: https://macronepal.com/blog/exception-handling-in-java-a-complete-guide/
Constructors in Java:
Constructors are special methods used to initialize objects when they are created. They help assign initial values to object variables automatically.
Read more: https://macronepal.com/blog/constructors-in-java/
Classes and Objects in Java:
Classes are blueprints used to create objects, while objects are instances of classes. These concepts form the foundation of object-oriented programming in Java.
Read more: https://macronepal.com/blog/classes-and-object-in-java/
Methods in Java:
Methods are blocks of code that perform specific tasks. They help organize programs into smaller reusable sections and improve code readability.
Read more: https://macronepal.com/blog/methods-in-java/
Arrays in Java:
Arrays store multiple values of the same type in a single variable. They are useful for handling lists of data such as numbers or names.
Read more: https://macronepal.com/blog/arrays-in-java/
While Loop in Java:
A while loop repeats a block of code as long as a given condition remains true. It is useful when the number of repetitions is not known beforehand.
Read more: https://macronepal.com/blog/while-loop-in-java/