Apply specific headers for portal endpoints (#2972)

#### What type of PR is this?

/kind improvement
/area core

#### What this PR does / why we need it:

This PR separates security configuration of RESTful APIs and portal pages to configure specific headers for portal pages, such as `Referrer-Policy` and `X-Frame-Options`.

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/2900

#### Special notes for your reviewer:

You can see the response headers of index page:

```diff
HTTP/1.1 200 OK
Content-Type: text/html
Content-Language: en-US
+ X-Content-Type-Options: nosniff
+ X-Frame-Options: SAMEORIGIN
+ X-XSS-Protection: 0
+ Referrer-Policy: strict-origin-when-cross-origin
content-encoding: gzip
content-length: 4285
```

and request headers with `Referer`:
```diff
GET / HTTP/1.1
Host: localhost:8090
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
+ Referer: http://localhost:8090/archives/12341234
Connection: keep-alive
Cookie: _ga_Z907HJBP8W=GS1.1.1670164888.1.1.1670165603.0.0.0; _ga=GA1.1.807839437.1670164889; SESSION=539e060e-c11e-4b6d-a749-882905b30a88; XSRF-TOKEN=4b692b55-638c-4497-8a4b-be00986eda90
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
```

#### Does this PR introduce a user-facing change?

```release-note
解决访问分析工具无法显示 referer 的问题
```
pull/2964/head
John Niang 2022-12-16 11:32:35 +08:00 committed by GitHub
parent 090b28b399
commit 09d4b40da8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 34 additions and 11 deletions

View File

@ -1,20 +1,27 @@
package run.halo.app.config;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN;
import static org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN;
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers;
import java.util.Set;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.userdetails.ReactiveUserDetailsService;
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.util.matcher.AndServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.MediaTypeServerWebExchangeMatcher;
import run.halo.app.core.extension.service.RoleService;
import run.halo.app.core.extension.service.UserService;
import run.halo.app.extension.ReactiveExtensionClient;
@ -40,18 +47,15 @@ public class WebServerSecurityConfig {
RoleService roleService,
ObjectProvider<SecurityConfigurer> securityConfigurers) {
http.authorizeExchange()
.pathMatchers("/api/**", "/apis/**", "/login", "/logout")
.access(new RequestInfoAuthorizationManager(roleService))
.pathMatchers("/**").permitAll()
.and()
.headers()
.frameOptions().mode(SAMEORIGIN)
.and()
.anonymous(anonymousSpec -> {
anonymousSpec.authorities(AnonymousUserConst.Role);
anonymousSpec.principal(AnonymousUserConst.PRINCIPAL);
http.securityMatcher(pathMatchers("/api/**", "/apis/**", "/login", "/logout"))
.authorizeExchange().anyExchange()
.access(new RequestInfoAuthorizationManager(roleService)).and()
.anonymous(spec -> {
spec.authorities(AnonymousUserConst.Role);
spec.principal(AnonymousUserConst.PRINCIPAL);
})
.formLogin(withDefaults())
.logout(withDefaults())
.httpBasic(withDefaults());
// Integrate with other configurers separately
@ -61,6 +65,25 @@ public class WebServerSecurityConfig {
return http.build();
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
SecurityWebFilterChain portalFilterChain(ServerHttpSecurity http) {
var pathMatcher = pathMatchers(HttpMethod.GET, "/**");
var mediaTypeMatcher = new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML);
mediaTypeMatcher.setIgnoredMediaTypes(Set.of(MediaType.ALL));
http.securityMatcher(new AndServerWebExchangeMatcher(pathMatcher, mediaTypeMatcher))
.authorizeExchange().anyExchange().permitAll().and()
.headers()
.frameOptions().mode(SAMEORIGIN)
.referrerPolicy().policy(STRICT_ORIGIN_WHEN_CROSS_ORIGIN).and()
.cache().disable().and()
.anonymous(spec -> {
spec.authorities(AnonymousUserConst.Role);
spec.principal(AnonymousUserConst.PRINCIPAL);
});
return http.build();
}
@Bean
ReactiveUserDetailsService userDetailsService(UserService userService,
RoleService roleService) {