parent
477a456d6c
commit
ce36fc1e76
|
@ -94,6 +94,7 @@ import org.springframework.security.config.annotation.AlreadyBuiltException;
|
|||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.authority.FactorGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.core.context.TransientSecurityContext;
|
||||
|
@ -584,6 +585,8 @@ final class SerializationSamples {
|
|||
token.setDetails(details);
|
||||
return token;
|
||||
});
|
||||
generatorByClassName.put(FactorGrantedAuthority.class,
|
||||
(r) -> FactorGrantedAuthority.withAuthority("profile:read").issuedAt(Instant.now()).build());
|
||||
generatorByClassName.put(UsernamePasswordAuthenticationToken.class, (r) -> {
|
||||
var token = UsernamePasswordAuthenticationToken.unauthenticated(user, "creds");
|
||||
token.setDetails(details);
|
||||
|
|
Binary file not shown.
|
@ -27,6 +27,7 @@ dependencies {
|
|||
optional 'org.jetbrains.kotlinx:kotlinx-coroutines-reactor'
|
||||
|
||||
testImplementation 'commons-collections:commons-collections'
|
||||
testImplementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
|
||||
testImplementation 'io.projectreactor:reactor-test'
|
||||
testImplementation "org.assertj:assertj-core"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api"
|
||||
|
|
|
@ -46,7 +46,7 @@ import org.springframework.security.core.Authentication;
|
|||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthorities;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.authority.FactorGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.session.SessionDestroyedEvent;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -214,7 +214,7 @@ public abstract class AbstractJaasAuthenticationProvider implements Authenticati
|
|||
}
|
||||
}
|
||||
}
|
||||
authorities.add(new SimpleGrantedAuthority(AUTHORITY));
|
||||
authorities.add(FactorGrantedAuthority.fromAuthority(AUTHORITY));
|
||||
return authorities;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ import org.springframework.security.core.Authentication;
|
|||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthorities;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.authority.FactorGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
|
@ -65,7 +65,7 @@ public final class OneTimeTokenAuthenticationProvider implements AuthenticationP
|
|||
try {
|
||||
UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername());
|
||||
Collection<GrantedAuthority> authorities = new HashSet<>(user.getAuthorities());
|
||||
authorities.add(new SimpleGrantedAuthority(AUTHORITY));
|
||||
authorities.add(FactorGrantedAuthority.fromAuthority(AUTHORITY));
|
||||
OneTimeTokenAuthentication authenticated = new OneTimeTokenAuthentication(user, authorities);
|
||||
authenticated.setDetails(otpAuthenticationToken.getDetails());
|
||||
return authenticated;
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.core.authority;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link GrantedAuthority} specifically used for indicating the factor used at time of
|
||||
* authentication.
|
||||
*
|
||||
* @author Yoobin Yoon
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
*/
|
||||
public final class FactorGrantedAuthority implements GrantedAuthority {
|
||||
|
||||
private static final long serialVersionUID = 1998010439847123984L;
|
||||
|
||||
private final String authority;
|
||||
|
||||
private final Instant issuedAt;
|
||||
|
||||
@SuppressWarnings("NullAway")
|
||||
private FactorGrantedAuthority(String authority, Instant issuedAt) {
|
||||
Assert.notNull(authority, "authority cannot be null");
|
||||
Assert.notNull(issuedAt, "issuedAt cannot be null");
|
||||
this.authority = authority;
|
||||
this.issuedAt = issuedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Builder} with the specified authority.
|
||||
* @param authority the authority value (must not be null or empty)
|
||||
* @return a new {@link Builder}
|
||||
*/
|
||||
public static Builder withAuthority(String authority) {
|
||||
return new Builder(authority);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Builder} with the specified factor which is automatically
|
||||
* prefixed with "FACTOR_".
|
||||
* @param factor the factor value which is automatically prefixed with "FACTOR_" (must
|
||||
* not be null or empty)
|
||||
* @return a new {@link Builder}
|
||||
*/
|
||||
public static Builder withFactor(String factor) {
|
||||
Assert.hasText(factor, "factor cannot be empty");
|
||||
Assert.isTrue(!factor.startsWith("FACTOR_"), () -> "factor cannot start with 'FACTOR_' got '" + factor + "'");
|
||||
return withAuthority("FACTOR_" + factor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for {@code withAuthority(authority).build()}.
|
||||
* @param authority the authority value (must not be null or empty)
|
||||
* @return a new {@link FactorGrantedAuthority}
|
||||
*/
|
||||
public static FactorGrantedAuthority fromAuthority(String authority) {
|
||||
return withAuthority(authority).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for {@code withFactor(factor).build()}.
|
||||
* @param factor the factor value which is automatically prefixed with "FACTOR_" (must
|
||||
* not be null or empty)
|
||||
* @return a new {@link FactorGrantedAuthority}
|
||||
*/
|
||||
public static FactorGrantedAuthority fromFactor(String factor) {
|
||||
return withFactor(factor).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return this.authority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the instant when this authority was issued.
|
||||
* @return the issued-at instant
|
||||
*/
|
||||
public Instant getIssuedAt() {
|
||||
return this.issuedAt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof FactorGrantedAuthority fga) {
|
||||
return this.authority.equals(fga.authority) && this.issuedAt.equals(fga.issuedAt);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.authority, this.issuedAt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("FactorGrantedAuthority [");
|
||||
sb.append("authority=").append(this.authority);
|
||||
sb.append(", issuedAt=").append(this.issuedAt);
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link FactorGrantedAuthority}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private final String authority;
|
||||
|
||||
private @Nullable Instant issuedAt;
|
||||
|
||||
private Builder(String authority) {
|
||||
Assert.hasText(authority, "A granted authority textual representation is required");
|
||||
this.authority = authority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the instant when this authority was issued.
|
||||
* @param issuedAt the issued-at instant
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder issuedAt(Instant issuedAt) {
|
||||
Assert.notNull(issuedAt, "issuedAt cannot be null");
|
||||
this.issuedAt = issuedAt;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link FactorGrantedAuthority}.
|
||||
* <p>
|
||||
* If {@code issuedAt} is not set, it defaults to {@link Instant#now()}.
|
||||
* @return a new {@link FactorGrantedAuthority}
|
||||
* @throws IllegalArgumentException if temporal constraints are invalid
|
||||
*/
|
||||
public FactorGrantedAuthority build() {
|
||||
if (this.issuedAt == null) {
|
||||
this.issuedAt = Instant.now();
|
||||
}
|
||||
|
||||
return new FactorGrantedAuthority(this.authority, this.issuedAt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -25,6 +25,7 @@ import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
|||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.RememberMeAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.FactorGrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
|
||||
|
@ -60,6 +61,7 @@ public class CoreJackson2Module extends SimpleModule {
|
|||
context.setMixInAnnotations(AnonymousAuthenticationToken.class, AnonymousAuthenticationTokenMixin.class);
|
||||
context.setMixInAnnotations(RememberMeAuthenticationToken.class, RememberMeAuthenticationTokenMixin.class);
|
||||
context.setMixInAnnotations(SimpleGrantedAuthority.class, SimpleGrantedAuthorityMixin.class);
|
||||
context.setMixInAnnotations(FactorGrantedAuthority.class, FactorGrantedAuthorityMixin.class);
|
||||
context.setMixInAnnotations(Collections.unmodifiableSet(Collections.emptySet()).getClass(),
|
||||
UnmodifiableSetMixin.class);
|
||||
context.setMixInAnnotations(Collections.unmodifiableList(Collections.emptyList()).getClass(),
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.jackson2;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
/**
|
||||
* Jackson Mixin class helps in serialize/deserialize
|
||||
* {@link org.springframework.security.core.authority.SimpleGrantedAuthority}.
|
||||
*
|
||||
* <pre>
|
||||
* ObjectMapper mapper = new ObjectMapper();
|
||||
* mapper.registerModule(new CoreJackson2Module());
|
||||
* </pre>
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
* @see CoreJackson2Module
|
||||
* @see SecurityJackson2Modules
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
getterVisibility = JsonAutoDetect.Visibility.PUBLIC_ONLY, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
abstract class FactorGrantedAuthorityMixin {
|
||||
|
||||
/**
|
||||
* Mixin Constructor.
|
||||
* @param authority the authority
|
||||
*/
|
||||
@JsonCreator
|
||||
FactorGrantedAuthorityMixin(@JsonProperty("authority") String authority,
|
||||
@JsonProperty("issuedAt") Instant issuedAt) {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.core.authority;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests {@link FactorGrantedAuthority}.
|
||||
*
|
||||
* @author Yoobin Yoon
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class FactorGrantedAuthorityTests {
|
||||
|
||||
@Test
|
||||
public void buildWhenOnlyAuthorityThenDefaultsIssuedAtToNow() {
|
||||
Instant before = Instant.now();
|
||||
|
||||
FactorGrantedAuthority authority = FactorGrantedAuthority.withAuthority("profile:read").build();
|
||||
|
||||
Instant after = Instant.now();
|
||||
|
||||
assertThat(authority.getAuthority()).isEqualTo("profile:read");
|
||||
assertThat(authority.getIssuedAt()).isBetween(before, after);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAllFieldsSetThenCreatesCorrectly() {
|
||||
Instant issuedAt = Instant.now();
|
||||
|
||||
FactorGrantedAuthority authority = FactorGrantedAuthority.withAuthority("admin:write")
|
||||
.issuedAt(issuedAt)
|
||||
.build();
|
||||
|
||||
assertThat(authority.getAuthority()).isEqualTo("admin:write");
|
||||
assertThat(authority.getIssuedAt()).isEqualTo(issuedAt);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenNullAuthorityThenThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> FactorGrantedAuthority.withAuthority(null))
|
||||
.withMessage("A granted authority textual representation is required");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenEmptyAuthorityThenThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> FactorGrantedAuthority.withAuthority(""))
|
||||
.withMessage("A granted authority textual representation is required");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright 2004-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.jackson2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.FactorGrantedAuthority;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 7.0
|
||||
*/
|
||||
class FactorGrantedAuthorityMixinTests extends AbstractMixinTests {
|
||||
|
||||
// @formatter:off
|
||||
public static final String AUTHORITY_JSON = "{\"@class\": \"org.springframework.security.core.authority.FactorGrantedAuthority\", \"authority\": \"FACTOR_PASSWORD\", \"issuedAt\": 1759177143.043000000 }";
|
||||
|
||||
private Instant issuedAt = Instant.ofEpochMilli(1759177143043L);
|
||||
|
||||
// @formatter:on
|
||||
|
||||
@Test
|
||||
void serializeSimpleGrantedAuthorityTest() throws JsonProcessingException, JSONException {
|
||||
GrantedAuthority authority = FactorGrantedAuthority.withAuthority("FACTOR_PASSWORD")
|
||||
.issuedAt(this.issuedAt)
|
||||
.build();
|
||||
String serializeJson = this.mapper.writeValueAsString(authority);
|
||||
JSONAssert.assertEquals(AUTHORITY_JSON, serializeJson, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeGrantedAuthorityTest() throws IOException {
|
||||
FactorGrantedAuthority authority = (FactorGrantedAuthority) this.mapper.readValue(AUTHORITY_JSON, Object.class);
|
||||
assertThat(authority).isNotNull();
|
||||
assertThat(authority.getAuthority()).isEqualTo("FACTOR_PASSWORD");
|
||||
assertThat(authority.getIssuedAt()).isEqualTo(this.issuedAt);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue