Idiomatic Kotlin DSL for server HTTP security
Issue: gh-5558
This commit is contained in:
parent
6017510fdd
commit
39e09e4ca5
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager
|
||||
import org.springframework.security.authorization.AuthorityReactiveAuthorizationManager
|
||||
import org.springframework.security.authorization.AuthorizationDecision
|
||||
import org.springframework.security.authorization.ReactiveAuthorizationManager
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.web.server.authorization.AuthorizationContext
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] exchange authorization using idiomatic
|
||||
* Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
*/
|
||||
class AuthorizeExchangeDsl {
|
||||
private val authorizationRules = mutableListOf<ExchangeAuthorizationRule>()
|
||||
|
||||
/**
|
||||
* Adds an exchange authorization rule for an endpoint matching the provided
|
||||
* matcher.
|
||||
*
|
||||
* @param matcher the [RequestMatcher] to match incoming requests against
|
||||
* @param access the [ReactiveAuthorizationManager] which determines the access
|
||||
* to the specific matcher.
|
||||
* Some predefined shortcuts have already been created, such as
|
||||
* [hasAnyAuthority], [hasAnyRole], [permitAll], [authenticated] and more
|
||||
*/
|
||||
fun authorize(matcher: ServerWebExchangeMatcher = ServerWebExchangeMatchers.anyExchange(),
|
||||
access: ReactiveAuthorizationManager<AuthorizationContext> = authenticated) {
|
||||
authorizationRules.add(MatcherExchangeAuthorizationRule(matcher, access))
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an exchange authorization rule for an endpoint matching the provided
|
||||
* ant pattern.
|
||||
*
|
||||
* @param antPattern the ant ant pattern to match incoming requests against.
|
||||
* @param access the [ReactiveAuthorizationManager] which determines the access
|
||||
* to the specific matcher.
|
||||
* Some predefined shortcuts have already been created, such as
|
||||
* [hasAnyAuthority], [hasAnyRole], [permitAll], [authenticated] and more
|
||||
*/
|
||||
fun authorize(antPattern: String, access: ReactiveAuthorizationManager<AuthorizationContext> = authenticated) {
|
||||
authorizationRules.add(PatternExchangeAuthorizationRule(antPattern, access))
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches any exchange.
|
||||
*/
|
||||
val anyExchange: ServerWebExchangeMatcher = ServerWebExchangeMatchers.anyExchange()
|
||||
|
||||
/**
|
||||
* Allow access for anyone.
|
||||
*/
|
||||
val permitAll: ReactiveAuthorizationManager<AuthorizationContext> =
|
||||
ReactiveAuthorizationManager { _: Mono<Authentication>, _: AuthorizationContext -> Mono.just(AuthorizationDecision(true)) }
|
||||
|
||||
/**
|
||||
* Deny access for everyone.
|
||||
*/
|
||||
val denyAll: ReactiveAuthorizationManager<AuthorizationContext> =
|
||||
ReactiveAuthorizationManager { _: Mono<Authentication>, _: AuthorizationContext -> Mono.just(AuthorizationDecision(false)) }
|
||||
|
||||
/**
|
||||
* Require a specific role. This is a shortcut for [hasAuthority].
|
||||
*/
|
||||
fun hasRole(role: String): ReactiveAuthorizationManager<AuthorizationContext> =
|
||||
AuthorityReactiveAuthorizationManager.hasRole<AuthorizationContext>(role)
|
||||
|
||||
/**
|
||||
* Require any specific role. This is a shortcut for [hasAnyAuthority].
|
||||
*/
|
||||
fun hasAnyRole(vararg roles: String): ReactiveAuthorizationManager<AuthorizationContext> =
|
||||
AuthorityReactiveAuthorizationManager.hasAnyRole<AuthorizationContext>(*roles)
|
||||
|
||||
/**
|
||||
* Require a specific authority.
|
||||
*/
|
||||
fun hasAuthority(authority: String): ReactiveAuthorizationManager<AuthorizationContext> =
|
||||
AuthorityReactiveAuthorizationManager.hasAuthority<AuthorizationContext>(authority)
|
||||
|
||||
/**
|
||||
* Require any authority.
|
||||
*/
|
||||
fun hasAnyAuthority(vararg authorities: String): ReactiveAuthorizationManager<AuthorizationContext> =
|
||||
AuthorityReactiveAuthorizationManager.hasAnyAuthority<AuthorizationContext>(*authorities)
|
||||
|
||||
/**
|
||||
* Require an authenticated user.
|
||||
*/
|
||||
val authenticated: ReactiveAuthorizationManager<AuthorizationContext> =
|
||||
AuthenticatedReactiveAuthorizationManager.authenticated<AuthorizationContext>()
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.AuthorizeExchangeSpec) -> Unit {
|
||||
return { requests ->
|
||||
authorizationRules.forEach { rule ->
|
||||
when (rule) {
|
||||
is MatcherExchangeAuthorizationRule -> requests.matchers(rule.matcher).access(rule.rule)
|
||||
is PatternExchangeAuthorizationRule -> requests.pathMatchers(rule.pattern).access(rule.rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class MatcherExchangeAuthorizationRule(val matcher: ServerWebExchangeMatcher,
|
||||
override val rule: ReactiveAuthorizationManager<AuthorizationContext>) : ExchangeAuthorizationRule(rule)
|
||||
|
||||
private data class PatternExchangeAuthorizationRule(val pattern: String,
|
||||
override val rule: ReactiveAuthorizationManager<AuthorizationContext>) : ExchangeAuthorizationRule(rule)
|
||||
|
||||
private abstract class ExchangeAuthorizationRule(open val rule: ReactiveAuthorizationManager<AuthorizationContext>)
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] anonymous authentication using idiomatic
|
||||
* Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property key the key to identify tokens created for anonymous authentication
|
||||
* @property principal the principal for [Authentication] objects of anonymous users
|
||||
* @property authorities the [Authentication.getAuthorities] for anonymous users
|
||||
* @property authenticationFilter the [AnonymousAuthenticationWebFilter] used to populate
|
||||
* an anonymous user.
|
||||
*/
|
||||
class ServerAnonymousDsl {
|
||||
var key: String? = null
|
||||
var principal: Any? = null
|
||||
var authorities: List<GrantedAuthority>? = null
|
||||
var authenticationFilter: AnonymousAuthenticationWebFilter? = null
|
||||
|
||||
private var disabled = false
|
||||
|
||||
/**
|
||||
* Disables anonymous authentication
|
||||
*/
|
||||
fun disable() {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.AnonymousSpec) -> Unit {
|
||||
return { anonymous ->
|
||||
key?.also { anonymous.key(key) }
|
||||
principal?.also { anonymous.principal(principal) }
|
||||
authorities?.also { anonymous.authorities(authorities) }
|
||||
authenticationFilter?.also { anonymous.authenticationFilter(authenticationFilter) }
|
||||
if (disabled) {
|
||||
anonymous.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.web.cors.reactive.CorsConfigurationSource
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] CORS headers using idiomatic
|
||||
* Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property configurationSource the [CorsConfigurationSource] to use.
|
||||
*/
|
||||
class ServerCorsDsl {
|
||||
var configurationSource: CorsConfigurationSource? = null
|
||||
|
||||
private var disabled = false
|
||||
|
||||
/**
|
||||
* Disables CORS support within Spring Security.
|
||||
*/
|
||||
fun disable() {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.CorsSpec) -> Unit {
|
||||
return { cors ->
|
||||
configurationSource?.also { cors.configurationSource(configurationSource) }
|
||||
if (disabled) {
|
||||
cors.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
|
||||
import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] CSRF protection using idiomatic
|
||||
* Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property accessDeniedHandler the [ServerAccessDeniedHandler] used when a CSRF token is invalid.
|
||||
* @property csrfTokenRepository the [ServerCsrfTokenRepository] used to persist the CSRF token.
|
||||
* @property requireCsrfProtectionMatcher the [ServerWebExchangeMatcher] used to determine when CSRF protection
|
||||
* is enabled.
|
||||
*/
|
||||
class ServerCsrfDsl {
|
||||
var accessDeniedHandler: ServerAccessDeniedHandler? = null
|
||||
var csrfTokenRepository: ServerCsrfTokenRepository? = null
|
||||
var requireCsrfProtectionMatcher: ServerWebExchangeMatcher? = null
|
||||
|
||||
private var disabled = false
|
||||
|
||||
/**
|
||||
* Disables CSRF protection
|
||||
*/
|
||||
fun disable() {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.CsrfSpec) -> Unit {
|
||||
return { csrf ->
|
||||
accessDeniedHandler?.also { csrf.accessDeniedHandler(accessDeniedHandler) }
|
||||
csrfTokenRepository?.also { csrf.csrfTokenRepository(csrfTokenRepository) }
|
||||
requireCsrfProtectionMatcher?.also { csrf.requireCsrfProtectionMatcher(requireCsrfProtectionMatcher) }
|
||||
if (disabled) {
|
||||
csrf.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
|
||||
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] exception handling using idiomatic Kotlin
|
||||
* code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property authenticationEntryPoint the [ServerAuthenticationEntryPoint] to use when
|
||||
* the application request authentication
|
||||
* @property accessDeniedHandler the [ServerAccessDeniedHandler] to use when an
|
||||
* authenticated user does not hold a required authority
|
||||
*/
|
||||
class ServerExceptionHandlingDsl {
|
||||
var authenticationEntryPoint: ServerAuthenticationEntryPoint? = null
|
||||
var accessDeniedHandler: ServerAccessDeniedHandler? = null
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.ExceptionHandlingSpec) -> Unit {
|
||||
return { exceptionHandling ->
|
||||
authenticationEntryPoint?.also { exceptionHandling.authenticationEntryPoint(authenticationEntryPoint) }
|
||||
accessDeniedHandler?.also { exceptionHandling.accessDeniedHandler(accessDeniedHandler) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.context.SecurityContext
|
||||
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler
|
||||
import org.springframework.security.web.server.context.ReactorContextWebFilter
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] form login using idiomatic
|
||||
* Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property authenticationManager the [ReactiveAuthenticationManager] used to authenticate.
|
||||
* @property loginPage the url to redirect to which provides a form to log in (i.e. "/login").
|
||||
* If this is customized:
|
||||
* - The default log in & log out page are no longer provided
|
||||
* - The application must render a log in page at the provided URL
|
||||
* - The application must render an authentication error page at the provided URL + "?error"
|
||||
* - Authentication will occur for POST to the provided URL
|
||||
* @property authenticationEntryPoint configures how to request for authentication.
|
||||
* @property requiresAuthenticationMatcher configures when authentication is performed.
|
||||
* @property authenticationSuccessHandler the [ServerAuthenticationSuccessHandler] used after
|
||||
* authentication success.
|
||||
* @property authenticationFailureHandler the [ServerAuthenticationFailureHandler] used to handle
|
||||
* a failed authentication.
|
||||
* @property securityContextRepository the [ServerSecurityContextRepository] used to save
|
||||
* the [Authentication]. For the [SecurityContext] to be loaded on subsequent requests the
|
||||
* [ReactorContextWebFilter] must be configured to be able to load the value (they are not
|
||||
* implicitly linked).
|
||||
*/
|
||||
class ServerFormLoginDsl {
|
||||
var authenticationManager: ReactiveAuthenticationManager? = null
|
||||
var loginPage: String? = null
|
||||
var authenticationEntryPoint: ServerAuthenticationEntryPoint? = null
|
||||
var requiresAuthenticationMatcher: ServerWebExchangeMatcher? = null
|
||||
var authenticationSuccessHandler: ServerAuthenticationSuccessHandler? = null
|
||||
var authenticationFailureHandler: ServerAuthenticationFailureHandler? = null
|
||||
var securityContextRepository: ServerSecurityContextRepository? = null
|
||||
|
||||
private var disabled = false
|
||||
|
||||
/**
|
||||
* Disables HTTP basic authentication
|
||||
*/
|
||||
fun disable() {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.FormLoginSpec) -> Unit {
|
||||
return { formLogin ->
|
||||
authenticationManager?.also { formLogin.authenticationManager(authenticationManager) }
|
||||
loginPage?.also { formLogin.loginPage(loginPage) }
|
||||
authenticationEntryPoint?.also { formLogin.authenticationEntryPoint(authenticationEntryPoint) }
|
||||
requiresAuthenticationMatcher?.also { formLogin.requiresAuthenticationMatcher(requiresAuthenticationMatcher) }
|
||||
authenticationSuccessHandler?.also { formLogin.authenticationSuccessHandler(authenticationSuccessHandler) }
|
||||
authenticationFailureHandler?.also { formLogin.authenticationFailureHandler(authenticationFailureHandler) }
|
||||
securityContextRepository?.also { formLogin.securityContextRepository(securityContextRepository) }
|
||||
if (disabled) {
|
||||
formLogin.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.config.web.server.headers.*
|
||||
import org.springframework.security.web.server.header.*
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] headers using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
*/
|
||||
class ServerHeadersDsl {
|
||||
private var contentTypeOptions: ((ServerHttpSecurity.HeaderSpec.ContentTypeOptionsSpec) -> Unit)? = null
|
||||
private var xssProtection: ((ServerHttpSecurity.HeaderSpec.XssProtectionSpec) -> Unit)? = null
|
||||
private var cacheControl: ((ServerHttpSecurity.HeaderSpec.CacheSpec) -> Unit)? = null
|
||||
private var hsts: ((ServerHttpSecurity.HeaderSpec.HstsSpec) -> Unit)? = null
|
||||
private var frameOptions: ((ServerHttpSecurity.HeaderSpec.FrameOptionsSpec) -> Unit)? = null
|
||||
private var contentSecurityPolicy: ((ServerHttpSecurity.HeaderSpec.ContentSecurityPolicySpec) -> Unit)? = null
|
||||
private var referrerPolicy: ((ServerHttpSecurity.HeaderSpec.ReferrerPolicySpec) -> Unit)? = null
|
||||
private var featurePolicyDirectives: String? = null
|
||||
|
||||
private var disabled = false
|
||||
|
||||
/**
|
||||
* Configures the [ContentTypeOptionsServerHttpHeadersWriter] which inserts the <a href=
|
||||
* "https://msdn.microsoft.com/en-us/library/ie/gg622941(v=vs.85).aspx"
|
||||
* >X-Content-Type-Options header</a>
|
||||
*
|
||||
* @param contentTypeOptionsConfig the customization to apply to the header
|
||||
*/
|
||||
fun contentTypeOptions(contentTypeOptionsConfig: ServerContentTypeOptionsDsl.() -> Unit) {
|
||||
this.contentTypeOptions = ServerContentTypeOptionsDsl().apply(contentTypeOptionsConfig).get()
|
||||
}
|
||||
|
||||
/**
|
||||
* <strong>Note this is not comprehensive XSS protection!</strong>
|
||||
*
|
||||
* <p>
|
||||
* Allows customizing the [XXssProtectionServerHttpHeadersWriter] which adds the <a href=
|
||||
* "https://blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx"
|
||||
* >X-XSS-Protection header</a>
|
||||
* </p>
|
||||
*
|
||||
* @param xssProtectionConfig the customization to apply to the header
|
||||
*/
|
||||
fun xssProtection(xssProtectionConfig: ServerXssProtectionDsl.() -> Unit) {
|
||||
this.xssProtection = ServerXssProtectionDsl().apply(xssProtectionConfig).get()
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows customizing the [CacheControlServerHttpHeadersWriter]. Specifically it adds
|
||||
* the following headers:
|
||||
* <ul>
|
||||
* <li>Cache-Control: no-cache, no-store, max-age=0, must-revalidate</li>
|
||||
* <li>Pragma: no-cache</li>
|
||||
* <li>Expires: 0</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param cacheControlConfig the customization to apply to the headers
|
||||
*/
|
||||
fun cache(cacheControlConfig: ServerCacheControlDsl.() -> Unit) {
|
||||
this.cacheControl = ServerCacheControlDsl().apply(cacheControlConfig).get()
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows customizing the [StrictTransportSecurityServerHttpHeadersWriter] which provides support
|
||||
* for <a href="https://tools.ietf.org/html/rfc6797">HTTP Strict Transport Security
|
||||
* (HSTS)</a>.
|
||||
*
|
||||
* @param hstsConfig the customization to apply to the header
|
||||
*/
|
||||
fun hsts(hstsConfig: ServerHttpStrictTransportSecurityDsl.() -> Unit) {
|
||||
this.hsts = ServerHttpStrictTransportSecurityDsl().apply(hstsConfig).get()
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows customizing the [XFrameOptionsServerHttpHeadersWriter] which add the X-Frame-Options
|
||||
* header.
|
||||
*
|
||||
* @param frameOptionsConfig the customization to apply to the header
|
||||
*/
|
||||
fun frameOptions(frameOptionsConfig: ServerFrameOptionsDsl.() -> Unit) {
|
||||
this.frameOptions = ServerFrameOptionsDsl().apply(frameOptionsConfig).get()
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows configuration for <a href="https://www.w3.org/TR/CSP2/">Content Security Policy (CSP) Level 2</a>.
|
||||
*
|
||||
* @param contentSecurityPolicyConfig the customization to apply to the header
|
||||
*/
|
||||
fun contentSecurityPolicy(contentSecurityPolicyConfig: ServerContentSecurityPolicyDsl.() -> Unit) {
|
||||
this.contentSecurityPolicy = ServerContentSecurityPolicyDsl().apply(contentSecurityPolicyConfig).get()
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows configuration for <a href="https://www.w3.org/TR/referrer-policy/">Referrer Policy</a>.
|
||||
*
|
||||
* <p>
|
||||
* Configuration is provided to the [ReferrerPolicyServerHttpHeadersWriter] which support the writing
|
||||
* of the header as detailed in the W3C Technical Report:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Referrer-Policy</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param referrerPolicyConfig the customization to apply to the header
|
||||
*/
|
||||
fun referrerPolicy(referrerPolicyConfig: ServerReferrerPolicyDsl.() -> Unit) {
|
||||
this.referrerPolicy = ServerReferrerPolicyDsl().apply(referrerPolicyConfig).get()
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows configuration for <a href="https://wicg.github.io/feature-policy/">Feature
|
||||
* Policy</a>.
|
||||
*
|
||||
* <p>
|
||||
* Calling this method automatically enables (includes) the Feature-Policy
|
||||
* header in the response using the supplied policy directive(s).
|
||||
* <p>
|
||||
*
|
||||
* @param policyDirectives policyDirectives the security policy directive(s)
|
||||
*/
|
||||
fun featurePolicy(policyDirectives: String) {
|
||||
this.featurePolicyDirectives = policyDirectives
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables HTTP response headers.
|
||||
*/
|
||||
fun disable() {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.HeaderSpec) -> Unit {
|
||||
return { headers ->
|
||||
contentTypeOptions?.also {
|
||||
headers.contentTypeOptions(contentTypeOptions)
|
||||
}
|
||||
xssProtection?.also {
|
||||
headers.xssProtection(xssProtection)
|
||||
}
|
||||
cacheControl?.also {
|
||||
headers.cache(cacheControl)
|
||||
}
|
||||
hsts?.also {
|
||||
headers.hsts(hsts)
|
||||
}
|
||||
frameOptions?.also {
|
||||
headers.frameOptions(frameOptions)
|
||||
}
|
||||
contentSecurityPolicy?.also {
|
||||
headers.contentSecurityPolicy(contentSecurityPolicy)
|
||||
}
|
||||
featurePolicyDirectives?.also {
|
||||
headers.featurePolicy(featurePolicyDirectives)
|
||||
}
|
||||
referrerPolicy?.also {
|
||||
headers.referrerPolicy(referrerPolicy)
|
||||
}
|
||||
if (disabled) {
|
||||
headers.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.context.SecurityContext
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
|
||||
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
|
||||
import org.springframework.security.web.server.context.ReactorContextWebFilter
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] basic authorization using idiomatic
|
||||
* Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property authenticationManager the [ReactiveAuthenticationManager] used to authenticate.
|
||||
* @property securityContextRepository the [ServerSecurityContextRepository] used to save
|
||||
* the [Authentication]. For the [SecurityContext] to be loaded on subsequent requests the
|
||||
* [ReactorContextWebFilter] must be configured to be able to load the value (they are not
|
||||
* implicitly linked).
|
||||
* @property authenticationEntryPoint the [ServerAuthenticationEntryPoint] to be
|
||||
* populated on [BasicAuthenticationFilter] in the event that authentication fails.
|
||||
*/
|
||||
class ServerHttpBasicDsl {
|
||||
var authenticationManager: ReactiveAuthenticationManager? = null
|
||||
var securityContextRepository: ServerSecurityContextRepository? = null
|
||||
var authenticationEntryPoint: ServerAuthenticationEntryPoint? = null
|
||||
|
||||
private var disabled = false
|
||||
|
||||
/**
|
||||
* Disables HTTP basic authentication
|
||||
*/
|
||||
fun disable() {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.HttpBasicSpec) -> Unit {
|
||||
return { httpBasic ->
|
||||
authenticationManager?.also { httpBasic.authenticationManager(authenticationManager) }
|
||||
securityContextRepository?.also { httpBasic.securityContextRepository(securityContextRepository) }
|
||||
authenticationEntryPoint?.also { httpBasic.authenticationEntryPoint(authenticationEntryPoint) }
|
||||
if (disabled) {
|
||||
httpBasic.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,528 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
|
||||
/**
|
||||
* Configures [ServerHttpSecurity] using a [ServerHttpSecurity Kotlin DSL][ServerHttpSecurityDsl].
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* authorizeExchange {
|
||||
* exchange("/public", permitAll)
|
||||
* exchange(anyExchange, authenticated)
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @param httpConfiguration the configurations to apply to [ServerHttpSecurity]
|
||||
*/
|
||||
operator fun ServerHttpSecurity.invoke(httpConfiguration: ServerHttpSecurityDsl.() -> Unit): SecurityWebFilterChain =
|
||||
ServerHttpSecurityDsl(this, httpConfiguration).build()
|
||||
|
||||
/**
|
||||
* A [ServerHttpSecurity] Kotlin DSL created by [`http { }`][invoke]
|
||||
* in order to configure [ServerHttpSecurity] using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @param init the configurations to apply to the provided [ServerHttpSecurity]
|
||||
*/
|
||||
class ServerHttpSecurityDsl(private val http: ServerHttpSecurity, private val init: ServerHttpSecurityDsl.() -> Unit) {
|
||||
|
||||
/**
|
||||
* Allows configuring the [ServerHttpSecurity] to only be invoked when matching the
|
||||
* provided [ServerWebExchangeMatcher].
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* securityMatcher(PathPatternParserServerWebExchangeMatcher("/api/**"))
|
||||
* formLogin {
|
||||
* loginPage = "/log-in"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param securityMatcher a [ServerWebExchangeMatcher] used to determine whether this
|
||||
* configuration should be invoked.
|
||||
*/
|
||||
fun securityMatcher(securityMatcher: ServerWebExchangeMatcher) {
|
||||
this.http.securityMatcher(securityMatcher)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables form based authentication.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* formLogin {
|
||||
* loginPage = "/log-in"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param formLoginConfiguration custom configuration to apply to the form based
|
||||
* authentication
|
||||
* @see [ServerFormLoginDsl]
|
||||
*/
|
||||
fun formLogin(formLoginConfiguration: ServerFormLoginDsl.() -> Unit) {
|
||||
val formLoginCustomizer = ServerFormLoginDsl().apply(formLoginConfiguration).get()
|
||||
this.http.formLogin(formLoginCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows restricting access based upon the [ServerWebExchange]
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* authorizeExchange {
|
||||
* exchange("/public", permitAll)
|
||||
* exchange(anyExchange, authenticated)
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param authorizeExchangeConfiguration custom configuration that specifies
|
||||
* access for an exchange
|
||||
* @see [AuthorizeExchangeDsl]
|
||||
*/
|
||||
fun authorizeExchange(authorizeExchangeConfiguration: AuthorizeExchangeDsl.() -> Unit) {
|
||||
val authorizeExchangeCustomizer = AuthorizeExchangeDsl().apply(authorizeExchangeConfiguration).get()
|
||||
this.http.authorizeExchange(authorizeExchangeCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables HTTP basic authentication.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* httpBasic { }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param httpBasicConfiguration custom configuration to be applied to the
|
||||
* HTTP basic authentication
|
||||
* @see [ServerHttpBasicDsl]
|
||||
*/
|
||||
fun httpBasic(httpBasicConfiguration: ServerHttpBasicDsl.() -> Unit) {
|
||||
val httpBasicCustomizer = ServerHttpBasicDsl().apply(httpBasicConfiguration).get()
|
||||
this.http.httpBasic(httpBasicCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows configuring response headers.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* headers {
|
||||
* referrerPolicy {
|
||||
* policy = ReferrerPolicy.SAME_ORIGIN
|
||||
* }
|
||||
* frameOptions {
|
||||
* mode = Mode.DENY
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param headersConfiguration custom configuration to be applied to the
|
||||
* response headers
|
||||
* @see [ServerHeadersDsl]
|
||||
*/
|
||||
fun headers(headersConfiguration: ServerHeadersDsl.() -> Unit) {
|
||||
val headersCustomizer = ServerHeadersDsl().apply(headersConfiguration).get()
|
||||
this.http.headers(headersCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows configuring CORS.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* cors {
|
||||
* configurationSource = customConfigurationSource
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param corsConfiguration custom configuration to be applied to the
|
||||
* CORS headers
|
||||
* @see [ServerCorsDsl]
|
||||
*/
|
||||
fun cors(corsConfiguration: ServerCorsDsl.() -> Unit) {
|
||||
val corsCustomizer = ServerCorsDsl().apply(corsConfiguration).get()
|
||||
this.http.cors(corsCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows configuring HTTPS redirection rules.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* redirectToHttps {
|
||||
* httpsRedirectWhen {
|
||||
* it.request.headers.containsKey("X-Requires-Https")
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param httpsRedirectConfiguration custom configuration for the HTTPS redirect
|
||||
* rules.
|
||||
* @see [ServerHttpsRedirectDsl]
|
||||
*/
|
||||
fun redirectToHttps(httpsRedirectConfiguration: ServerHttpsRedirectDsl.() -> Unit) {
|
||||
val httpsRedirectCustomizer = ServerHttpsRedirectDsl().apply(httpsRedirectConfiguration).get()
|
||||
this.http.redirectToHttps(httpsRedirectCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows configuring exception handling.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* exceptionHandling {
|
||||
* authenticationEntryPoint = RedirectServerAuthenticationEntryPoint("/auth")
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param exceptionHandlingConfiguration custom configuration to apply to
|
||||
* exception handling
|
||||
* @see [ServerExceptionHandlingDsl]
|
||||
*/
|
||||
fun exceptionHandling(exceptionHandlingConfiguration: ServerExceptionHandlingDsl.() -> Unit) {
|
||||
val exceptionHandlingCustomizer = ServerExceptionHandlingDsl().apply(exceptionHandlingConfiguration).get()
|
||||
this.http.exceptionHandling(exceptionHandlingCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds X509 based pre authentication to an application using a certificate provided by a client.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* x509 { }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param x509Configuration custom configuration to apply to the X509 based pre authentication
|
||||
* @see [ServerX509Dsl]
|
||||
*/
|
||||
fun x509(x509Configuration: ServerX509Dsl.() -> Unit) {
|
||||
val x509Customizer = ServerX509Dsl().apply(x509Configuration).get()
|
||||
this.http.x509(x509Customizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows configuring request cache which is used when a flow is interrupted (i.e. due to requesting credentials)
|
||||
* so that the request can be replayed after authentication.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* requestCache { }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param requestCacheConfiguration custom configuration to apply to the request cache
|
||||
* @see [ServerRequestCacheDsl]
|
||||
*/
|
||||
fun requestCache(requestCacheConfiguration: ServerRequestCacheDsl.() -> Unit) {
|
||||
val requestCacheCustomizer = ServerRequestCacheDsl().apply(requestCacheConfiguration).get()
|
||||
this.http.requestCache(requestCacheCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables CSRF protection.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* csrf { }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param csrfConfiguration custom configuration to apply to the CSRF protection
|
||||
* @see [ServerCsrfDsl]
|
||||
*/
|
||||
fun csrf(csrfConfiguration: ServerCsrfDsl.() -> Unit) {
|
||||
val csrfCustomizer = ServerCsrfDsl().apply(csrfConfiguration).get()
|
||||
this.http.csrf(csrfCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides logout support.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* logout {
|
||||
* logoutUrl = "/sign-out"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param logoutConfiguration custom configuration to apply to logout
|
||||
* @see [ServerLogoutDsl]
|
||||
*/
|
||||
fun logout(logoutConfiguration: ServerLogoutDsl.() -> Unit) {
|
||||
val logoutCustomizer = ServerLogoutDsl().apply(logoutConfiguration).get()
|
||||
this.http.logout(logoutCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables and configures anonymous authentication.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* anonymous {
|
||||
* authorities = listOf(SimpleGrantedAuthority("ROLE_ANON"))
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param anonymousConfiguration custom configuration to apply to anonymous authentication
|
||||
* @see [ServerAnonymousDsl]
|
||||
*/
|
||||
fun anonymous(anonymousConfiguration: ServerAnonymousDsl.() -> Unit) {
|
||||
val anonymousCustomizer = ServerAnonymousDsl().apply(anonymousConfiguration).get()
|
||||
this.http.anonymous(anonymousCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider.
|
||||
* A [ReactiveClientRegistrationRepository] is required and must be registered as a Bean or
|
||||
* configured via [ServerOAuth2LoginDsl.clientRegistrationRepository].
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* oauth2Login {
|
||||
* clientRegistrationRepository = getClientRegistrationRepository()
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param oauth2LoginConfiguration custom configuration to configure the OAuth 2.0 Login
|
||||
* @see [ServerOAuth2LoginDsl]
|
||||
*/
|
||||
fun oauth2Login(oauth2LoginConfiguration: ServerOAuth2LoginDsl.() -> Unit) {
|
||||
val oauth2LoginCustomizer = ServerOAuth2LoginDsl().apply(oauth2LoginConfiguration).get()
|
||||
this.http.oauth2Login(oauth2LoginCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures OAuth2 client support.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* oauth2Client {
|
||||
* clientRegistrationRepository = getClientRegistrationRepository()
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param oauth2ClientConfiguration custom configuration to configure the OAuth 2.0 client
|
||||
* @see [ServerOAuth2ClientDsl]
|
||||
*/
|
||||
fun oauth2Client(oauth2ClientConfiguration: ServerOAuth2ClientDsl.() -> Unit) {
|
||||
val oauth2ClientCustomizer = ServerOAuth2ClientDsl().apply(oauth2ClientConfiguration).get()
|
||||
this.http.oauth2Client(oauth2ClientCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures OAuth2 resource server support.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* oauth2ResourceServer {
|
||||
* jwt { }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param oauth2ResourceServerConfiguration custom configuration to configure the OAuth 2.0 resource server
|
||||
* @see [ServerOAuth2ResourceServerDsl]
|
||||
*/
|
||||
fun oauth2ResourceServer(oauth2ResourceServerConfiguration: ServerOAuth2ResourceServerDsl.() -> Unit) {
|
||||
val oauth2ResourceServerCustomizer = ServerOAuth2ResourceServerDsl().apply(oauth2ResourceServerConfiguration).get()
|
||||
this.http.oauth2ResourceServer(oauth2ResourceServerCustomizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply all configurations to the provided [ServerHttpSecurity]
|
||||
*/
|
||||
internal fun build(): SecurityWebFilterChain {
|
||||
init()
|
||||
return this.http.build()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.web.PortMapper
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] HTTPS redirection rules using idiomatic
|
||||
* Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property portMapper the [PortMapper] that specifies a custom HTTPS port to redirect to.
|
||||
*/
|
||||
class ServerHttpsRedirectDsl {
|
||||
var portMapper: PortMapper? = null
|
||||
|
||||
private var redirectMatchers: Array<out ServerWebExchangeMatcher>? = null
|
||||
private var redirectMatcherFunction: ((ServerWebExchange) -> Boolean)? = null
|
||||
|
||||
/**
|
||||
* Configures when this filter should redirect to https.
|
||||
* If invoked multiple times, whether a matcher or a function is provided, only the
|
||||
* last redirect rule will apply and all previous rules will be overridden.
|
||||
*
|
||||
* @param redirectMatchers the list of conditions that, when any are met, the
|
||||
* filter should redirect to https.
|
||||
*/
|
||||
fun httpsRedirectWhen(vararg redirectMatchers: ServerWebExchangeMatcher) {
|
||||
this.redirectMatcherFunction = null
|
||||
this.redirectMatchers = redirectMatchers
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures when this filter should redirect to https.
|
||||
* If invoked multiple times, whether a matcher or a function is provided, only the
|
||||
* last redirect rule will apply and all previous rules will be overridden.
|
||||
*
|
||||
* @param redirectMatcherFunction the condition in which the filter should redirect to
|
||||
* https.
|
||||
*/
|
||||
fun httpsRedirectWhen(redirectMatcherFunction: (ServerWebExchange) -> Boolean) {
|
||||
this.redirectMatchers = null
|
||||
this.redirectMatcherFunction = redirectMatcherFunction
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.HttpsRedirectSpec) -> Unit {
|
||||
return { httpsRedirect ->
|
||||
portMapper?.also { httpsRedirect.portMapper(portMapper) }
|
||||
redirectMatchers?.also { httpsRedirect.httpsRedirectWhen(*redirectMatchers!!) }
|
||||
redirectMatcherFunction?.also { httpsRedirect.httpsRedirectWhen(redirectMatcherFunction) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler
|
||||
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] logout support using idiomatic Kotlin
|
||||
* code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property logoutHandler a [ServerLogoutHandler] that is invoked when logout occurs.
|
||||
* @property logoutUrl the URL that triggers logout to occur.
|
||||
* @property requiresLogout the [ServerWebExchangeMatcher] that triggers logout to occur.
|
||||
* @property logoutSuccessHandler the [ServerLogoutSuccessHandler] to use after logout has
|
||||
* occurred.
|
||||
*/
|
||||
class ServerLogoutDsl {
|
||||
var logoutHandler: ServerLogoutHandler? = null
|
||||
var logoutUrl: String? = null
|
||||
var requiresLogout: ServerWebExchangeMatcher? = null
|
||||
var logoutSuccessHandler: ServerLogoutSuccessHandler? = null
|
||||
|
||||
private var disabled = false
|
||||
|
||||
/**
|
||||
* Disables logout
|
||||
*/
|
||||
fun disable() {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.LogoutSpec) -> Unit {
|
||||
return { logout ->
|
||||
logoutHandler?.also { logout.logoutHandler(logoutHandler) }
|
||||
logoutUrl?.also { logout.logoutUrl(logoutUrl) }
|
||||
requiresLogout?.also { logout.requiresLogout(requiresLogout) }
|
||||
logoutSuccessHandler?.also { logout.logoutSuccessHandler(logoutSuccessHandler) }
|
||||
if (disabled) {
|
||||
logout.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
|
||||
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure the [ServerHttpSecurity] OAuth 2.0 client using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property authenticationManager the [ReactiveAuthenticationManager] used to determine if the provided
|
||||
* [Authentication] can be authenticated.
|
||||
* @property authenticationConverter the [ServerAuthenticationConverter] used for converting from a [ServerWebExchange]
|
||||
* to an [Authentication].
|
||||
* @property clientRegistrationRepository the repository of client registrations.
|
||||
* @property authorizedClientRepository the repository for authorized client(s).
|
||||
* @property authorizationRequestRepository the repository to use for storing [OAuth2AuthorizationRequest]s.
|
||||
*/
|
||||
class ServerOAuth2ClientDsl {
|
||||
var authenticationManager: ReactiveAuthenticationManager? = null
|
||||
var authenticationConverter: ServerAuthenticationConverter? = null
|
||||
var clientRegistrationRepository: ReactiveClientRegistrationRepository? = null
|
||||
var authorizedClientRepository: ServerOAuth2AuthorizedClientRepository? = null
|
||||
var authorizationRequestRepository: ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.OAuth2ClientSpec) -> Unit {
|
||||
return { oauth2Client ->
|
||||
authenticationManager?.also { oauth2Client.authenticationManager(authenticationManager) }
|
||||
authenticationConverter?.also { oauth2Client.authenticationConverter(authenticationConverter) }
|
||||
clientRegistrationRepository?.also { oauth2Client.clientRegistrationRepository(clientRegistrationRepository) }
|
||||
authorizedClientRepository?.also { oauth2Client.authorizedClientRepository(authorizedClientRepository) }
|
||||
authorizationRequestRepository?.also { oauth2Client.authorizationRequestRepository(authorizationRequestRepository) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
|
||||
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver
|
||||
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] OAuth 2.0 login using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property authenticationManager the [ReactiveAuthenticationManager] used to determine if the provided
|
||||
* [Authentication] can be authenticated.
|
||||
* @property securityContextRepository the [ServerSecurityContextRepository] used to save the [Authentication].
|
||||
* @property authenticationSuccessHandler the [ServerAuthenticationSuccessHandler] used after authentication success.
|
||||
* @property authenticationFailureHandler the [ServerAuthenticationFailureHandler] used after authentication failure.
|
||||
* @property authenticationConverter the [ServerAuthenticationConverter] used for converting from a [ServerWebExchange]
|
||||
* to an [Authentication].
|
||||
* @property clientRegistrationRepository the repository of client registrations.
|
||||
* @property authorizedClientService the service responsible for associating an access token to a client and resource
|
||||
* owner.
|
||||
* @property authorizedClientRepository the repository for authorized client(s).
|
||||
* @property authorizationRequestRepository the repository to use for storing [OAuth2AuthorizationRequest]s.
|
||||
* @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]s.
|
||||
* @property authenticationMatcher the [ServerWebExchangeMatcher] used for determining if the request is an
|
||||
* authentication request.
|
||||
*/
|
||||
class ServerOAuth2LoginDsl {
|
||||
var authenticationManager: ReactiveAuthenticationManager? = null
|
||||
var securityContextRepository: ServerSecurityContextRepository? = null
|
||||
var authenticationSuccessHandler: ServerAuthenticationSuccessHandler? = null
|
||||
var authenticationFailureHandler: ServerAuthenticationFailureHandler? = null
|
||||
var authenticationConverter: ServerAuthenticationConverter? = null
|
||||
var clientRegistrationRepository: ReactiveClientRegistrationRepository? = null
|
||||
var authorizedClientService: ReactiveOAuth2AuthorizedClientService? = null
|
||||
var authorizedClientRepository: ServerOAuth2AuthorizedClientRepository? = null
|
||||
var authorizationRequestRepository: ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
|
||||
var authorizationRequestResolver: ServerOAuth2AuthorizationRequestResolver? = null
|
||||
var authenticationMatcher: ServerWebExchangeMatcher? = null
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.OAuth2LoginSpec) -> Unit {
|
||||
return { oauth2Login ->
|
||||
authenticationManager?.also { oauth2Login.authenticationManager(authenticationManager) }
|
||||
securityContextRepository?.also { oauth2Login.securityContextRepository(securityContextRepository) }
|
||||
authenticationSuccessHandler?.also { oauth2Login.authenticationSuccessHandler(authenticationSuccessHandler) }
|
||||
authenticationFailureHandler?.also { oauth2Login.authenticationFailureHandler(authenticationFailureHandler) }
|
||||
authenticationConverter?.also { oauth2Login.authenticationConverter(authenticationConverter) }
|
||||
clientRegistrationRepository?.also { oauth2Login.clientRegistrationRepository(clientRegistrationRepository) }
|
||||
authorizedClientService?.also { oauth2Login.authorizedClientService(authorizedClientService) }
|
||||
authorizedClientRepository?.also { oauth2Login.authorizedClientRepository(authorizedClientRepository) }
|
||||
authorizationRequestRepository?.also { oauth2Login.authorizationRequestRepository(authorizationRequestRepository) }
|
||||
authorizationRequestResolver?.also { oauth2Login.authorizationRequestResolver(authorizationRequestResolver) }
|
||||
authenticationMatcher?.also { oauth2Login.authenticationMatcher(authenticationMatcher) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver
|
||||
import org.springframework.security.config.web.server.oauth2.resourceserver.ServerJwtDsl
|
||||
import org.springframework.security.config.web.server.oauth2.resourceserver.ServerOpaqueTokenDsl
|
||||
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
|
||||
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] OAuth 2.0 resource server using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property accessDeniedHandler the [ServerAccessDeniedHandler] to use for requests authenticating with
|
||||
* Bearer Tokens.
|
||||
* @property authenticationEntryPoint the [ServerAuthenticationEntryPoint] to use for requests authenticating with
|
||||
* Bearer Tokens.
|
||||
* @property bearerTokenConverter the [ServerAuthenticationConverter] to use for requests authenticating with
|
||||
* Bearer Tokens.
|
||||
* @property authenticationManagerResolver the [ReactiveAuthenticationManagerResolver] to use.
|
||||
*/
|
||||
class ServerOAuth2ResourceServerDsl {
|
||||
var accessDeniedHandler: ServerAccessDeniedHandler? = null
|
||||
var authenticationEntryPoint: ServerAuthenticationEntryPoint? = null
|
||||
var bearerTokenConverter: ServerAuthenticationConverter? = null
|
||||
var authenticationManagerResolver: ReactiveAuthenticationManagerResolver<ServerWebExchange>? = null
|
||||
|
||||
private var jwt: ((ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec) -> Unit)? = null
|
||||
private var opaqueToken: ((ServerHttpSecurity.OAuth2ResourceServerSpec.OpaqueTokenSpec) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* Enables JWT-encoded bearer token support.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* oauth2ResourceServer {
|
||||
* jwt {
|
||||
* jwkSetUri = "https://example.com/oauth2/jwk"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param jwtConfig custom configurations to configure JWT resource server support
|
||||
* @see [ServerJwtDsl]
|
||||
*/
|
||||
fun jwt(jwtConfig: ServerJwtDsl.() -> Unit) {
|
||||
this.jwt = ServerJwtDsl().apply(jwtConfig).get()
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables opaque token support.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```
|
||||
* @EnableWebFluxSecurity
|
||||
* class SecurityConfig {
|
||||
*
|
||||
* @Bean
|
||||
* fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
* return http {
|
||||
* oauth2ResourceServer {
|
||||
* opaqueToken {
|
||||
* introspectionUri = "https://example.com/introspect"
|
||||
* introspectionClientCredentials("client", "secret")
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param opaqueTokenConfig custom configurations to configure JWT resource server support
|
||||
* @see [ServerOpaqueTokenDsl]
|
||||
*/
|
||||
fun opaqueToken(opaqueTokenConfig: ServerOpaqueTokenDsl.() -> Unit) {
|
||||
this.opaqueToken = ServerOpaqueTokenDsl().apply(opaqueTokenConfig).get()
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec) -> Unit {
|
||||
return { oauth2ResourceServer ->
|
||||
accessDeniedHandler?.also { oauth2ResourceServer.accessDeniedHandler(accessDeniedHandler) }
|
||||
authenticationEntryPoint?.also { oauth2ResourceServer.authenticationEntryPoint(authenticationEntryPoint) }
|
||||
bearerTokenConverter?.also { oauth2ResourceServer.bearerTokenConverter(bearerTokenConverter) }
|
||||
authenticationManagerResolver?.also { oauth2ResourceServer.authenticationManagerResolver(authenticationManagerResolver!!) }
|
||||
jwt?.also { oauth2ResourceServer.jwt(jwt) }
|
||||
opaqueToken?.also { oauth2ResourceServer.opaqueToken(opaqueToken) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.web.server.savedrequest.ServerRequestCache
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure the request cache using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property requestCache allows explicit configuration of the [ServerRequestCache] to be used.
|
||||
*/
|
||||
class ServerRequestCacheDsl {
|
||||
var requestCache: ServerRequestCache? = null
|
||||
|
||||
private var disabled = false
|
||||
|
||||
/**
|
||||
* Disables the request cache.
|
||||
*/
|
||||
fun disable() {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.RequestCacheSpec) -> Unit {
|
||||
return { requestCacheConfig ->
|
||||
requestCache?.also {
|
||||
requestCacheConfig.requestCache(requestCache)
|
||||
if (disabled) {
|
||||
requestCacheConfig.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] X509 based pre authentication using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property principalExtractor the [X509PrincipalExtractor] used to obtain the principal for use within the framework.
|
||||
* @property authenticationManager the [ReactiveAuthenticationManager] used to determine if the provided
|
||||
* [Authentication] can be authenticated.
|
||||
*/
|
||||
class ServerX509Dsl {
|
||||
var principalExtractor: X509PrincipalExtractor? = null
|
||||
var authenticationManager: ReactiveAuthenticationManager? = null
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.X509Spec) -> Unit {
|
||||
return { x509 ->
|
||||
authenticationManager?.also { x509.authenticationManager(authenticationManager) }
|
||||
principalExtractor?.also { x509.principalExtractor(principalExtractor) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure the [ServerHttpSecurity] cache control headers using
|
||||
* idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
*/
|
||||
class ServerCacheControlDsl {
|
||||
private var disabled = false
|
||||
|
||||
/**
|
||||
* Disables cache control response headers
|
||||
*/
|
||||
fun disable() {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.HeaderSpec.CacheSpec) -> Unit {
|
||||
return { cacheControl ->
|
||||
if (disabled) {
|
||||
cacheControl.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure the [ServerHttpSecurity] Content-Security-Policy header using
|
||||
* idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
*/
|
||||
class ServerContentSecurityPolicyDsl {
|
||||
var policyDirectives: String? = null
|
||||
var reportOnly: Boolean? = null
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.HeaderSpec.ContentSecurityPolicySpec) -> Unit {
|
||||
return { contentSecurityPolicy ->
|
||||
policyDirectives?.also {
|
||||
contentSecurityPolicy.policyDirectives(policyDirectives)
|
||||
}
|
||||
reportOnly?.also {
|
||||
contentSecurityPolicy.reportOnly(reportOnly!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure the [ServerHttpSecurity] the content type options header
|
||||
* using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
*/
|
||||
class ServerContentTypeOptionsDsl {
|
||||
private var disabled = false
|
||||
|
||||
/**
|
||||
* Disables content type options response header
|
||||
*/
|
||||
fun disable() {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.HeaderSpec.ContentTypeOptionsSpec) -> Unit {
|
||||
return { contentTypeOptions ->
|
||||
if (disabled) {
|
||||
contentTypeOptions.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure the [ServerHttpSecurity] X-Frame-Options header using
|
||||
* idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property mode the X-Frame-Options mode to set in the response header.
|
||||
*/
|
||||
class ServerFrameOptionsDsl {
|
||||
var mode: XFrameOptionsServerHttpHeadersWriter.Mode? = null
|
||||
|
||||
private var disabled = false
|
||||
|
||||
/**
|
||||
* Disables the X-Frame-Options response header
|
||||
*/
|
||||
fun disable() {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.HeaderSpec.FrameOptionsSpec) -> Unit {
|
||||
return { frameOptions ->
|
||||
mode?.also {
|
||||
frameOptions.mode(mode)
|
||||
}
|
||||
if (disabled) {
|
||||
frameOptions.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import java.time.Duration
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure the [ServerHttpSecurity] HTTP Strict Transport Security
|
||||
* header using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property maxAge he value for the max-age directive of the Strict-Transport-Security
|
||||
* header.
|
||||
* @property includeSubdomains if true, subdomains should be considered HSTS Hosts too.
|
||||
* @property preload if true, preload will be included in HSTS Header.
|
||||
*/
|
||||
class ServerHttpStrictTransportSecurityDsl {
|
||||
var maxAge: Duration? = null
|
||||
var includeSubdomains: Boolean? = null
|
||||
var preload: Boolean? = null
|
||||
|
||||
private var disabled = false
|
||||
|
||||
/**
|
||||
* Disables the X-Frame-Options response header
|
||||
*/
|
||||
fun disable() {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.HeaderSpec.HstsSpec) -> Unit {
|
||||
return { hsts ->
|
||||
maxAge?.also { hsts.maxAge(maxAge) }
|
||||
includeSubdomains?.also { hsts.includeSubdomains(includeSubdomains!!) }
|
||||
preload?.also { hsts.preload(preload!!) }
|
||||
if (disabled) {
|
||||
hsts.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure the [ServerHttpSecurity] referrer policy header using
|
||||
* idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property policy the policy to be used in the response header.
|
||||
*/
|
||||
class ServerReferrerPolicyDsl {
|
||||
var policy: ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy? = null
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.HeaderSpec.ReferrerPolicySpec) -> Unit {
|
||||
return { referrerPolicy ->
|
||||
policy?.also {
|
||||
referrerPolicy.policy(policy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure the [ServerHttpSecurity] XSS protection header using
|
||||
* idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
*/
|
||||
class ServerXssProtectionDsl {
|
||||
private var disabled = false
|
||||
|
||||
/**
|
||||
* Disables cache control response headers
|
||||
*/
|
||||
fun disable() {
|
||||
disabled = true
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.HeaderSpec.XssProtectionSpec) -> Unit {
|
||||
return { xss ->
|
||||
if (disabled) {
|
||||
xss.disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.oauth2.resourceserver
|
||||
|
||||
import org.springframework.core.convert.converter.Converter
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.oauth2.jwt.Jwt
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder
|
||||
import reactor.core.publisher.Mono
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] JWT Resource Server support using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property authenticationManager the [ReactiveAuthenticationManager] used to determine if the provided
|
||||
* [Authentication] can be authenticated.
|
||||
* @property jwtAuthenticationConverter the [Converter] to use for converting a [Jwt] into an
|
||||
* [AbstractAuthenticationToken].
|
||||
* @property jwtDecoder the [ReactiveJwtDecoder] to use.
|
||||
* @property publicKey configures a [ReactiveJwtDecoder] that leverages the provided [RSAPublicKey]
|
||||
* @property jwkSetUri configures a [ReactiveJwtDecoder] using a
|
||||
* <a target="_blank" href="https://tools.ietf.org/html/rfc7517">JSON Web Key (JWK)</a> URL
|
||||
*/
|
||||
class ServerJwtDsl {
|
||||
private var _jwtDecoder: ReactiveJwtDecoder? = null
|
||||
private var _publicKey: RSAPublicKey? = null
|
||||
private var _jwkSetUri: String? = null
|
||||
|
||||
var authenticationManager: ReactiveAuthenticationManager? = null
|
||||
var jwtAuthenticationConverter: Converter<Jwt, out Mono<out AbstractAuthenticationToken>>? = null
|
||||
|
||||
var jwtDecoder: ReactiveJwtDecoder?
|
||||
get() = _jwtDecoder
|
||||
set(value) {
|
||||
_jwtDecoder = value
|
||||
_publicKey = null
|
||||
_jwkSetUri = null
|
||||
}
|
||||
var publicKey: RSAPublicKey?
|
||||
get() = _publicKey
|
||||
set(value) {
|
||||
_publicKey = value
|
||||
_jwtDecoder = null
|
||||
_jwkSetUri = null
|
||||
}
|
||||
var jwkSetUri: String?
|
||||
get() = _jwkSetUri
|
||||
set(value) {
|
||||
_jwkSetUri = value
|
||||
_jwtDecoder = null
|
||||
_publicKey = null
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec) -> Unit {
|
||||
return { jwt ->
|
||||
authenticationManager?.also { jwt.authenticationManager(authenticationManager) }
|
||||
jwtAuthenticationConverter?.also { jwt.jwtAuthenticationConverter(jwtAuthenticationConverter) }
|
||||
jwtDecoder?.also { jwt.jwtDecoder(jwtDecoder) }
|
||||
publicKey?.also { jwt.publicKey(publicKey) }
|
||||
jwkSetUri?.also { jwt.jwkSetUri(jwkSetUri) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.oauth2.resourceserver
|
||||
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector
|
||||
|
||||
/**
|
||||
* A Kotlin DSL to configure [ServerHttpSecurity] Opaque Token Resource Server support using idiomatic Kotlin code.
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
* @since 5.4
|
||||
* @property introspectionUri the URI of the Introspection endpoint.
|
||||
* @property introspector the [ReactiveOpaqueTokenIntrospector] to use.
|
||||
*/
|
||||
class ServerOpaqueTokenDsl {
|
||||
private var _introspectionUri: String? = null
|
||||
private var _introspector: ReactiveOpaqueTokenIntrospector? = null
|
||||
private var clientCredentials: Pair<String, String>? = null
|
||||
|
||||
var introspectionUri: String?
|
||||
get() = _introspectionUri
|
||||
set(value) {
|
||||
_introspectionUri = value
|
||||
_introspector = null
|
||||
}
|
||||
var introspector: ReactiveOpaqueTokenIntrospector?
|
||||
get() = _introspector
|
||||
set(value) {
|
||||
_introspector = value
|
||||
_introspectionUri = null
|
||||
clientCredentials = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the credentials for Introspection endpoint.
|
||||
*
|
||||
* @param clientId the clientId part of the credentials.
|
||||
* @param clientSecret the clientSecret part of the credentials.
|
||||
*/
|
||||
fun introspectionClientCredentials(clientId: String, clientSecret: String) {
|
||||
clientCredentials = Pair(clientId, clientSecret)
|
||||
_introspector = null
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec.OpaqueTokenSpec) -> Unit {
|
||||
return { opaqueToken ->
|
||||
introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
|
||||
clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
|
||||
introspector?.also { opaqueToken.introspector(introspector) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Tests for [AuthorizeExchangeDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class AuthorizeExchangeDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when secured by matcher then responds with unauthorized`() {
|
||||
this.spring.register(MatcherAuthenticatedConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class MatcherAuthenticatedConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when allowed by matcher then responds with ok`() {
|
||||
this.spring.register(MatcherPermitAllConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class MatcherPermitAllConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, permitAll)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
internal class PathController {
|
||||
@RequestMapping("/")
|
||||
fun path() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when secured by pattern then responds with unauthorized`() {
|
||||
this.spring.register(PatternAuthenticatedConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when allowed by pattern then responds with ok`() {
|
||||
this.spring.register(PatternAuthenticatedConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/public")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class PatternAuthenticatedConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize("/public", permitAll)
|
||||
authorize("/**", authenticated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
internal class PathController {
|
||||
@RequestMapping("/public")
|
||||
fun public() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when missing required role then responds with forbidden`() {
|
||||
this.spring.register(HasRoleConfig::class.java).autowire()
|
||||
this.client
|
||||
.get()
|
||||
.uri("/")
|
||||
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:password".toByteArray()))
|
||||
.exchange()
|
||||
.expectStatus().isForbidden
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class HasRoleConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, hasRole("ADMIN"))
|
||||
}
|
||||
httpBasic { }
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun userDetailsService(): MapReactiveUserDetailsService {
|
||||
val user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
return MapReactiveUserDetailsService(user)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.test.web.reactive.server.expectBody
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
/**
|
||||
* Tests for [ServerAnonymousDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerAnonymousDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `authentication when anonymous enabled then is of type anonymous authentication`() {
|
||||
this.spring.register(AnonymousConfig::class.java, HttpMeController::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/principal")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
.expectBody<String>().isEqualTo("anonymousUser")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class AnonymousConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
anonymous { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `anonymous when custom principal specified then custom principal is used`() {
|
||||
this.spring.register(CustomPrincipalConfig::class.java, HttpMeController::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/principal")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
.expectBody<String>().isEqualTo("anon")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomPrincipalConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
anonymous {
|
||||
principal = "anon"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `anonymous when disabled then principal is null`() {
|
||||
this.spring.register(AnonymousDisabledConfig::class.java, HttpMeController::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/principal")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
.expectBody<String>().consumeWith { body -> assertThat(body.responseBody).isNull() }
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class AnonymousDisabledConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
anonymous {
|
||||
disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `anonymous when custom key specified then custom key used`() {
|
||||
this.spring.register(CustomKeyConfig::class.java, HttpMeController::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/key")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
.expectBody<String>().isEqualTo("key".hashCode().toString())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomKeyConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
anonymous {
|
||||
key = "key"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `anonymous when custom authorities specified then custom authorities used`() {
|
||||
this.spring.register(CustomAuthoritiesConfig::class.java, HttpMeController::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/principal")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomAuthoritiesConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
anonymous {
|
||||
authorities = listOf(SimpleGrantedAuthority("TEST"))
|
||||
}
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, hasAuthority("TEST"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
class HttpMeController {
|
||||
@GetMapping("/principal")
|
||||
fun principal(@AuthenticationPrincipal principal: String?): String? {
|
||||
return principal
|
||||
}
|
||||
|
||||
@GetMapping("/key")
|
||||
fun key(@AuthenticationPrincipal principal: Mono<AnonymousAuthenticationToken>): Mono<String> {
|
||||
return principal
|
||||
.map { it.keyHash }
|
||||
.map { it.toString() }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.cors.CorsConfiguration
|
||||
import org.springframework.web.cors.reactive.CorsConfigurationSource
|
||||
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Tests for [ServerCorsDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerCorsDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when CORS configured using bean then Access-Control-Allow-Origin header in response`() {
|
||||
this.spring.register(CorsBeanConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.header(HttpHeaders.ORIGIN, "https://origin.example.com")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals("Access-Control-Allow-Origin", "*")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CorsBeanConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
cors { }
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun corsConfigurationSource(): CorsConfigurationSource {
|
||||
val source = UrlBasedCorsConfigurationSource()
|
||||
val corsConfiguration = CorsConfiguration()
|
||||
corsConfiguration.allowedOrigins = listOf("*")
|
||||
source.registerCorsConfiguration("/**", corsConfiguration)
|
||||
return source
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when CORS configured using source then Access-Control-Allow-Origin header in response`() {
|
||||
this.spring.register(CorsSourceConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.header(HttpHeaders.ORIGIN, "https://origin.example.com")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals("Access-Control-Allow-Origin", "*")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CorsSourceConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
val source = UrlBasedCorsConfigurationSource()
|
||||
val corsConfiguration = CorsConfiguration()
|
||||
corsConfiguration.allowedOrigins = listOf("*")
|
||||
source.registerCorsConfiguration("/**", corsConfiguration)
|
||||
return http {
|
||||
cors {
|
||||
configurationSource = source
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when CORS disabled then no Access-Control-Allow-Origin header in response`() {
|
||||
this.spring.register(CorsDisabledConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.header(HttpHeaders.ORIGIN, "https://origin.example.com")
|
||||
.exchange()
|
||||
.expectHeader().doesNotExist("Access-Control-Allow-Origin")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CorsDisabledConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
cors {
|
||||
disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.mock
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
|
||||
import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository
|
||||
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Tests for [ServerCsrfDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerCsrfDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `post when CSRF protection enabled then requires CSRF token`() {
|
||||
this.spring.register(CsrfConfig::class.java).autowire()
|
||||
|
||||
this.client.post()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isForbidden
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CsrfConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
csrf { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `post when CSRF protection disabled then CSRF token is not required`() {
|
||||
this.spring.register(CsrfDisabledConfig::class.java).autowire()
|
||||
|
||||
this.client.post()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CsrfDisabledConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
csrf {
|
||||
disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
internal class TestController {
|
||||
@PostMapping("/")
|
||||
fun home() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `post when request matches CSRF matcher then CSRF token required`() {
|
||||
this.spring.register(CsrfMatcherConfig::class.java).autowire()
|
||||
|
||||
this.client.post()
|
||||
.uri("/csrf")
|
||||
.exchange()
|
||||
.expectStatus().isForbidden
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `post when request does not match CSRF matcher then CSRF token is not required`() {
|
||||
this.spring.register(CsrfMatcherConfig::class.java).autowire()
|
||||
|
||||
this.client.post()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CsrfMatcherConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
csrf {
|
||||
requireCsrfProtectionMatcher = PathPatternParserServerWebExchangeMatcher("/csrf")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
internal class TestController {
|
||||
@PostMapping("/")
|
||||
fun home() {
|
||||
}
|
||||
|
||||
@PostMapping("/csrf")
|
||||
fun csrf() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `csrf when custom access denied handler then handler used`() {
|
||||
this.spring.register(CustomAccessDeniedHandlerConfig::class.java).autowire()
|
||||
|
||||
this.client.post()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
|
||||
Mockito.verify<ServerAccessDeniedHandler>(CustomAccessDeniedHandlerConfig.ACCESS_DENIED_HANDLER)
|
||||
.handle(any(), any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomAccessDeniedHandlerConfig {
|
||||
companion object {
|
||||
var ACCESS_DENIED_HANDLER: ServerAccessDeniedHandler = mock(ServerAccessDeniedHandler::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
csrf {
|
||||
accessDeniedHandler = ACCESS_DENIED_HANDLER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `csrf when custom token repository then repository used`() {
|
||||
this.spring.register(CustomCsrfTokenRepositoryConfig::class.java).autowire()
|
||||
|
||||
this.client.post()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
|
||||
Mockito.verify<ServerCsrfTokenRepository>(CustomCsrfTokenRepositoryConfig.TOKEN_REPOSITORY)
|
||||
.loadToken(any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomCsrfTokenRepositoryConfig {
|
||||
companion object {
|
||||
var TOKEN_REPOSITORY: ServerCsrfTokenRepository = mock(ServerCsrfTokenRepository::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
csrf {
|
||||
csrfTokenRepository = TOKEN_REPOSITORY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint
|
||||
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Tests for [ServerExceptionHandlingDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerExceptionHandlingDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unauthenticated request when custom entry point then directed to custom entry point`() {
|
||||
this.spring.register(EntryPointConfig::class.java).autowire()
|
||||
|
||||
val result = this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
Assertions.assertThat(result.responseHeaders.location).hasPath("/auth")
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class EntryPointConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
exceptionHandling {
|
||||
authenticationEntryPoint = RedirectServerAuthenticationEntryPoint("/auth")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unauthorized request when custom access denied handler then directed to custom access denied handler`() {
|
||||
this.spring.register(AccessDeniedHandlerConfig::class.java).autowire()
|
||||
|
||||
this.client
|
||||
.get()
|
||||
.uri("/")
|
||||
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:password".toByteArray()))
|
||||
.exchange()
|
||||
.expectStatus().isSeeOther
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class AccessDeniedHandlerConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, hasRole("ADMIN"))
|
||||
}
|
||||
httpBasic { }
|
||||
exceptionHandling {
|
||||
accessDeniedHandler = HttpStatusServerAccessDeniedHandler(HttpStatus.SEE_OTHER)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun userDetailsService(): MapReactiveUserDetailsService {
|
||||
val user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
return MapReactiveUserDetailsService(user)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.verify
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint
|
||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler
|
||||
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers
|
||||
import org.springframework.test.web.reactive.server.FluxExchangeResult
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.util.LinkedMultiValueMap
|
||||
import org.springframework.util.MultiValueMap
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import org.springframework.web.reactive.function.BodyInserters
|
||||
|
||||
/**
|
||||
* Tests for [ServerFormLoginDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerFormLoginDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when form login enabled then redirects to default login page`() {
|
||||
this.spring.register(DefaultFormLoginConfig::class.java, UserDetailsConfig::class.java).autowire()
|
||||
|
||||
val result: FluxExchangeResult<String> = this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
assertThat(result.responseHeaders.location).hasPath("/login")
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class DefaultFormLoginConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
formLogin { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when custom login page then redirects to custom login page`() {
|
||||
this.spring.register(CustomLoginPageConfig::class.java, UserDetailsConfig::class.java).autowire()
|
||||
|
||||
val result: FluxExchangeResult<String> = this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
assertThat(result.responseHeaders.location).hasPath("/log-in")
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomLoginPageConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
formLogin {
|
||||
loginPage = "/log-in"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `form login when custom authentication manager then manager used`() {
|
||||
this.spring.register(CustomAuthenticationManagerConfig::class.java).autowire()
|
||||
val data: MultiValueMap<String, String> = LinkedMultiValueMap()
|
||||
data.add("username", "user")
|
||||
data.add("password", "password")
|
||||
|
||||
this.client
|
||||
.mutateWith(csrf())
|
||||
.post()
|
||||
.uri("/login")
|
||||
.body(BodyInserters.fromFormData(data))
|
||||
.exchange()
|
||||
|
||||
verify<ReactiveAuthenticationManager>(CustomAuthenticationManagerConfig.AUTHENTICATION_MANAGER)
|
||||
.authenticate(any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomAuthenticationManagerConfig {
|
||||
companion object {
|
||||
var AUTHENTICATION_MANAGER: ReactiveAuthenticationManager = Mockito.mock(ReactiveAuthenticationManager::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
formLogin {
|
||||
authenticationManager = AUTHENTICATION_MANAGER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `form login when custom authentication entry point then entry point used`() {
|
||||
this.spring.register(CustomConfig::class.java, UserDetailsConfig::class.java).autowire()
|
||||
|
||||
val result = this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
assertThat(result.responseHeaders.location).hasPath("/entry")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `form login when custom requires authentication matcher then matching request logs in`() {
|
||||
this.spring.register(CustomConfig::class.java, UserDetailsConfig::class.java).autowire()
|
||||
val data: MultiValueMap<String, String> = LinkedMultiValueMap()
|
||||
data.add("username", "user")
|
||||
data.add("password", "password")
|
||||
|
||||
val result = this.client
|
||||
.mutateWith(csrf())
|
||||
.post()
|
||||
.uri("/log-in")
|
||||
.body(BodyInserters.fromFormData(data))
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
assertThat(result.responseHeaders.location).hasPath("/")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invalid login when custom failure handler then failure handler used`() {
|
||||
this.spring.register(CustomConfig::class.java, UserDetailsConfig::class.java).autowire()
|
||||
|
||||
val result = this.client
|
||||
.mutateWith(csrf())
|
||||
.post()
|
||||
.uri("/log-in")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
assertThat(result.responseHeaders.location).hasPath("/log-in-error")
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
formLogin {
|
||||
authenticationEntryPoint = RedirectServerAuthenticationEntryPoint("/entry")
|
||||
requiresAuthenticationMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/log-in")
|
||||
authenticationFailureHandler = RedirectServerAuthenticationFailureHandler("/log-in-error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `login when custom success handler then success handler used`() {
|
||||
this.spring.register(CustomSuccessHandlerConfig::class.java, UserDetailsConfig::class.java).autowire()
|
||||
val data: MultiValueMap<String, String> = LinkedMultiValueMap()
|
||||
data.add("username", "user")
|
||||
data.add("password", "password")
|
||||
|
||||
val result = this.client
|
||||
.mutateWith(csrf())
|
||||
.post()
|
||||
.uri("/login")
|
||||
.body(BodyInserters.fromFormData(data))
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
assertThat(result.responseHeaders.location).hasPath("/success")
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomSuccessHandlerConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
formLogin {
|
||||
authenticationSuccessHandler = RedirectServerAuthenticationSuccessHandler("/success")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `form login when custom security context repository then repository used`() {
|
||||
this.spring.register(CustomSecurityContextRepositoryConfig::class.java, UserDetailsConfig::class.java).autowire()
|
||||
val data: MultiValueMap<String, String> = LinkedMultiValueMap()
|
||||
data.add("username", "user")
|
||||
data.add("password", "password")
|
||||
|
||||
this.client
|
||||
.mutateWith(csrf())
|
||||
.post()
|
||||
.uri("/login")
|
||||
.body(BodyInserters.fromFormData(data))
|
||||
.exchange()
|
||||
|
||||
verify<ServerSecurityContextRepository>(CustomSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPOSITORY)
|
||||
.save(Mockito.any(), Mockito.any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomSecurityContextRepositoryConfig {
|
||||
companion object {
|
||||
var SECURITY_CONTEXT_REPOSITORY: ServerSecurityContextRepository = Mockito.mock(ServerSecurityContextRepository::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
formLogin {
|
||||
securityContextRepository = SECURITY_CONTEXT_REPOSITORY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
open class UserDetailsConfig {
|
||||
@Bean
|
||||
open fun userDetailsService(): MapReactiveUserDetailsService {
|
||||
val user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
return MapReactiveUserDetailsService(user)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
|
||||
import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
|
||||
import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
|
||||
import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Tests for [ServerHeadersDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerHeadersDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when default headers configured then default headers are in the response`() {
|
||||
this.spring.register(DefaultHeadersConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff")
|
||||
.expectHeader().valueEquals(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name)
|
||||
.expectHeader().valueEquals(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")
|
||||
.expectHeader().valueEquals(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")
|
||||
.expectHeader().valueEquals(HttpHeaders.EXPIRES, "0")
|
||||
.expectHeader().valueEquals(HttpHeaders.PRAGMA, "no-cache")
|
||||
.expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class DefaultHeadersConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when headers disabled then no security headers are in the response`() {
|
||||
this.spring.register(HeadersDisabledConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.exchange()
|
||||
.expectHeader().doesNotExist(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS)
|
||||
.expectHeader().doesNotExist(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS)
|
||||
.expectHeader().doesNotExist(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY)
|
||||
.expectHeader().doesNotExist(HttpHeaders.CACHE_CONTROL)
|
||||
.expectHeader().doesNotExist(HttpHeaders.EXPIRES)
|
||||
.expectHeader().doesNotExist(HttpHeaders.PRAGMA)
|
||||
.expectHeader().doesNotExist(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION)
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class HeadersDisabledConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when feature policy configured then feature policy header in response`() {
|
||||
this.spring.register(FeaturePolicyConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals("Feature-Policy", "geolocation 'self'")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class FeaturePolicyConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
featurePolicy("geolocation 'self'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.BDDMockito.given
|
||||
import org.mockito.Mockito.*
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import reactor.core.publisher.Mono
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Tests for [ServerHttpBasicDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerHttpBasicDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `http basic when no authorization header then responds with unauthorized`() {
|
||||
this.spring.register(HttpBasicConfig::class.java, UserDetailsConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `http basic when valid authorization header then responds with ok`() {
|
||||
this.spring.register(HttpBasicConfig::class.java, UserDetailsConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:password".toByteArray()))
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class HttpBasicConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
httpBasic { }
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
internal class PathController {
|
||||
@RequestMapping("/")
|
||||
fun path() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `http basic when custom authentication manager then manager used`() {
|
||||
given<Mono<Authentication>>(CustomAuthenticationManagerConfig.AUTHENTICATION_MANAGER.authenticate(any()))
|
||||
.willReturn(Mono.just<Authentication>(TestingAuthenticationToken("user", "password", "ROLE_USER")))
|
||||
|
||||
this.spring.register(CustomAuthenticationManagerConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:password".toByteArray()))
|
||||
.exchange()
|
||||
|
||||
verify<ReactiveAuthenticationManager>(CustomAuthenticationManagerConfig.AUTHENTICATION_MANAGER)
|
||||
.authenticate(any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomAuthenticationManagerConfig {
|
||||
companion object {
|
||||
var AUTHENTICATION_MANAGER: ReactiveAuthenticationManager = mock(ReactiveAuthenticationManager::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
httpBasic {
|
||||
authenticationManager = AUTHENTICATION_MANAGER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `http basic when custom security context repository then repository used`() {
|
||||
this.spring.register(CustomSecurityContextRepositoryConfig::class.java, UserDetailsConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:password".toByteArray()))
|
||||
.exchange()
|
||||
|
||||
verify<ServerSecurityContextRepository>(CustomSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPOSITORY)
|
||||
.save(any(), any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomSecurityContextRepositoryConfig {
|
||||
companion object {
|
||||
var SECURITY_CONTEXT_REPOSITORY: ServerSecurityContextRepository = mock(ServerSecurityContextRepository::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
httpBasic {
|
||||
securityContextRepository = SECURITY_CONTEXT_REPOSITORY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `http basic when custom authentication entry point then entry point used`() {
|
||||
this.spring.register(CustomAuthenticationEntryPointConfig::class.java, UserDetailsConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
|
||||
verify<ServerAuthenticationEntryPoint>(CustomAuthenticationEntryPointConfig.ENTRY_POINT)
|
||||
.commence(any(), any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomAuthenticationEntryPointConfig {
|
||||
companion object {
|
||||
var ENTRY_POINT: ServerAuthenticationEntryPoint = mock(ServerAuthenticationEntryPoint::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
httpBasic {
|
||||
authenticationEntryPoint = ENTRY_POINT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
open class UserDetailsConfig {
|
||||
@Bean
|
||||
open fun userDetailsService(): MapReactiveUserDetailsService {
|
||||
val user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
return MapReactiveUserDetailsService(user)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
|
||||
import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
|
||||
import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
|
||||
import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
|
||||
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Tests for [ServerHttpSecurityDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerHttpSecurityDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when it does not match the security matcher then the security rules do not apply`() {
|
||||
this.spring.register(PatternMatcherConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when it matches the security matcher then the security rules apply`() {
|
||||
this.spring.register(PatternMatcherConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/api")
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class PatternMatcherConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
securityMatcher(PathPatternParserServerWebExchangeMatcher("/api/**"))
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `post when default security configured then CSRF prevents the request`() {
|
||||
this.spring.register(DefaultSecurityConfig::class.java).autowire()
|
||||
|
||||
this.client.post()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isForbidden
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when default security configured then default headers are in the response`() {
|
||||
this.spring.register(DefaultSecurityConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff")
|
||||
.expectHeader().valueEquals(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name)
|
||||
.expectHeader().valueEquals(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")
|
||||
.expectHeader().valueEquals(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")
|
||||
.expectHeader().valueEquals(HttpHeaders.EXPIRES, "0")
|
||||
.expectHeader().valueEquals(HttpHeaders.PRAGMA, "no-cache")
|
||||
.expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class DefaultSecurityConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.web.PortMapperImpl
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Tests for [ServerHttpsRedirectDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerHttpsRedirectDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when matches redirect to HTTPS matcher then redirects to HTTPS`() {
|
||||
this.spring.register(HttpRedirectMatcherConfig::class.java).autowire()
|
||||
|
||||
val result = this.client.get()
|
||||
.uri("/secure")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
assertThat(result.responseHeaders.location).hasScheme("https")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when does not match redirect to HTTPS matcher then does not redirect`() {
|
||||
this.spring.register(HttpRedirectMatcherConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class HttpRedirectMatcherConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
redirectToHttps {
|
||||
httpsRedirectWhen(PathPatternParserServerWebExchangeMatcher("/secure"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when matches redirect to HTTPS function then redirects to HTTPS`() {
|
||||
this.spring.register(HttpRedirectFunctionConfig::class.java).autowire()
|
||||
|
||||
val result = this.client.get()
|
||||
.uri("/")
|
||||
.header("X-Requires-Https", "required")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
assertThat(result.responseHeaders.location).hasScheme("https")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when does not match redirect to HTTPS function then does not redirect`() {
|
||||
this.spring.register(HttpRedirectFunctionConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class HttpRedirectFunctionConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
redirectToHttps {
|
||||
httpsRedirectWhen {
|
||||
it.request.headers.containsKey("X-Requires-Https")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when multiple rules configured then only the last rule applies`() {
|
||||
this.spring.register(HttpRedirectMatcherAndFunctionConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/secure")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound
|
||||
|
||||
val result = this.client.get()
|
||||
.uri("/")
|
||||
.header("X-Requires-Https", "required")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
assertThat(result.responseHeaders.location).hasScheme("https")
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class HttpRedirectMatcherAndFunctionConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
redirectToHttps {
|
||||
httpsRedirectWhen(PathPatternParserServerWebExchangeMatcher("/secure"))
|
||||
httpsRedirectWhen {
|
||||
it.request.headers.containsKey("X-Requires-Https")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when port mapper configured then redirected to HTTPS port`() {
|
||||
this.spring.register(PortMapperConfig::class.java).autowire()
|
||||
|
||||
val result = this.client.get()
|
||||
.uri("http://localhost:543")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
assertThat(result.responseHeaders.location).hasScheme("https").hasPort(123)
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class PortMapperConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
val customPortMapper = PortMapperImpl()
|
||||
customPortMapper.setPortMappings(Collections.singletonMap("543", "123"))
|
||||
return http {
|
||||
redirectToHttps {
|
||||
portMapper = customPortMapper
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mockito.*
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler
|
||||
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler
|
||||
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
/**
|
||||
* Tests for [ServerLogoutDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerLogoutDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `logout when defaults used then redirects to login page`() {
|
||||
this.spring.register(LogoutConfig::class.java).autowire()
|
||||
|
||||
val result = this.client
|
||||
.mutateWith(csrf())
|
||||
.post()
|
||||
.uri("/logout")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
assertThat(result.responseHeaders.location)
|
||||
.hasPath("/login")
|
||||
.hasParameter("logout")
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class LogoutConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
logout { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `logout when custom logout URL then custom URL redirects to login page`() {
|
||||
this.spring.register(CustomUrlConfig::class.java).autowire()
|
||||
|
||||
val result = this.client
|
||||
.mutateWith(csrf())
|
||||
.post()
|
||||
.uri("/custom-logout")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
assertThat(result.responseHeaders.location)
|
||||
.hasPath("/login")
|
||||
.hasParameter("logout")
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomUrlConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
logout {
|
||||
logoutUrl = "/custom-logout"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `logout when custom requires logout matcher then matching request redirects to login page`() {
|
||||
this.spring.register(RequiresLogoutConfig::class.java).autowire()
|
||||
|
||||
val result = this.client
|
||||
.mutateWith(csrf())
|
||||
.post()
|
||||
.uri("/custom-logout")
|
||||
.exchange()
|
||||
.expectStatus().is3xxRedirection
|
||||
.returnResult(String::class.java)
|
||||
|
||||
result.assertWithDiagnostics {
|
||||
assertThat(result.responseHeaders.location)
|
||||
.hasPath("/login")
|
||||
.hasParameter("logout")
|
||||
}
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class RequiresLogoutConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
logout {
|
||||
requiresLogout = PathPatternParserServerWebExchangeMatcher("/custom-logout")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `logout when custom logout handler then custom handler invoked`() {
|
||||
this.spring.register(CustomLogoutHandlerConfig::class.java).autowire()
|
||||
|
||||
`when`(CustomLogoutHandlerConfig.LOGOUT_HANDLER.logout(any(), any()))
|
||||
.thenReturn(Mono.empty())
|
||||
|
||||
this.client
|
||||
.mutateWith(csrf())
|
||||
.post()
|
||||
.uri("/logout")
|
||||
.exchange()
|
||||
|
||||
verify<ServerLogoutHandler>(CustomLogoutHandlerConfig.LOGOUT_HANDLER)
|
||||
.logout(any(), any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomLogoutHandlerConfig {
|
||||
companion object {
|
||||
var LOGOUT_HANDLER: ServerLogoutHandler = mock(ServerLogoutHandler::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
logout {
|
||||
logoutHandler = LOGOUT_HANDLER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `logout when custom logout success handler then custom handler invoked`() {
|
||||
this.spring.register(CustomLogoutSuccessHandlerConfig::class.java).autowire()
|
||||
|
||||
this.client
|
||||
.mutateWith(csrf())
|
||||
.post()
|
||||
.uri("/logout")
|
||||
.exchange()
|
||||
|
||||
verify<ServerLogoutSuccessHandler>(CustomLogoutSuccessHandlerConfig.LOGOUT_HANDLER)
|
||||
.onLogoutSuccess(any(), any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomLogoutSuccessHandlerConfig {
|
||||
companion object {
|
||||
var LOGOUT_HANDLER: ServerLogoutSuccessHandler = mock(ServerLogoutSuccessHandler::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
logout {
|
||||
logoutSuccessHandler = LOGOUT_HANDLER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `logout when disabled then logout URL not found`() {
|
||||
this.spring.register(LogoutDisabledConfig::class.java).autowire()
|
||||
|
||||
this.client
|
||||
.mutateWith(csrf())
|
||||
.post()
|
||||
.uri("/logout")
|
||||
.exchange()
|
||||
.expectStatus().isNotFound
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class LogoutDisabledConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, permitAll)
|
||||
}
|
||||
logout {
|
||||
disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mockito.*
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
|
||||
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
/**
|
||||
* Tests for [ServerOAuth2ClientDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerOAuth2ClientDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `OAuth2 client when custom client registration repository then bean is not required`() {
|
||||
this.spring.register(ClientRepoConfig::class.java).autowire()
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class ClientRepoConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2Client {
|
||||
clientRegistrationRepository = InMemoryReactiveClientRegistrationRepository(
|
||||
CommonOAuth2Provider.GOOGLE
|
||||
.getBuilder("google").clientId("clientId").clientSecret("clientSecret")
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `OAuth2 client when authorization request repository configured then custom repository used`() {
|
||||
this.spring.register(AuthorizationRequestRepositoryConfig::class.java, ClientConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri {
|
||||
it.path("/")
|
||||
.queryParam(OAuth2ParameterNames.CODE, "code")
|
||||
.queryParam(OAuth2ParameterNames.STATE, "state")
|
||||
.build()
|
||||
}
|
||||
.exchange()
|
||||
|
||||
verify(AuthorizationRequestRepositoryConfig.AUTHORIZATION_REQUEST_REPOSITORY).loadAuthorizationRequest(any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class AuthorizationRequestRepositoryConfig {
|
||||
companion object {
|
||||
var AUTHORIZATION_REQUEST_REPOSITORY = mock(ServerAuthorizationRequestRepository::class.java)
|
||||
as ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest>
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
oauth2Client {
|
||||
authorizationRequestRepository = AUTHORIZATION_REQUEST_REPOSITORY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `OAuth2 client when authentication converter configured then custom converter used`() {
|
||||
this.spring.register(AuthenticationConverterConfig::class.java, ClientConfig::class.java).autowire()
|
||||
|
||||
`when`(AuthenticationConverterConfig.AUTHORIZATION_REQUEST_REPOSITORY.loadAuthorizationRequest(any()))
|
||||
.thenReturn(Mono.just(OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/login/oauth/authorize")
|
||||
.clientId("clientId")
|
||||
.redirectUri("/authorize/oauth2/code/google")
|
||||
.build()))
|
||||
|
||||
this.client.get()
|
||||
.uri {
|
||||
it.path("/authorize/oauth2/code/google")
|
||||
.queryParam(OAuth2ParameterNames.CODE, "code")
|
||||
.queryParam(OAuth2ParameterNames.STATE, "state")
|
||||
.build()
|
||||
}
|
||||
.exchange()
|
||||
|
||||
verify(AuthenticationConverterConfig.AUTHENTICATION_CONVERTER).convert(any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class AuthenticationConverterConfig {
|
||||
companion object {
|
||||
var AUTHORIZATION_REQUEST_REPOSITORY = mock(ServerAuthorizationRequestRepository::class.java)
|
||||
as ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest>
|
||||
var AUTHENTICATION_CONVERTER: ServerAuthenticationConverter = mock(ServerAuthenticationConverter::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
oauth2Client {
|
||||
authorizationRequestRepository = AUTHORIZATION_REQUEST_REPOSITORY
|
||||
authenticationConverter = AUTHENTICATION_CONVERTER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `OAuth2 client when authentication manager configured then custom manager used`() {
|
||||
this.spring.register(AuthenticationManagerConfig::class.java, ClientConfig::class.java).autowire()
|
||||
|
||||
`when`(AuthenticationManagerConfig.AUTHORIZATION_REQUEST_REPOSITORY.loadAuthorizationRequest(any()))
|
||||
.thenReturn(Mono.just(OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://example.com/login/oauth/authorize")
|
||||
.clientId("clientId")
|
||||
.redirectUri("/authorize/oauth2/code/google")
|
||||
.build()))
|
||||
`when`(AuthenticationManagerConfig.AUTHENTICATION_CONVERTER.convert(any()))
|
||||
.thenReturn(Mono.just(TestingAuthenticationToken("a", "b", "c")))
|
||||
|
||||
this.client.get()
|
||||
.uri {
|
||||
it.path("/authorize/oauth2/code/google")
|
||||
.queryParam(OAuth2ParameterNames.CODE, "code")
|
||||
.queryParam(OAuth2ParameterNames.STATE, "state")
|
||||
.build()
|
||||
}
|
||||
.exchange()
|
||||
|
||||
verify(AuthenticationManagerConfig.AUTHENTICATION_MANAGER).authenticate(any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class AuthenticationManagerConfig {
|
||||
companion object {
|
||||
var AUTHORIZATION_REQUEST_REPOSITORY = mock(ServerAuthorizationRequestRepository::class.java)
|
||||
as ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest>
|
||||
var AUTHENTICATION_CONVERTER: ServerAuthenticationConverter = mock(ServerAuthenticationConverter::class.java)
|
||||
var AUTHENTICATION_MANAGER: ReactiveAuthenticationManager = mock(ReactiveAuthenticationManager::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
oauth2Client {
|
||||
authorizationRequestRepository = AUTHORIZATION_REQUEST_REPOSITORY
|
||||
authenticationConverter = AUTHENTICATION_CONVERTER
|
||||
authenticationManager = AUTHENTICATION_MANAGER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
open class ClientConfig {
|
||||
@Bean
|
||||
open fun clientRegistrationRepository(): ReactiveClientRegistrationRepository {
|
||||
return InMemoryReactiveClientRegistrationRepository(
|
||||
CommonOAuth2Provider.GOOGLE
|
||||
.getBuilder("google").clientId("clientId").clientSecret("clientSecret")
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.*
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository
|
||||
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository
|
||||
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Tests for [ServerOAuth2LoginDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerOAuth2LoginDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `oauth2Login when custom client registration repository then bean is not required`() {
|
||||
this.spring.register(ClientRepoConfig::class.java).autowire()
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class ClientRepoConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2Login {
|
||||
clientRegistrationRepository = InMemoryReactiveClientRegistrationRepository(
|
||||
CommonOAuth2Provider.GOOGLE
|
||||
.getBuilder("google").clientId("clientId").clientSecret("clientSecret")
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `login page when OAuth2 login configured then default login page created`() {
|
||||
this.spring.register(OAuth2LoginConfig::class.java, ClientConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/login")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class OAuth2LoginConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
oauth2Login { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `OAuth2 login when authorization request repository configured then custom repository used`() {
|
||||
this.spring.register(AuthorizationRequestRepositoryConfig::class.java, ClientConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/login/oauth2/code/google")
|
||||
.exchange()
|
||||
|
||||
verify(AuthorizationRequestRepositoryConfig.AUTHORIZATION_REQUEST_REPOSITORY).removeAuthorizationRequest(any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class AuthorizationRequestRepositoryConfig {
|
||||
companion object {
|
||||
var AUTHORIZATION_REQUEST_REPOSITORY = mock(ServerAuthorizationRequestRepository::class.java)
|
||||
as ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest>
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
oauth2Login {
|
||||
authorizationRequestRepository = AUTHORIZATION_REQUEST_REPOSITORY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `OAuth2 login when authentication matcher configured then custom matcher used`() {
|
||||
this.spring.register(AuthenticationMatcherConfig::class.java, ClientConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
|
||||
verify(AuthenticationMatcherConfig.AUTHENTICATION_MATCHER).matches(any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class AuthenticationMatcherConfig {
|
||||
companion object {
|
||||
var AUTHENTICATION_MATCHER: ServerWebExchangeMatcher = mock(ServerWebExchangeMatcher::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
oauth2Login {
|
||||
authenticationMatcher = AUTHENTICATION_MATCHER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `OAuth2 login when authentication converter configured then custom converter used`() {
|
||||
this.spring.register(AuthenticationConverterConfig::class.java, ClientConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/login/oauth2/code/google")
|
||||
.exchange()
|
||||
|
||||
verify(AuthenticationConverterConfig.AUTHENTICATION_CONVERTER).convert(any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class AuthenticationConverterConfig {
|
||||
companion object {
|
||||
var AUTHENTICATION_CONVERTER: ServerAuthenticationConverter = mock(ServerAuthenticationConverter::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
oauth2Login {
|
||||
authenticationConverter = AUTHENTICATION_CONVERTER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
open class ClientConfig {
|
||||
@Bean
|
||||
open fun clientRegistrationRepository(): ReactiveClientRegistrationRepository {
|
||||
return InMemoryReactiveClientRegistrationRepository(
|
||||
CommonOAuth2Provider.GOOGLE
|
||||
.getBuilder("google").clientId("clientId").clientSecret("clientSecret")
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint
|
||||
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyFactory
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
import java.security.spec.RSAPublicKeySpec
|
||||
|
||||
/**
|
||||
* Tests for [ServerOAuth2ResourceServerDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerOAuth2ResourceServerDslTests {
|
||||
private val validJwt = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJtb2NrLXN1YmplY3QiLCJzY29wZSI6Im1lc3NhZ2U6cmVhZCIsImV4cCI6NDY4ODY0MTQxM30.cRl1bv_dDYcAN5U4NlIVKj8uu4mLMwjABF93P4dShiq-GQ-owzaqTSlB4YarNFgV3PKQvT9wxN1jBpGribvISljakoC0E8wDV-saDi8WxN-qvImYsn1zLzYFiZXCfRIxCmonJpydeiAPRxMTPtwnYDS9Ib0T_iA80TBGd-INhyxUUfrwRW5sqKRbjUciRJhpp7fW2ZYXmi9iPt3HDjRQA4IloJZ7f4-spt5Q9wl5HcQTv1t4XrX4eqhVbE5cCoIkFQnKPOc-jhVM44_eazLU6Xk-CCXP8C_UT5pX0luRS2cJrVFfHp2IR_AWxC-shItg6LNEmNFD4Zc-JLZcr0Q86Q"
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when custom access denied handler configured then custom handler used`() {
|
||||
this.spring.register(AccessDeniedHandlerConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.headers { it.setBearerAuth(validJwt) }
|
||||
.exchange()
|
||||
.expectStatus().isSeeOther
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class AccessDeniedHandlerConfig {
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, hasAuthority("ADMIN"))
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
accessDeniedHandler = HttpStatusServerAccessDeniedHandler(HttpStatus.SEE_OTHER)
|
||||
jwt {
|
||||
publicKey = publicKey()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when custom entry point configured then custom entry point used`() {
|
||||
this.spring.register(AuthenticationEntryPointConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectStatus().isSeeOther
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class AuthenticationEntryPointConfig {
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
authenticationEntryPoint = HttpStatusServerEntryPoint(HttpStatus.SEE_OTHER)
|
||||
jwt {
|
||||
publicKey = publicKey()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when custom bearer token converter configured then custom converter used`() {
|
||||
this.spring.register(BearerTokenConverterConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.headers { it.setBearerAuth(validJwt) }
|
||||
.exchange()
|
||||
|
||||
verify(BearerTokenConverterConfig.CONVERTER).convert(any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class BearerTokenConverterConfig {
|
||||
companion object {
|
||||
val CONVERTER: ServerBearerTokenAuthenticationConverter = mock(ServerBearerTokenAuthenticationConverter::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
bearerTokenConverter = CONVERTER
|
||||
jwt {
|
||||
publicKey = publicKey()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when custom authentication manager resolver configured then custom resolver used`() {
|
||||
this.spring.register(AuthenticationManagerResolverConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.headers { it.setBearerAuth(validJwt) }
|
||||
.exchange()
|
||||
|
||||
verify(AuthenticationManagerResolverConfig.RESOLVER).resolve(any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class AuthenticationManagerResolverConfig {
|
||||
companion object {
|
||||
val RESOLVER: ReactiveAuthenticationManagerResolver<ServerWebExchange> =
|
||||
mock(ReactiveAuthenticationManagerResolver::class.java) as ReactiveAuthenticationManagerResolver<ServerWebExchange>
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
authenticationManagerResolver = RESOLVER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun publicKey(): RSAPublicKey {
|
||||
val modulus = "26323220897278656456354815752829448539647589990395639665273015355787577386000316054335559633864476469390247312823732994485311378484154955583861993455004584140858982659817218753831620205191028763754231454775026027780771426040997832758235764611119743390612035457533732596799927628476322029280486807310749948064176545712270582940917249337311592011920620009965129181413510845780806191965771671528886508636605814099711121026468495328702234901200169245493126030184941412539949521815665744267183140084667383643755535107759061065656273783542590997725982989978433493861515415520051342321336460543070448417126615154138673620797"
|
||||
val exponent = "65537"
|
||||
val spec = RSAPublicKeySpec(BigInteger(modulus), BigInteger(exponent))
|
||||
val factory = KeyFactory.getInstance("RSA")
|
||||
return factory.generatePublic(spec) as RSAPublicKey
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers.any
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.verify
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.savedrequest.ServerRequestCache
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
/**
|
||||
* Tests for [ServerRequestCacheDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerRequestCacheDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GET when request cache enabled then redirected to cached page`() {
|
||||
this.spring.register(RequestCacheConfig::class.java, UserDetailsConfig::class.java).autowire()
|
||||
`when`(RequestCacheConfig.REQUEST_CACHE.removeMatchingRequest(any())).thenReturn(Mono.empty())
|
||||
|
||||
this.client.get()
|
||||
.uri("/test")
|
||||
.exchange()
|
||||
|
||||
verify(RequestCacheConfig.REQUEST_CACHE).saveRequest(any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class RequestCacheConfig {
|
||||
companion object {
|
||||
var REQUEST_CACHE: ServerRequestCache = Mockito.mock(ServerRequestCache::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
formLogin { }
|
||||
requestCache {
|
||||
requestCache = REQUEST_CACHE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
open class UserDetailsConfig {
|
||||
@Bean
|
||||
open fun userDetailsService(): MapReactiveUserDetailsService {
|
||||
val user = User.withDefaultPasswordEncoder()
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
return MapReactiveUserDetailsService(user)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.mock
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.core.io.ClassPathResource
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector
|
||||
import org.springframework.http.server.reactive.ServerHttpRequestDecorator
|
||||
import org.springframework.http.server.reactive.SslInfo
|
||||
import org.springframework.lang.Nullable
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager
|
||||
import org.springframework.test.web.reactive.server.MockServerConfigurer
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.test.web.reactive.server.WebTestClientConfigurer
|
||||
import org.springframework.test.web.reactive.server.expectBody
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import org.springframework.web.server.ServerWebExchange
|
||||
import org.springframework.web.server.ServerWebExchangeDecorator
|
||||
import org.springframework.web.server.WebFilter
|
||||
import org.springframework.web.server.WebFilterChain
|
||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder
|
||||
import reactor.core.publisher.Mono
|
||||
import java.security.cert.Certificate
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
/**
|
||||
* Tests for [ServerX509Dsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerX509DslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `x509 when configured with defaults then user authenticated with expected username`() {
|
||||
this.spring
|
||||
.register(X509DefaultConfig::class.java, UserDetailsConfig::class.java, UsernameController::class.java)
|
||||
.autowire()
|
||||
val certificate = loadCert<X509Certificate>("rod.cer")
|
||||
|
||||
this.client
|
||||
.mutateWith(mockX509(certificate))
|
||||
.get()
|
||||
.uri("/username")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
.expectBody<String>().isEqualTo("rod")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class X509DefaultConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
x509 { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `x509 when principal extractor customized then custom principal extractor used`() {
|
||||
this.spring
|
||||
.register(PrincipalExtractorConfig::class.java, UserDetailsConfig::class.java, UsernameController::class.java)
|
||||
.autowire()
|
||||
val certificate = loadCert<X509Certificate>("rodatexampledotcom.cer")
|
||||
|
||||
this.client
|
||||
.mutateWith(mockX509(certificate))
|
||||
.get()
|
||||
.uri("/username")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
.expectBody<String>().isEqualTo("rod")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class PrincipalExtractorConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
val customPrincipalExtractor = SubjectDnX509PrincipalExtractor()
|
||||
customPrincipalExtractor.setSubjectDnRegex("CN=(.*?)@example.com(?:,|$)")
|
||||
return http {
|
||||
x509 {
|
||||
principalExtractor = customPrincipalExtractor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `x509 when authentication manager customized then custom authentication manager used`() {
|
||||
this.spring
|
||||
.register(AuthenticationManagerConfig::class.java, UsernameController::class.java)
|
||||
.autowire()
|
||||
val certificate = loadCert<X509Certificate>("rod.cer")
|
||||
|
||||
this.client
|
||||
.mutateWith(mockX509(certificate))
|
||||
.get()
|
||||
.uri("/username")
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
.expectBody<String>().isEqualTo("rod")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class AuthenticationManagerConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
x509 {
|
||||
authenticationManager = ReactivePreAuthenticatedAuthenticationManager(userDetailsService())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun userDetailsService(): MapReactiveUserDetailsService {
|
||||
val user = User.withDefaultPasswordEncoder()
|
||||
.username("rod")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
return MapReactiveUserDetailsService(user)
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
class UsernameController {
|
||||
@GetMapping("/username")
|
||||
fun principal(@AuthenticationPrincipal user: User?): String {
|
||||
return user!!.username
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
open class UserDetailsConfig {
|
||||
@Bean
|
||||
open fun userDetailsService(): MapReactiveUserDetailsService {
|
||||
val user = User.withDefaultPasswordEncoder()
|
||||
.username("rod")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
return MapReactiveUserDetailsService(user)
|
||||
}
|
||||
}
|
||||
|
||||
private fun mockX509(certificate: X509Certificate): X509Mutator {
|
||||
return X509Mutator(certificate)
|
||||
}
|
||||
|
||||
private class X509Mutator internal constructor(private var certificate: X509Certificate) : WebTestClientConfigurer, MockServerConfigurer {
|
||||
|
||||
override fun afterConfigurerAdded(builder: WebTestClient.Builder,
|
||||
@Nullable httpHandlerBuilder: WebHttpHandlerBuilder?,
|
||||
@Nullable connector: ClientHttpConnector?) {
|
||||
val filter = SetSslInfoWebFilter(certificate)
|
||||
httpHandlerBuilder!!.filters { filters: MutableList<WebFilter?> -> filters.add(0, filter) }
|
||||
}
|
||||
}
|
||||
|
||||
private class SetSslInfoWebFilter(var certificate: X509Certificate) : WebFilter {
|
||||
|
||||
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
|
||||
return chain.filter(decorate(exchange))
|
||||
}
|
||||
|
||||
private fun decorate(exchange: ServerWebExchange): ServerWebExchange {
|
||||
val decorated: ServerHttpRequestDecorator = object : ServerHttpRequestDecorator(exchange.request) {
|
||||
override fun getSslInfo(): SslInfo {
|
||||
val sslInfo = mock(SslInfo::class.java)
|
||||
`when`(sslInfo.sessionId).thenReturn("sessionId")
|
||||
`when`(sslInfo.peerCertificates).thenReturn(arrayOf(certificate))
|
||||
return sslInfo
|
||||
}
|
||||
}
|
||||
return object : ServerWebExchangeDecorator(exchange) {
|
||||
override fun getRequest(): org.springframework.http.server.reactive.ServerHttpRequest {
|
||||
return decorated
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : Certificate> loadCert(location: String): T {
|
||||
ClassPathResource(location).inputStream.use { inputStream ->
|
||||
val certFactory = CertificateFactory.getInstance("X.509")
|
||||
return certFactory.generateCertificate(inputStream) as T
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Tests for [ServerCacheControlDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerCacheControlDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when cache control configured then cache headers in response`() {
|
||||
this.spring.register(CacheControlConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")
|
||||
.expectHeader().valueEquals(HttpHeaders.EXPIRES, "0")
|
||||
.expectHeader().valueEquals(HttpHeaders.PRAGMA, "no-cache")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CacheControlConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
cache { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when cache control disabled then no cache headers in response`() {
|
||||
this.spring.register(CacheControlDisabledConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().doesNotExist(HttpHeaders.CACHE_CONTROL)
|
||||
.expectHeader().doesNotExist(HttpHeaders.EXPIRES)
|
||||
.expectHeader().doesNotExist(HttpHeaders.PRAGMA)
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CacheControlDisabledConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
cache {
|
||||
disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Tests for [ServerContentSecurityPolicyDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerContentSecurityPolicyDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when content security policy configured then content security policy header in response`() {
|
||||
this.spring.register(ContentSecurityPolicyConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, "default-src 'self'")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class ContentSecurityPolicyConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
contentSecurityPolicy { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when custom policy directives then custom policy directive in response header`() {
|
||||
this.spring.register(CustomPolicyDirectivesConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, "default-src 'self'; script-src trustedscripts.example.com")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomPolicyDirectivesConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
contentSecurityPolicy {
|
||||
policyDirectives = "default-src 'self'; script-src trustedscripts.example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when report only configured then content security policy report only header in response`() {
|
||||
this.spring.register(ReportOnlyConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY_REPORT_ONLY, "default-src 'self'")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class ReportOnlyConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
contentSecurityPolicy {
|
||||
reportOnly = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Tests for [ServerContentTypeOptionsDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerContentTypeOptionsDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when content type options configured then header in response`() {
|
||||
this.spring.register(ContentTypeOptionsConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class ContentTypeOptionsConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
contentTypeOptions { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when content type options disabled then no content type options header in response`() {
|
||||
this.spring.register(ContentTypeOptionsDisabledConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().doesNotExist(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS)
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class ContentTypeOptionsDisabledConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
contentTypeOptions {
|
||||
disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Tests for [ServerFrameOptionsDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerFrameOptionsDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when frame options configured then header in response`() {
|
||||
this.spring.register(FrameOptionsConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name)
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class FrameOptionsConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
frameOptions { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when frame options disabled then no frame options header in response`() {
|
||||
this.spring.register(FrameOptionsDisabledConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().doesNotExist(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS)
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class FrameOptionsDisabledConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
frameOptions {
|
||||
disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when frame options mode set then frame options response header has mode value`() {
|
||||
this.spring.register(CustomModeConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN.name)
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomModeConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
frameOptions {
|
||||
mode = XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import java.time.Duration
|
||||
|
||||
/**
|
||||
* Tests for [ServerReferrerPolicyDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerHttpStrictTransportSecurityDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when hsts configured then hsts header in response`() {
|
||||
this.spring.register(HstsConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class HstsConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
hsts { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when hsts disabled then no hsts header in response`() {
|
||||
this.spring.register(HstsDisabledConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.exchange()
|
||||
.expectHeader().doesNotExist(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY)
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class HstsDisabledConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
hsts {
|
||||
disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when max age set then max age in response header`() {
|
||||
this.spring.register(MaxAgeConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=1 ; includeSubDomains")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class MaxAgeConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
hsts {
|
||||
maxAge = Duration.ofSeconds(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when includeSubdomains false then includeSubdomains not in response header`() {
|
||||
this.spring.register(IncludeSubdomainsConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class IncludeSubdomainsConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
hsts {
|
||||
includeSubdomains = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when preload true then preload included in response header`() {
|
||||
this.spring.register(PreloadConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("https://example.com")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains ; preload")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class PreloadConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
hsts {
|
||||
preload = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Tests for [ServerReferrerPolicyDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerReferrerPolicyDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when referrer policy configured then referrer policy header in response`() {
|
||||
this.spring.register(ReferrerPolicyConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals("Referrer-Policy", ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER.policy)
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class ReferrerPolicyConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
referrerPolicy { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when custom policy configured then custom policy in response header`() {
|
||||
this.spring.register(CustomPolicyConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals("Referrer-Policy", ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy.SAME_ORIGIN.policy)
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomPolicyConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
referrerPolicy {
|
||||
policy = ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy.SAME_ORIGIN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.headers
|
||||
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
|
||||
/**
|
||||
* Tests for [ServerXssProtectionDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerXssProtectionDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when xss protection configured then xss header in response`() {
|
||||
this.spring.register(XssConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1 ; mode=block")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class XssConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
xssProtection { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when xss protection disabled then no xss header in response`() {
|
||||
this.spring.register(XssDisabledConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.exchange()
|
||||
.expectHeader().doesNotExist(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION)
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class XssDisabledConfig {
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
headers {
|
||||
xssProtection {
|
||||
disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.oauth2.resourceserver
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.*
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.core.convert.converter.Converter
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames
|
||||
import org.springframework.security.oauth2.jwt.Jwt
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import reactor.core.publisher.Mono
|
||||
import java.math.BigInteger
|
||||
import java.security.KeyFactory
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
import java.security.spec.RSAPublicKeySpec
|
||||
import javax.annotation.PreDestroy
|
||||
|
||||
/**
|
||||
* Tests for [ServerJwtDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerJwtDslTests {
|
||||
|
||||
private val expired = "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1MzUwMzc4OTd9.jqZDDjfc2eysX44lHXEIr9XFd2S8vjIZHCccZU-dRWMRJNsQ1QN5VNnJGklqJBXJR4qgla6cmVqPOLkUHDb0sL0nxM5XuzQaG5ZzKP81RV88shFyAiT0fD-6nl1k-Fai-Fu-VkzSpNXgeONoTxDaYhdB-yxmgrgsApgmbOTE_9AcMk-FQDXQ-pL9kynccFGV0lZx4CA7cyknKN7KBxUilfIycvXODwgKCjj_1WddLTCNGYogJJSg__7NoxzqbyWd3udbHVjqYq7GsMMrGB4_2kBD4CkghOSNcRHbT_DIXowxfAVT7PAg7Q0E5ruZsr2zPZacEUDhJ6-wbvlA0FAOUg"
|
||||
private val messageReadToken = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJtb2NrLXN1YmplY3QiLCJzY29wZSI6Im1lc3NhZ2U6cmVhZCIsImV4cCI6NDY4ODY0MTQxM30.cRl1bv_dDYcAN5U4NlIVKj8uu4mLMwjABF93P4dShiq-GQ-owzaqTSlB4YarNFgV3PKQvT9wxN1jBpGribvISljakoC0E8wDV-saDi8WxN-qvImYsn1zLzYFiZXCfRIxCmonJpydeiAPRxMTPtwnYDS9Ib0T_iA80TBGd-INhyxUUfrwRW5sqKRbjUciRJhpp7fW2ZYXmi9iPt3HDjRQA4IloJZ7f4-spt5Q9wl5HcQTv1t4XrX4eqhVbE5cCoIkFQnKPOc-jhVM44_eazLU6Xk-CCXP8C_UT5pX0luRS2cJrVFfHp2IR_AWxC-shItg6LNEmNFD4Zc-JLZcr0Q86Q"
|
||||
private val jwkSet = "{\n" +
|
||||
" \"keys\":[\n" +
|
||||
" {\n" +
|
||||
" \"kty\":\"RSA\",\n" +
|
||||
" \"e\":\"AQAB\",\n" +
|
||||
" \"use\":\"sig\",\n" +
|
||||
" \"kid\":\"one\",\n" +
|
||||
" \"n\":\"0IUjrPZDz-3z0UE4ppcKU36v7hnh8FJjhu3lbJYj0qj9eZiwEJxi9HHUfSK1DhUQG7mJBbYTK1tPYCgre5EkfKh-64VhYUa-vz17zYCmuB8fFj4XHE3MLkWIG-AUn8hNbPzYYmiBTjfGnMKxLHjsbdTiF4mtn-85w366916R6midnAuiPD4HjZaZ1PAsuY60gr8bhMEDtJ8unz81hoQrozpBZJ6r8aR1PrsWb1OqPMloK9kAIutJNvWYKacp8WYAp2WWy72PxQ7Fb0eIA1br3A5dnp-Cln6JROJcZUIRJ-QvS6QONWeS2407uQmS-i-lybsqaH0ldYC7NBEBA5inPQ\"\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}\n"
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when JWT configured with public key and valid token then responds with ok`() {
|
||||
this.spring.register(PublicKeyConfig::class.java, BaseController::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.headers { headers: HttpHeaders -> headers.setBearerAuth(messageReadToken) }
|
||||
.exchange()
|
||||
.expectStatus().isOk
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `request when JWT configured with public key and expired token then responds with unauthorized`() {
|
||||
this.spring.register(PublicKeyConfig::class.java, BaseController::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.headers { headers: HttpHeaders -> headers.setBearerAuth(expired) }
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class PublicKeyConfig {
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
jwt {
|
||||
publicKey = publicKey()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jwt when using custom JWT decoded then custom decoded used`() {
|
||||
this.spring.register(CustomDecoderConfig::class.java).autowire()
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.headers { headers: HttpHeaders -> headers.setBearerAuth("token") }
|
||||
.exchange()
|
||||
|
||||
verify(CustomDecoderConfig.JWT_DECODER).decode("token")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomDecoderConfig {
|
||||
companion object {
|
||||
var JWT_DECODER: ReactiveJwtDecoder = mock(ReactiveJwtDecoder::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
jwt {
|
||||
jwtDecoder = JWT_DECODER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `jwt when using custom JWK Set URI then custom URI used`() {
|
||||
this.spring.register(CustomJwkSetUriConfig::class.java).autowire()
|
||||
|
||||
CustomJwkSetUriConfig.MOCK_WEB_SERVER.enqueue(MockResponse().setBody(jwkSet))
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.headers { headers: HttpHeaders -> headers.setBearerAuth(messageReadToken) }
|
||||
.exchange()
|
||||
|
||||
val recordedRequest = CustomJwkSetUriConfig.MOCK_WEB_SERVER.takeRequest()
|
||||
assertThat(recordedRequest.path).isEqualTo("/.well-known/jwks.json")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomJwkSetUriConfig {
|
||||
companion object {
|
||||
var MOCK_WEB_SERVER: MockWebServer = MockWebServer()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
jwt {
|
||||
jwkSetUri = mockWebServer().url("/.well-known/jwks.json").toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun mockWebServer(): MockWebServer {
|
||||
return MOCK_WEB_SERVER
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
open fun shutdown() {
|
||||
MOCK_WEB_SERVER.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `opaque token when custom JWT authentication converter then converter used`() {
|
||||
this.spring.register(CustomJwtAuthenticationConverterConfig::class.java).autowire()
|
||||
`when`(CustomJwtAuthenticationConverterConfig.DECODER.decode(anyString())).thenReturn(
|
||||
Mono.just(Jwt.withTokenValue("token")
|
||||
.header("alg", "none")
|
||||
.claim(IdTokenClaimNames.SUB, "user")
|
||||
.build()))
|
||||
`when`(CustomJwtAuthenticationConverterConfig.CONVERTER.convert(any()))
|
||||
.thenReturn(Mono.just(TestingAuthenticationToken("test", "this", "ROLE")))
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.headers { headers: HttpHeaders -> headers.setBearerAuth("token") }
|
||||
.exchange()
|
||||
|
||||
verify(CustomJwtAuthenticationConverterConfig.CONVERTER).convert(any())
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomJwtAuthenticationConverterConfig {
|
||||
companion object {
|
||||
var CONVERTER: Converter<Jwt, out Mono<AbstractAuthenticationToken>> = mock(Converter::class.java) as Converter<Jwt, out Mono<AbstractAuthenticationToken>>
|
||||
var DECODER: ReactiveJwtDecoder = mock(ReactiveJwtDecoder::class.java)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
jwt {
|
||||
jwtAuthenticationConverter = CONVERTER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun jwtDecoder(): ReactiveJwtDecoder {
|
||||
return DECODER
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
internal class BaseController {
|
||||
@GetMapping
|
||||
fun index() {
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun publicKey(): RSAPublicKey {
|
||||
val modulus = "26323220897278656456354815752829448539647589990395639665273015355787577386000316054335559633864476469390247312823732994485311378484154955583861993455004584140858982659817218753831620205191028763754231454775026027780771426040997832758235764611119743390612035457533732596799927628476322029280486807310749948064176545712270582940917249337311592011920620009965129181413510845780806191965771671528886508636605814099711121026468495328702234901200169245493126030184941412539949521815665744267183140084667383643755535107759061065656273783542590997725982989978433493861515415520051342321336460543070448417126615154138673620797"
|
||||
val exponent = "65537"
|
||||
val spec = RSAPublicKeySpec(BigInteger(modulus), BigInteger(exponent))
|
||||
val factory = KeyFactory.getInstance("RSA")
|
||||
return factory.generatePublic(spec) as RSAPublicKey
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Copyright 2002-2020 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.config.web.server.oauth2.resourceserver
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
|
||||
import org.springframework.security.config.test.SpringTestRule
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity
|
||||
import org.springframework.security.config.web.server.invoke
|
||||
import org.springframework.security.oauth2.server.resource.introspection.NimbusReactiveOpaqueTokenIntrospector
|
||||
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain
|
||||
import org.springframework.test.web.reactive.server.WebTestClient
|
||||
import org.springframework.web.reactive.config.EnableWebFlux
|
||||
import javax.annotation.PreDestroy
|
||||
|
||||
/**
|
||||
* Tests for [ServerOpaqueTokenDsl]
|
||||
*
|
||||
* @author Eleftheria Stein
|
||||
*/
|
||||
class ServerOpaqueTokenDslTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val spring = SpringTestRule()
|
||||
|
||||
private lateinit var client: WebTestClient
|
||||
|
||||
@Autowired
|
||||
fun setup(context: ApplicationContext) {
|
||||
this.client = WebTestClient
|
||||
.bindToApplicationContext(context)
|
||||
.configureClient()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `opaque token when using defaults then uses introspector bean`() {
|
||||
this.spring.register(IntrospectorBeanConfig::class.java).autowire()
|
||||
|
||||
IntrospectorBeanConfig.MOCK_WEB_SERVER.enqueue(MockResponse())
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer token")
|
||||
.exchange()
|
||||
|
||||
val recordedRequest = IntrospectorBeanConfig.MOCK_WEB_SERVER.takeRequest()
|
||||
assertThat(recordedRequest.path).isEqualTo("/introspect")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class IntrospectorBeanConfig {
|
||||
companion object {
|
||||
var MOCK_WEB_SERVER: MockWebServer = MockWebServer()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
opaqueToken { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun mockWebServer(): MockWebServer {
|
||||
return MOCK_WEB_SERVER
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
open fun shutdown() {
|
||||
MOCK_WEB_SERVER.shutdown()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun tokenIntrospectionClient(): ReactiveOpaqueTokenIntrospector {
|
||||
return NimbusReactiveOpaqueTokenIntrospector(mockWebServer().url("/introspect").toString(), "client", "secret")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `opaque token when using custom introspector then introspector used`() {
|
||||
this.spring.register(CustomIntrospectorConfig::class.java).autowire()
|
||||
|
||||
CustomIntrospectorConfig.MOCK_WEB_SERVER.enqueue(MockResponse())
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer token")
|
||||
.exchange()
|
||||
|
||||
val recordedRequest = CustomIntrospectorConfig.MOCK_WEB_SERVER.takeRequest()
|
||||
assertThat(recordedRequest.path).isEqualTo("/introspector")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomIntrospectorConfig {
|
||||
companion object {
|
||||
var MOCK_WEB_SERVER: MockWebServer = MockWebServer()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
opaqueToken {
|
||||
introspector = NimbusReactiveOpaqueTokenIntrospector(mockWebServer().url("/introspector").toString(), "client", "secret")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun mockWebServer(): MockWebServer {
|
||||
return MOCK_WEB_SERVER
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
open fun shutdown() {
|
||||
MOCK_WEB_SERVER.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `opaque token when using custom introspection URI and credentials then custom used`() {
|
||||
this.spring.register(CustomIntrospectionUriAndCredentialsConfig::class.java).autowire()
|
||||
|
||||
CustomIntrospectionUriAndCredentialsConfig.MOCK_WEB_SERVER.enqueue(MockResponse())
|
||||
|
||||
this.client.get()
|
||||
.uri("/")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer token")
|
||||
.exchange()
|
||||
|
||||
val recordedRequest = CustomIntrospectionUriAndCredentialsConfig.MOCK_WEB_SERVER.takeRequest()
|
||||
assertThat(recordedRequest.path).isEqualTo("/introspection-uri")
|
||||
}
|
||||
|
||||
@EnableWebFluxSecurity
|
||||
@EnableWebFlux
|
||||
open class CustomIntrospectionUriAndCredentialsConfig {
|
||||
companion object {
|
||||
var MOCK_WEB_SERVER: MockWebServer = MockWebServer()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
|
||||
return http {
|
||||
authorizeExchange {
|
||||
authorize(anyExchange, authenticated)
|
||||
}
|
||||
oauth2ResourceServer {
|
||||
opaqueToken {
|
||||
introspectionUri = mockWebServer().url("/introspection-uri").toString()
|
||||
introspectionClientCredentials("client", "secret")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun mockWebServer(): MockWebServer {
|
||||
return MOCK_WEB_SERVER
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
open fun shutdown() {
|
||||
MOCK_WEB_SERVER.shutdown()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue