1
0
mirror of synced 2026-05-22 13:23:17 +00:00

Fix auth_time claim should represent authentication time

Closes gh-18282
This commit is contained in:
Joe Grandja
2026-04-07 15:38:00 -04:00
parent 2361dc131e
commit 41524880c6
4 changed files with 40 additions and 11 deletions
@@ -60,6 +60,7 @@ import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder;
@@ -210,7 +211,8 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user").roles("A", "B"))) .with(user("user").roles("A", "B")
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
@@ -270,7 +272,8 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user").roles("A", "B"))) .with(user("user").roles("A", "B")
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
@@ -335,7 +338,8 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI)).queryParams(authorizationRequestParameters) .perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI)).queryParams(authorizationRequestParameters)
.with(user("user"))) .with(user("user")
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
@@ -388,7 +392,8 @@ public class OidcTests {
registeredClient1); registeredClient1);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user1"))) .with(user("user1")
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
@@ -424,7 +429,8 @@ public class OidcTests {
authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient2); authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient2);
mvcResult = this.mvc mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user2"))) .with(user("user2")
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
@@ -497,7 +503,8 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user"))) .with(user("user")
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
@@ -537,7 +544,8 @@ public class OidcTests {
registeredClient); registeredClient);
MvcResult mvcResult = this.mvc MvcResult mvcResult = this.mvc
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters)
.with(user("user"))) .with(user("user")
.authorities(FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY))))
.andExpect(status().is3xxRedirection()) .andExpect(status().is3xxRedirection())
.andReturn(); .andReturn();
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
@@ -24,6 +24,9 @@ import java.util.Date;
import java.util.UUID; import java.util.UUID;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.session.SessionInformation; import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AccessToken;
@@ -141,7 +144,7 @@ public final class JwtGenerator implements OAuth2TokenGenerator<Jwt> {
SessionInformation sessionInformation = context.get(SessionInformation.class); SessionInformation sessionInformation = context.get(SessionInformation.class);
if (sessionInformation != null) { if (sessionInformation != null) {
claimsBuilder.claim("sid", sessionInformation.getSessionId()); claimsBuilder.claim("sid", sessionInformation.getSessionId());
claimsBuilder.claim(IdTokenClaimNames.AUTH_TIME, sessionInformation.getLastRequest()); claimsBuilder.claim(IdTokenClaimNames.AUTH_TIME, getAuthenticationTime(context.getPrincipal()));
} }
} }
else if (AuthorizationGrantType.REFRESH_TOKEN.equals(context.getAuthorizationGrantType())) { else if (AuthorizationGrantType.REFRESH_TOKEN.equals(context.getAuthorizationGrantType())) {
@@ -222,4 +225,17 @@ public final class JwtGenerator implements OAuth2TokenGenerator<Jwt> {
this.clock = clock; this.clock = clock;
} }
static Date getAuthenticationTime(Authentication authentication) {
Instant authenticationTime = null;
for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
if (grantedAuthority instanceof FactorGrantedAuthority factorGrantedAuthority) {
if (authenticationTime == null || factorGrantedAuthority.getIssuedAt().isAfter(authenticationTime)) {
authenticationTime = factorGrantedAuthority.getIssuedAt();
}
}
}
Assert.notNull(authenticationTime, "authenticationTime cannot be null");
return Date.from(authenticationTime);
}
} }
@@ -24,6 +24,9 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken;
@@ -85,6 +88,9 @@ public final class TestOAuth2Authorizations {
.additionalParameters(authorizationRequestAdditionalParameters) .additionalParameters(authorizationRequestAdditionalParameters)
.state("state") .state("state")
.build(); .build();
Authentication principal = new TestingAuthenticationToken("principal", null,
new SimpleGrantedAuthority("ROLE_A"), new SimpleGrantedAuthority("ROLE_B"),
FactorGrantedAuthority.fromAuthority(FactorGrantedAuthority.PASSWORD_AUTHORITY));
OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient) OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient)
.id("id") .id("id")
.principalName("principal") .principalName("principal")
@@ -93,8 +99,7 @@ public final class TestOAuth2Authorizations {
.token(authorizationCode) .token(authorizationCode)
.attribute(OAuth2ParameterNames.STATE, "consent-state") .attribute(OAuth2ParameterNames.STATE, "consent-state")
.attribute(OAuth2AuthorizationRequest.class.getName(), authorizationRequest) .attribute(OAuth2AuthorizationRequest.class.getName(), authorizationRequest)
.attribute(Principal.class.getName(), .attribute(Principal.class.getName(), principal);
new TestingAuthenticationToken("principal", null, "ROLE_A", "ROLE_B"));
if (accessToken != null) { if (accessToken != null) {
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now(), OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now(),
Instant.now().plus(1, ChronoUnit.HOURS)); Instant.now().plus(1, ChronoUnit.HOURS));
@@ -363,7 +363,7 @@ public class JwtGeneratorTests {
SessionInformation sessionInformation = tokenContext.get(SessionInformation.class); SessionInformation sessionInformation = tokenContext.get(SessionInformation.class);
assertThat(jwtClaimsSet.<String>getClaim("sid")).isEqualTo(sessionInformation.getSessionId()); assertThat(jwtClaimsSet.<String>getClaim("sid")).isEqualTo(sessionInformation.getSessionId());
assertThat(jwtClaimsSet.<Date>getClaim(IdTokenClaimNames.AUTH_TIME)) assertThat(jwtClaimsSet.<Date>getClaim(IdTokenClaimNames.AUTH_TIME))
.isEqualTo(sessionInformation.getLastRequest()); .isEqualTo(JwtGenerator.getAuthenticationTime(tokenContext.getPrincipal()));
} }
else if (tokenContext.getAuthorizationGrantType().equals(AuthorizationGrantType.REFRESH_TOKEN)) { else if (tokenContext.getAuthorizationGrantType().equals(AuthorizationGrantType.REFRESH_TOKEN)) {
OidcIdToken currentIdToken = tokenContext.getAuthorization().getToken(OidcIdToken.class).getToken(); OidcIdToken currentIdToken = tokenContext.getAuthorization().getToken(OidcIdToken.class).getToken();