|
1 | 1 | /*
|
2 |
| - * Copyright 2002-2021 the original author or authors. |
| 2 | + * Copyright 2002-2025 the original author or authors. |
3 | 3 | *
|
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | 5 | * you may not use this file except in compliance with the License.
|
|
17 | 17 | package org.springframework.security.oauth2.server.resource.web.authentication;
|
18 | 18 |
|
19 | 19 | import java.io.IOException;
|
| 20 | +import java.util.Map; |
20 | 21 |
|
21 | 22 | import jakarta.servlet.FilterChain;
|
22 | 23 | import jakarta.servlet.ServletException;
|
|
32 | 33 | import org.springframework.security.core.context.SecurityContext;
|
33 | 34 | import org.springframework.security.core.context.SecurityContextHolder;
|
34 | 35 | import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
| 36 | +import org.springframework.security.oauth2.core.ClaimAccessor; |
35 | 37 | import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
| 38 | +import org.springframework.security.oauth2.server.resource.BearerTokenError; |
| 39 | +import org.springframework.security.oauth2.server.resource.BearerTokenErrors; |
| 40 | +import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; |
36 | 41 | import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
|
37 | 42 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
|
38 | 43 | import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
|
|
45 | 50 | import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
|
46 | 51 | import org.springframework.security.web.context.SecurityContextRepository;
|
47 | 52 | import org.springframework.util.Assert;
|
| 53 | +import org.springframework.util.CollectionUtils; |
| 54 | +import org.springframework.util.StringUtils; |
48 | 55 | import org.springframework.web.filter.OncePerRequestFilter;
|
49 | 56 |
|
50 | 57 | /**
|
@@ -135,6 +142,12 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
|
135 | 142 | try {
|
136 | 143 | AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
|
137 | 144 | Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);
|
| 145 | + if (isDPoPBoundAccessToken(authenticationResult)) { |
| 146 | + // Prevent downgraded usage of DPoP-bound access tokens, |
| 147 | + // by rejecting a DPoP-bound access token received as a bearer token. |
| 148 | + BearerTokenError error = BearerTokenErrors.invalidToken("Invalid bearer token"); |
| 149 | + throw new OAuth2AuthenticationException(error); |
| 150 | + } |
138 | 151 | SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
|
139 | 152 | context.setAuthentication(authenticationResult);
|
140 | 153 | this.securityContextHolderStrategy.setContext(context);
|
@@ -217,4 +230,17 @@ public void setAuthenticationDetailsSource(
|
217 | 230 | this.authenticationDetailsSource = authenticationDetailsSource;
|
218 | 231 | }
|
219 | 232 |
|
| 233 | + private static boolean isDPoPBoundAccessToken(Authentication authentication) { |
| 234 | + if (!(authentication instanceof AbstractOAuth2TokenAuthenticationToken<?> accessTokenAuthentication)) { |
| 235 | + return false; |
| 236 | + } |
| 237 | + ClaimAccessor accessTokenClaims = accessTokenAuthentication::getTokenAttributes; |
| 238 | + String jwkThumbprintClaim = null; |
| 239 | + Map<String, Object> confirmationMethodClaim = accessTokenClaims.getClaimAsMap("cnf"); |
| 240 | + if (!CollectionUtils.isEmpty(confirmationMethodClaim) && confirmationMethodClaim.containsKey("jkt")) { |
| 241 | + jwkThumbprintClaim = (String) confirmationMethodClaim.get("jkt"); |
| 242 | + } |
| 243 | + return StringUtils.hasText(jwkThumbprintClaim); |
| 244 | + } |
| 245 | + |
220 | 246 | }
|
0 commit comments