Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
ruibaby committed May 22, 2024
2 parents 9c0212e + bbc5c97 commit fb706df
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 125 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,20 @@
import static org.springframework.security.web.server.authentication.ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver.builder;
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers;

import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.session.SessionProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.MediaTypeServerWebExchangeMatcher;
import org.springframework.session.MapSession;
import org.springframework.session.config.annotation.web.server.EnableSpringWebSession;
import org.springframework.web.reactive.function.server.RouterFunction;
Expand Down Expand Up @@ -60,24 +52,30 @@
@RequiredArgsConstructor
public class WebServerSecurityConfig {

@Bean(name = "apiSecurityFilterChain")
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityWebFilterChain apiFilterChain(ServerHttpSecurity http,
@Bean
SecurityWebFilterChain filterChain(ServerHttpSecurity http,
RoleService roleService,
ObjectProvider<SecurityConfigurer> securityConfigurers,
ServerSecurityContextRepository securityContextRepository,
ReactiveExtensionClient client,
PatJwkSupplier patJwkSupplier) {
PatJwkSupplier patJwkSupplier,
HaloProperties haloProperties) {

http.securityMatcher(pathMatchers("/api/**", "/apis/**", "/oauth2/**",
"/login/**", "/logout", "/actuator/**"))
.authorizeExchange(spec -> {
spec.anyExchange().access(
http.securityMatcher(pathMatchers("/**"))
.authorizeExchange(spec -> spec.pathMatchers(
"/api/**",
"/apis/**",
"/oauth2/**",
"/login/**",
"/logout",
"/actuator/**"
)
.access(
new TwoFactorAuthorizationManager(
new RequestInfoAuthorizationManager(roleService)
)
);
})
)
.anyExchange().permitAll())
.anonymous(spec -> {
spec.authorities(AnonymousUserConst.Role);
spec.principal(AnonymousUserConst.PRINCIPAL);
Expand All @@ -87,33 +85,12 @@ SecurityWebFilterChain apiFilterChain(ServerHttpSecurity http,
.oauth2ResourceServer(oauth2 -> {
var authManagerResolver = builder().add(
new PatServerWebExchangeMatcher(),
new PatAuthenticationManager(client, patJwkSupplier))
new PatAuthenticationManager(client, patJwkSupplier)
)
// TODO Add other authentication mangers here. e.g.: JwtAuthenticationManager.
.build();
oauth2.authenticationManagerResolver(authManagerResolver);
})
.headers(headerSpec -> headerSpec.hsts(hstsSpec -> hstsSpec.includeSubdomains(false)))
;

// Integrate with other configurers separately
securityConfigurers.orderedStream()
.forEach(securityConfigurer -> securityConfigurer.configure(http));
return http.build();
}

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
SecurityWebFilterChain portalFilterChain(ServerHttpSecurity http,
ServerSecurityContextRepository securityContextRepository,
HaloProperties haloProperties) {
var pathMatcher = pathMatchers(HttpMethod.GET, "/**");
var mediaTypeMatcher = new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML);
mediaTypeMatcher.setIgnoredMediaTypes(Set.of(MediaType.ALL));
http.securityMatcher(new AndServerWebExchangeMatcher(pathMatcher, mediaTypeMatcher))
.securityContextRepository(securityContextRepository)
.authorizeExchange(spec -> {
spec.anyExchange().permitAll();
})
.headers(headerSpec -> headerSpec
.frameOptions(frameSpec -> {
var frameOptions = haloProperties.getSecurity().getFrameOptions();
Expand All @@ -122,17 +99,16 @@ SecurityWebFilterChain portalFilterChain(ServerHttpSecurity http,
frameSpec.disable();
}
})
.referrerPolicy(referrerPolicySpec -> {
referrerPolicySpec.policy(
haloProperties.getSecurity().getReferrerOptions().getPolicy());
})
.referrerPolicy(referrerPolicySpec -> referrerPolicySpec.policy(
haloProperties.getSecurity().getReferrerOptions().getPolicy())
)
.cache(ServerHttpSecurity.HeaderSpec.CacheSpec::disable)
.hsts(hstsSpec -> hstsSpec.includeSubdomains(false))
)
.anonymous(spec -> spec.authenticationFilter(
new HaloAnonymousAuthenticationWebFilter("portal", AnonymousUserConst.PRINCIPAL,
AuthorityUtils.createAuthorityList(AnonymousUserConst.Role),
securityContextRepository)));
);

// Integrate with other configurers separately
securityConfigurers.orderedStream()
.forEach(securityConfigurer -> securityConfigurer.configure(http));
return http.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,65 +1,53 @@
package run.halo.app.theme.dialect;

import java.util.HashMap;
import java.util.Map;
import static org.springframework.security.core.authority.AuthorityUtils.createAuthorityList;
import static run.halo.app.infra.AnonymousUserConst.PRINCIPAL;
import static run.halo.app.infra.AnonymousUserConst.Role;

import java.util.function.Function;
import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.security.web.server.csrf.CsrfToken;
import org.springframework.web.server.ServerWebExchange;
import org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect;
import org.thymeleaf.extras.springsecurity6.util.SpringSecurityContextUtils;
import org.thymeleaf.extras.springsecurity6.util.SpringVersionUtils;
import reactor.core.publisher.Mono;

/**
* HaloSpringSecurityDialect overwrites value of thymeleafSpringSecurityContext.
*
* @author johnniang
*/
public class HaloSpringSecurityDialect extends SpringSecurityDialect {
public class HaloSpringSecurityDialect extends SpringSecurityDialect implements InitializingBean {

private static final String SECURITY_CONTEXT_EXECUTION_ATTRIBUTE_NAME =
"ThymeleafReactiveModelAdditions:"
+ SpringSecurityContextUtils.SECURITY_CONTEXT_MODEL_ATTRIBUTE_NAME;
private static final String CSRF_EXECUTION_ATTRIBUTE_NAME =
"ThymeleafReactiveModelAdditions:" + CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME;

private final Map<String, Object> executionAttributes;

private final ServerSecurityContextRepository securityContextRepository;

public HaloSpringSecurityDialect(ServerSecurityContextRepository securityContextRepository) {
this.securityContextRepository = securityContextRepository;
executionAttributes = new HashMap<>(3, 1.0f);
initExecutionAttributes();
}

private void initExecutionAttributes() {
@Override
public void afterPropertiesSet() {
if (!SpringVersionUtils.isSpringWebFluxPresent()) {
return;
}

final Function<ServerWebExchange, Object> secCtxInitializer =
(exchange) -> securityContextRepository.load(exchange);

final Function<ServerWebExchange, Object> csrfTokenInitializer =
(exchange) -> {
final Mono<CsrfToken> csrfToken = exchange.getAttribute(CsrfToken.class.getName());
if (csrfToken == null) {
return Mono.empty();
}
return csrfToken.doOnSuccess(
token -> exchange.getAttributes()
.put(CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME, token));
};
// We have to build an anonymous authentication token here because the token won't be saved
// into repository during anonymous authentication.
var anonymousAuthentication =
new AnonymousAuthenticationToken("fallback", PRINCIPAL, createAuthorityList(Role));
var anonymousSecurityContext = new SecurityContextImpl(anonymousAuthentication);

executionAttributes.put(SECURITY_CONTEXT_EXECUTION_ATTRIBUTE_NAME, secCtxInitializer);
executionAttributes.put(CSRF_EXECUTION_ATTRIBUTE_NAME, csrfTokenInitializer);
}
final Function<ServerWebExchange, Object> secCtxInitializer =
exchange -> securityContextRepository.load(exchange)
.defaultIfEmpty(anonymousSecurityContext);

@Override
public Map<String, Object> getExecutionAttributes() {
return executionAttributes;
// Just overwrite the value of the attribute
getExecutionAttributes().put(SECURITY_CONTEXT_EXECUTION_ATTRIBUTE_NAME, secCtxInitializer);
}
}

0 comments on commit fb706df

Please sign in to comment.