mirror of https://github.com/halo-dev/halo
Add support for serializing or deserializing HaloUser and 2FA (#6005)
#### What type of PR is this? /kind improvement /area core /milestone 2.16.x #### What this PR does / why we need it: This PR adds support for serializing HaloUser and 2FA. 1. Refactor delegate of HaloUser using `org.springframework.security.core.userdetails.User`. 2. Add `HaloSecurityJackson2Module` to enable serialization/deserialization of Halo security module. Below is code snippet of integration: ```java this.objectMapper = Jackson2ObjectMapperBuilder.json() .modules(SecurityJackson2Modules.getModules(this.getClass().getClassLoader())) .modules(modules -> modules.add(new HaloSecurityJackson2Module())) .indentOutput(true) .build(); ``` #### Does this PR introduce a user-facing change? ```release-note None ```pull/6007/head
parent
afabffc546
commit
dad6610cce
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.security;
|
||||
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
import static run.halo.app.core.extension.User.GROUP;
|
||||
import static run.halo.app.core.extension.User.KIND;
|
||||
import static run.halo.app.security.authorization.AuthorityUtils.ANONYMOUS_ROLE_NAME;
|
||||
|
@ -48,19 +49,27 @@ public class DefaultUserDetailService
|
|||
.flatMap(user -> {
|
||||
var name = user.getMetadata().getName();
|
||||
var subject = new Subject(KIND, name, GROUP);
|
||||
|
||||
var builder = new HaloUser.Builder(user);
|
||||
|
||||
var userBuilder = User.withUsername(name)
|
||||
.password(user.getSpec().getPassword())
|
||||
.disabled(requireNonNullElse(user.getSpec().getDisabled(), false));
|
||||
var setAuthorities = roleService.listRoleRefs(subject)
|
||||
.filter(this::isRoleRef)
|
||||
.map(RoleRef::getName)
|
||||
// every authenticated user should have authenticated and anonymous roles.
|
||||
.concatWithValues(AUTHENTICATED_ROLE_NAME, ANONYMOUS_ROLE_NAME)
|
||||
.map(roleName -> new SimpleGrantedAuthority(ROLE_PREFIX + roleName))
|
||||
.distinct()
|
||||
.collectList()
|
||||
.doOnNext(builder::authorities);
|
||||
.doOnNext(userBuilder::authorities);
|
||||
|
||||
return setAuthorities.then(Mono.fromSupplier(builder::build));
|
||||
return setAuthorities.then(Mono.fromSupplier(() -> {
|
||||
var twoFactorAuthEnabled =
|
||||
requireNonNullElse(user.getSpec().getTwoFactorAuthEnabled(), false);
|
||||
return new HaloUser.Builder(userBuilder.build())
|
||||
.twoFactorAuthEnabled(twoFactorAuthEnabled)
|
||||
.totpEncryptedSecret(user.getSpec().getTotpEncryptedSecret())
|
||||
.build();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package run.halo.app.security;
|
||||
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
public interface HaloUserDetails extends UserDetails {
|
||||
|
||||
/**
|
||||
* Checks if two-factor authentication is enabled.
|
||||
*
|
||||
* @return true if two-factor authentication is enabled, false otherwise.
|
||||
*/
|
||||
boolean isTwoFactorAuthEnabled();
|
||||
|
||||
/**
|
||||
* Gets the encrypted secret of TOTP.
|
||||
*
|
||||
* @return encrypted secret of TOTP.
|
||||
*/
|
||||
String getTotpEncryptedSecret();
|
||||
|
||||
}
|
|
@ -1,113 +1,120 @@
|
|||
package run.halo.app.security.authentication.login;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.springframework.security.core.CredentialsContainer;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.util.Assert;
|
||||
import run.halo.app.core.extension.User;
|
||||
import run.halo.app.security.HaloUserDetails;
|
||||
|
||||
public class HaloUser implements UserDetails, CredentialsContainer {
|
||||
public class HaloUser implements HaloUserDetails, CredentialsContainer {
|
||||
|
||||
private final User delegate;
|
||||
private final UserDetails delegate;
|
||||
|
||||
private final Collection<? extends GrantedAuthority> authorities;
|
||||
private final boolean twoFactorAuthEnabled;
|
||||
|
||||
public HaloUser(User delegate, Collection<? extends GrantedAuthority> authorities) {
|
||||
private String totpEncryptedSecret;
|
||||
|
||||
public HaloUser(UserDetails delegate,
|
||||
boolean twoFactorAuthEnabled,
|
||||
String totpEncryptedSecret) {
|
||||
Assert.notNull(delegate, "Delegate user must not be null");
|
||||
Assert.notNull(authorities, "Authorities must not be null");
|
||||
this.delegate = delegate;
|
||||
|
||||
this.authorities = authorities.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(Comparator.comparing(GrantedAuthority::getAuthority))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public HaloUser(User delegate) {
|
||||
this(delegate, List.of());
|
||||
this.twoFactorAuthEnabled = twoFactorAuthEnabled;
|
||||
this.totpEncryptedSecret = totpEncryptedSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return authorities;
|
||||
return delegate.getAuthorities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return delegate.getSpec().getPassword();
|
||||
return delegate.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return delegate.getMetadata().getName();
|
||||
return delegate.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
return delegate.isAccountNonExpired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
return delegate.isAccountNonLocked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
return delegate.isCredentialsNonExpired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
var disabled = delegate.getSpec().getDisabled();
|
||||
return disabled == null || !disabled;
|
||||
}
|
||||
|
||||
public User getDelegate() {
|
||||
return delegate;
|
||||
return delegate.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eraseCredentials() {
|
||||
delegate.getSpec().setPassword(null);
|
||||
if (delegate instanceof CredentialsContainer container) {
|
||||
container.eraseCredentials();
|
||||
}
|
||||
this.totpEncryptedSecret = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof HaloUser user) {
|
||||
var username = this.delegate.getMetadata().getName();
|
||||
var otherUsername = user.delegate.getMetadata().getName();
|
||||
return username.equals(otherUsername);
|
||||
return Objects.equals(this.delegate, user.delegate);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.delegate.getMetadata().getName().hashCode();
|
||||
return this.delegate.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTwoFactorAuthEnabled() {
|
||||
return this.twoFactorAuthEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTotpEncryptedSecret() {
|
||||
return this.totpEncryptedSecret;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final User user;
|
||||
private final UserDetails user;
|
||||
|
||||
private Collection<? extends GrantedAuthority> authorities;
|
||||
private boolean twoFactorAuthEnabled;
|
||||
|
||||
public Builder(User user) {
|
||||
private String totpEncryptedSecret;
|
||||
|
||||
public Builder(UserDetails user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public Builder authorities(Collection<? extends GrantedAuthority> authorities) {
|
||||
this.authorities = authorities;
|
||||
public Builder twoFactorAuthEnabled(boolean twoFactorAuthEnabled) {
|
||||
this.twoFactorAuthEnabled = twoFactorAuthEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HaloUser build() {
|
||||
return new HaloUser(user, authorities);
|
||||
public Builder totpEncryptedSecret(String totpEncryptedSecret) {
|
||||
this.totpEncryptedSecret = totpEncryptedSecret;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HaloUserDetails build() {
|
||||
return new HaloUser(user, twoFactorAuthEnabled, totpEncryptedSecret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ import org.springframework.security.core.Authentication;
|
|||
import org.springframework.security.core.AuthenticationException;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
||||
import run.halo.app.security.HaloUserDetails;
|
||||
import run.halo.app.security.authentication.twofactor.TwoFactorAuthentication;
|
||||
import run.halo.app.security.authentication.twofactor.TwoFactorUtils;
|
||||
|
||||
@Slf4j
|
||||
public class UsernamePasswordDelegatingAuthenticationManager
|
||||
|
@ -43,12 +43,9 @@ public class UsernamePasswordDelegatingAuthenticationManager
|
|||
)
|
||||
// check if MFA is enabled after authenticated
|
||||
.map(a -> {
|
||||
if (a.getPrincipal() instanceof HaloUser user) {
|
||||
var twoFactorAuthSettings =
|
||||
TwoFactorUtils.getTwoFactorAuthSettings(user.getDelegate());
|
||||
if (twoFactorAuthSettings.isAvailable()) {
|
||||
a = new TwoFactorAuthentication(a);
|
||||
}
|
||||
if (a.getPrincipal() instanceof HaloUserDetails user
|
||||
&& user.isTwoFactorAuthEnabled()) {
|
||||
a = new TwoFactorAuthentication(a);
|
||||
}
|
||||
return a;
|
||||
});
|
||||
|
|
|
@ -71,6 +71,10 @@ public class UsernamePasswordHandler implements ServerAuthenticationSuccessHandl
|
|||
.then(webFilterExchange.getChain().filter(webFilterExchange.getExchange()));
|
||||
}
|
||||
|
||||
if (authentication instanceof CredentialsContainer container) {
|
||||
container.eraseCredentials();
|
||||
}
|
||||
|
||||
ServerWebExchangeMatcher xhrMatcher = exchange -> {
|
||||
if (exchange.getRequest().getHeaders().getOrEmpty("X-Requested-With")
|
||||
.contains("XMLHttpRequest")) {
|
||||
|
@ -87,14 +91,9 @@ public class UsernamePasswordHandler implements ServerAuthenticationSuccessHandl
|
|||
() -> defaultSuccessHandler.onAuthenticationSuccess(webFilterExchange,
|
||||
authentication)
|
||||
.then(Mono.empty())))
|
||||
.flatMap(isXhr -> {
|
||||
if (authentication instanceof CredentialsContainer container) {
|
||||
container.eraseCredentials();
|
||||
}
|
||||
return ServerResponse.ok()
|
||||
.bodyValue(authentication.getPrincipal())
|
||||
.flatMap(response -> response.writeTo(exchange, context));
|
||||
}));
|
||||
.flatMap(isXhr -> ServerResponse.ok()
|
||||
.bodyValue(authentication.getPrincipal())
|
||||
.flatMap(response -> response.writeTo(exchange, context))));
|
||||
}
|
||||
|
||||
private Mono<Void> handleAuthenticationException(Throwable exception,
|
||||
|
|
|
@ -15,6 +15,7 @@ import run.halo.app.security.authentication.twofactor.totp.TotpAuthenticationFil
|
|||
public class TwoFactorAuthSecurityConfigurer implements SecurityConfigurer {
|
||||
|
||||
private final ServerSecurityContextRepository securityContextRepository;
|
||||
|
||||
private final TotpAuthService totpAuthService;
|
||||
|
||||
private final ServerResponse.Context context;
|
||||
|
@ -25,8 +26,11 @@ public class TwoFactorAuthSecurityConfigurer implements SecurityConfigurer {
|
|||
|
||||
public TwoFactorAuthSecurityConfigurer(
|
||||
ServerSecurityContextRepository securityContextRepository,
|
||||
TotpAuthService totpAuthService, ServerResponse.Context context,
|
||||
MessageSource messageSource, RememberMeServices rememberMeServices) {
|
||||
TotpAuthService totpAuthService,
|
||||
ServerResponse.Context context,
|
||||
MessageSource messageSource,
|
||||
RememberMeServices rememberMeServices
|
||||
) {
|
||||
this.securityContextRepository = securityContextRepository;
|
||||
this.totpAuthService = totpAuthService;
|
||||
this.context = context;
|
||||
|
|
|
@ -7,6 +7,11 @@ import org.springframework.security.authentication.AbstractAuthenticationToken;
|
|||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
/**
|
||||
* Authentication token for two-factor authentication.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
public class TwoFactorAuthentication extends AbstractAuthenticationToken {
|
||||
|
||||
private final Authentication previous;
|
||||
|
@ -33,10 +38,12 @@ public class TwoFactorAuthentication extends AbstractAuthenticationToken {
|
|||
|
||||
@Override
|
||||
public boolean isAuthenticated() {
|
||||
// return true for accessing anonymous resources
|
||||
return true;
|
||||
}
|
||||
|
||||
public Authentication getPrevious() {
|
||||
return previous;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.springframework.http.HttpMethod;
|
|||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.CredentialsContainer;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
|
||||
|
@ -17,7 +18,7 @@ import org.springframework.security.web.server.context.ServerSecurityContextRepo
|
|||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.security.authentication.login.HaloUser;
|
||||
import run.halo.app.security.HaloUserDetails;
|
||||
import run.halo.app.security.authentication.login.UsernamePasswordHandler;
|
||||
import run.halo.app.security.authentication.rememberme.RememberMeServices;
|
||||
import run.halo.app.security.authentication.twofactor.TwoFactorAuthentication;
|
||||
|
@ -25,11 +26,13 @@ import run.halo.app.security.authentication.twofactor.TwoFactorAuthentication;
|
|||
@Slf4j
|
||||
public class TotpAuthenticationFilter extends AuthenticationWebFilter {
|
||||
|
||||
public TotpAuthenticationFilter(ServerSecurityContextRepository securityContextRepository,
|
||||
public TotpAuthenticationFilter(
|
||||
ServerSecurityContextRepository securityContextRepository,
|
||||
TotpAuthService totpAuthService,
|
||||
ServerResponse.Context context,
|
||||
MessageSource messageSource,
|
||||
RememberMeServices rememberMeServices) {
|
||||
RememberMeServices rememberMeServices
|
||||
) {
|
||||
super(new TwoFactorAuthManager(totpAuthService));
|
||||
|
||||
setSecurityContextRepository(securityContextRepository);
|
||||
|
@ -96,36 +99,38 @@ public class TotpAuthenticationFilter extends AuthenticationWebFilter {
|
|||
// it should be TotpAuthenticationToken
|
||||
var code = (Integer) authentication.getCredentials();
|
||||
log.debug("Got TOTP code {}", code);
|
||||
|
||||
// get user details
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.cast(TwoFactorAuthentication.class)
|
||||
.map(TwoFactorAuthentication::getPrevious)
|
||||
.<Authentication>handle((prevAuth, sink) -> {
|
||||
var principal = prevAuth.getPrincipal();
|
||||
if (!(principal instanceof HaloUser haloUser)) {
|
||||
sink.error(new TwoFactorAuthException("Invalid MFA authentication."));
|
||||
return;
|
||||
.flatMap(previousAuth -> {
|
||||
var principal = previousAuth.getPrincipal();
|
||||
if (!(principal instanceof HaloUserDetails user)) {
|
||||
return Mono.error(
|
||||
new TwoFactorAuthException("Invalid authentication principal.")
|
||||
);
|
||||
}
|
||||
var encryptedSecret =
|
||||
haloUser.getDelegate().getSpec().getTotpEncryptedSecret();
|
||||
if (StringUtils.isBlank(encryptedSecret)) {
|
||||
sink.error(new TwoFactorAuthException("Empty secret configured"));
|
||||
return;
|
||||
var totpEncryptedSecret = user.getTotpEncryptedSecret();
|
||||
if (StringUtils.isBlank(totpEncryptedSecret)) {
|
||||
return Mono.error(
|
||||
new TwoFactorAuthException("TOTP secret not configured.")
|
||||
);
|
||||
}
|
||||
var rawSecret = totpAuthService.decryptSecret(encryptedSecret);
|
||||
var rawSecret = totpAuthService.decryptSecret(totpEncryptedSecret);
|
||||
var validated = totpAuthService.validateTotp(rawSecret, code);
|
||||
if (!validated) {
|
||||
sink.error(new TwoFactorAuthException("Invalid TOTP code " + code));
|
||||
return;
|
||||
return Mono.error(new TwoFactorAuthException("Invalid TOTP code " + code));
|
||||
}
|
||||
sink.next(prevAuth);
|
||||
})
|
||||
.doOnNext(previousAuth -> {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("TOTP authentication for {} with code {} successfully.",
|
||||
previousAuth.getName(), code);
|
||||
}
|
||||
if (previousAuth instanceof CredentialsContainer container) {
|
||||
container.eraseCredentials();
|
||||
}
|
||||
return Mono.just(previousAuth);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package run.halo.app.security.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||
import run.halo.app.security.authentication.login.HaloUser;
|
||||
import run.halo.app.security.authentication.twofactor.TwoFactorAuthentication;
|
||||
|
||||
/**
|
||||
* Halo security Jackson2 module.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
public class HaloSecurityJackson2Module extends SimpleModule {
|
||||
|
||||
public HaloSecurityJackson2Module() {
|
||||
super(HaloSecurityJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupModule(SetupContext context) {
|
||||
SecurityJackson2Modules.enableDefaultTyping(context.getOwner());
|
||||
context.setMixInAnnotations(HaloUser.class, HaloUserMixin.class);
|
||||
context.setMixInAnnotations(TwoFactorAuthentication.class,
|
||||
TwoFactorAuthenticationMixin.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package run.halo.app.security.jackson2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility =
|
||||
JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
abstract class HaloUserMixin {
|
||||
|
||||
HaloUserMixin(@JsonProperty("delegate") UserDetails delegate,
|
||||
@JsonProperty("twoFactorAuthEnabled") boolean twoFactorAuthEnabled,
|
||||
@JsonProperty("totpEncryptedSecret") String totpEncryptedSecret) {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package run.halo.app.security.jackson2;
|
||||
|
||||
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;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
/**
|
||||
* This mixin class is used to serialize/deserialize TwoFactorAuthentication.
|
||||
*
|
||||
* @author johnniang
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY,
|
||||
getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
abstract class TwoFactorAuthenticationMixin {
|
||||
|
||||
@JsonCreator
|
||||
TwoFactorAuthenticationMixin(@JsonProperty("previous") Authentication previous) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package run.halo.app.security.jackson2;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.function.Function;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||
import run.halo.app.security.authentication.login.HaloUser;
|
||||
import run.halo.app.security.authentication.twofactor.TwoFactorAuthentication;
|
||||
|
||||
class HaloSecurityJacksonModuleTest {
|
||||
|
||||
ObjectMapper objectMapper;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
this.objectMapper = Jackson2ObjectMapperBuilder.json()
|
||||
.modules(SecurityJackson2Modules.getModules(this.getClass().getClassLoader()))
|
||||
.modules(modules -> modules.add(new HaloSecurityJackson2Module()))
|
||||
.indentOutput(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void codecHaloUserTest() throws JsonProcessingException {
|
||||
codecAssert(haloUser -> UsernamePasswordAuthenticationToken.authenticated(haloUser,
|
||||
haloUser.getPassword(),
|
||||
haloUser.getAuthorities()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void codecTwoFactorAuthenticationTokenTest() throws JsonProcessingException {
|
||||
codecAssert(haloUser -> new TwoFactorAuthentication(
|
||||
UsernamePasswordAuthenticationToken.authenticated(haloUser,
|
||||
haloUser.getPassword(),
|
||||
haloUser.getAuthorities())));
|
||||
}
|
||||
|
||||
void codecAssert(Function<HaloUser, Authentication> authenticationConverter)
|
||||
throws JsonProcessingException {
|
||||
var userDetails = User.withUsername("faker")
|
||||
.password("123456")
|
||||
.authorities("ROLE_USER")
|
||||
.build();
|
||||
var haloUser = new HaloUser(userDetails, true, "fake-encrypted-secret");
|
||||
|
||||
var authentication = authenticationConverter.apply(haloUser);
|
||||
|
||||
var securityContext = new SecurityContextImpl(authentication);
|
||||
var securityContextJson = objectMapper.writeValueAsString(securityContext);
|
||||
|
||||
var deserializedSecurityContext =
|
||||
objectMapper.readValue(securityContextJson, SecurityContext.class);
|
||||
|
||||
assertEquals(deserializedSecurityContext, securityContext);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue