Idiomatic Kotlin DSL for configuring HTTP security

Issue: gh-5558
This commit is contained in:
Eleftheria Stein-Kousathana 2020-01-07 12:08:43 -05:00 committed by GitHub
parent e306482a96
commit 2df1099da5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 9773 additions and 2 deletions

View File

@ -4,15 +4,18 @@ buildscript {
classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion"
classpath 'io.spring.nohttp:nohttp-gradle:0.0.2.RELEASE'
classpath "io.freefair.gradle:aspectj-plugin:4.0.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
repositories {
maven { url 'https://repo.spring.io/plugins-snapshot' }
maven { url 'https://plugins.gradle.org/m2/' }
}
}
apply plugin: 'io.spring.nohttp'
apply plugin: 'locks'
apply plugin: 'io.spring.convention.root'
apply plugin: 'org.jetbrains.kotlin.jvm'
group = 'org.springframework.security'
description = 'Spring Security'

View File

@ -1,5 +1,6 @@
apply plugin: 'io.spring.convention.spring-module'
apply plugin: 'trang'
apply plugin: 'kotlin'
dependencies {
// NB: Don't add other compile time dependencies to the config module as this breaks tooling
@ -27,6 +28,8 @@ dependencies {
optional'org.springframework:spring-web'
optional'org.springframework:spring-webflux'
optional'org.springframework:spring-websocket'
optional 'org.jetbrains.kotlin:kotlin-reflect'
optional 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
provided 'javax.servlet:javax.servlet-api'
@ -84,4 +87,11 @@ rncToXsd {
xslFile = new File(rncDir, 'spring-security.xsl')
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs = ["-Xjsr305=strict"]
}
}
build.dependsOn rncToXsd

View File

@ -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.servlet
import org.springframework.security.web.util.matcher.AnyRequestMatcher
import org.springframework.security.web.util.matcher.RequestMatcher
abstract class AbstractRequestMatcherDsl {
/**
* Matches any request.
*/
val anyRequest: RequestMatcher = AnyRequestMatcher.INSTANCE
protected data class MatcherAuthorizationRule(val matcher: RequestMatcher,
override val rule: String) : AuthorizationRule(rule)
protected data class PatternAuthorizationRule(val pattern: String,
val patternType: PatternType,
val servletPath: String?,
override val rule: String) : AuthorizationRule(rule)
protected abstract class AuthorizationRule(open val rule: String)
protected enum class PatternType {
ANT, MVC
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.servlet
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer
import org.springframework.security.core.Authentication
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
/**
* A Kotlin DSL to configure [HttpSecurity] anonymous authentication using idiomatic
* Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @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 authenticationProvider the [AuthenticationProvider] used to validate an
* anonymous user
* @property authenticationFilter the [AnonymousAuthenticationFilter] used to populate
* an anonymous user.
*/
class AnonymousDsl {
var key: String? = null
var principal: Any? = null
var authorities: List<GrantedAuthority>? = null
var authenticationProvider: AuthenticationProvider? = null
var authenticationFilter: AnonymousAuthenticationFilter? = null
private var disabled = false
/**
* Disable anonymous authentication
*/
fun disable() {
disabled = true
}
internal fun get(): (AnonymousConfigurer<HttpSecurity>) -> Unit {
return { anonymous ->
key?.also { anonymous.key(key) }
principal?.also { anonymous.principal(principal) }
authorities?.also { anonymous.authorities(authorities) }
authenticationProvider?.also { anonymous.authenticationProvider(authenticationProvider) }
authenticationFilter?.also { anonymous.authenticationFilter(authenticationFilter) }
if (disabled) {
anonymous.disable()
}
}
}
}

View File

@ -0,0 +1,159 @@
/*
* 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.servlet
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer
import org.springframework.security.web.util.matcher.AnyRequestMatcher
import org.springframework.security.web.util.matcher.RequestMatcher
import org.springframework.util.ClassUtils
/**
* A Kotlin DSL to configure [HttpSecurity] request authorization using idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
*/
class AuthorizeRequestsDsl : AbstractRequestMatcherDsl() {
private val authorizationRules = mutableListOf<AuthorizationRule>()
private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector"
private val MVC_PRESENT = ClassUtils.isPresent(
HANDLER_MAPPING_INTROSPECTOR,
AuthorizeRequestsDsl::class.java.classLoader)
/**
* Adds a request authorization rule.
*
* @param matches the [RequestMatcher] to match incoming requests against
* @param access the SpEL expression to secure the matching request
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
*/
fun authorize(matches: RequestMatcher = AnyRequestMatcher.INSTANCE,
access: String = "authenticated") {
authorizationRules.add(MatcherAuthorizationRule(matches, access))
}
/**
* Adds a request authorization rule for an endpoint matching the provided
* pattern.
* If Spring MVC is on the classpath, it will use an MVC matcher.
* If Spring MVC is not an the classpath, it will use an ant matcher.
* The MVC will use the same rules that Spring MVC uses for matching.
* For example, often times a mapping of the path "/path" will match on
* "/path", "/path/", "/path.html", etc.
* If the current request will not be processed by Spring MVC, a reasonable default
* using the pattern as an ant pattern will be used.
*
* @param pattern the pattern to match incoming requests against.
* @param access the SpEL expression to secure the matching request
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
*/
fun authorize(pattern: String, access: String = "authenticated") {
if (MVC_PRESENT) {
authorizationRules.add(PatternAuthorizationRule(pattern, PatternType.MVC, null, access))
} else {
authorizationRules.add(PatternAuthorizationRule(pattern, PatternType.ANT, null, access))
}
}
/**
* Adds a request authorization rule for an endpoint matching the provided
* pattern.
* If Spring MVC is on the classpath, it will use an MVC matcher.
* If Spring MVC is not an the classpath, it will use an ant matcher.
* The MVC will use the same rules that Spring MVC uses for matching.
* For example, often times a mapping of the path "/path" will match on
* "/path", "/path/", "/path.html", etc.
* If the current request will not be processed by Spring MVC, a reasonable default
* using the pattern as an ant pattern will be used.
*
* @param pattern the pattern to match incoming requests against.
* @param servletPath the servlet path to match incoming requests against. This
* only applies when using an MVC pattern matcher.
* @param access the SpEL expression to secure the matching request
* (i.e. "hasAuthority('ROLE_USER') and hasAuthority('ROLE_SUPER')")
*/
fun authorize(pattern: String, servletPath: String, access: String = "authenticated") {
if (MVC_PRESENT) {
authorizationRules.add(PatternAuthorizationRule(pattern, PatternType.MVC, servletPath, access))
} else {
authorizationRules.add(PatternAuthorizationRule(pattern, PatternType.ANT, servletPath, access))
}
}
/**
* Specify that URLs require a particular authority.
*
* @param authority the authority to require (i.e. ROLE_USER, ROLE_ADMIN, etc).
* @return the SpEL expression "hasAuthority" with the given authority as a
* parameter
*/
fun hasAuthority(authority: String) = "hasAuthority('$authority')"
/**
* Specify that URLs are allowed by anyone.
*/
val permitAll = "permitAll"
/**
* Specify that URLs are allowed by anonymous users.
*/
val anonymous = "anonymous"
/**
* Specify that URLs are allowed by users that have been remembered.
*/
val rememberMe = "rememberMe"
/**
* Specify that URLs are not allowed by anyone.
*/
val denyAll = "denyAll"
/**
* Specify that URLs are allowed by any authenticated user.
*/
val authenticated = "authenticated"
/**
* Specify that URLs are allowed by users who have authenticated and were not
* "remembered".
*/
val fullyAuthenticated = "fullyAuthenticated"
internal fun get(): (ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry) -> Unit {
return { requests ->
authorizationRules.forEach { rule ->
when (rule) {
is MatcherAuthorizationRule -> requests.requestMatchers(rule.matcher).access(rule.rule)
is PatternAuthorizationRule -> {
when (rule.patternType) {
PatternType.ANT -> requests.antMatchers(rule.pattern).access(rule.rule)
PatternType.MVC -> {
val mvcMatchersAuthorizeUrl = requests.mvcMatchers(rule.pattern)
rule.servletPath?.also { mvcMatchersAuthorizeUrl.servletPath(rule.servletPath) }
mvcMatchersAuthorizeUrl.access(rule.rule)
}
}
}
}
}
}
}
}

View File

@ -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.servlet
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.CorsConfigurer
/**
* A Kotlin DSL to configure [HttpSecurity] CORS using idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
*/
class CorsDsl {
private var disabled = false
/**
* Disable CORS.
*/
fun disable() {
disabled = true
}
internal fun get(): (CorsConfigurer<HttpSecurity>) -> Unit {
return { cors ->
if (disabled) {
cors.disable()
}
}
}
}

View File

@ -0,0 +1,87 @@
/*
* 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.servlet
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy
import org.springframework.security.web.csrf.CsrfTokenRepository
import org.springframework.security.web.util.matcher.RequestMatcher
import javax.servlet.http.HttpServletRequest
/**
* A Kotlin DSL to configure [HttpSecurity] CSRF protection
* using idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property csrfTokenRepository the [CsrfTokenRepository] to use.
* @property requireCsrfProtectionMatcher specify the [RequestMatcher] to use for
* determining when CSRF should be applied.
* @property sessionAuthenticationStrategy the [SessionAuthenticationStrategy] to use.
*/
class CsrfDsl {
var csrfTokenRepository: CsrfTokenRepository? = null
var requireCsrfProtectionMatcher: RequestMatcher? = null
var sessionAuthenticationStrategy: SessionAuthenticationStrategy? = null
private var ignoringAntMatchers: Array<out String>? = null
private var ignoringRequestMatchers: Array<out RequestMatcher>? = null
private var disabled = false
/**
* Allows specifying [HttpServletRequest]s that should not use CSRF Protection
* even if they match the [requireCsrfProtectionMatcher].
*
* @param antMatchers the ANT pattern matchers that should not use CSRF
* protection
*/
fun ignoringAntMatchers(vararg antMatchers: String) {
ignoringAntMatchers = antMatchers
}
/**
* Allows specifying [HttpServletRequest]s that should not use CSRF Protection
* even if they match the [requireCsrfProtectionMatcher].
*
* @param requestMatchers the request matchers that should not use CSRF
* protection
*/
fun ignoringRequestMatchers(vararg requestMatchers: RequestMatcher) {
ignoringRequestMatchers = requestMatchers
}
/**
* Disable CSRF protection
*/
fun disable() {
disabled = true
}
internal fun get(): (CsrfConfigurer<HttpSecurity>) -> Unit {
return { csrf ->
csrfTokenRepository?.also { csrf.csrfTokenRepository(csrfTokenRepository) }
requireCsrfProtectionMatcher?.also { csrf.requireCsrfProtectionMatcher(requireCsrfProtectionMatcher) }
sessionAuthenticationStrategy?.also { csrf.sessionAuthenticationStrategy(sessionAuthenticationStrategy) }
ignoringAntMatchers?.also { csrf.ignoringAntMatchers(*ignoringAntMatchers!!) }
ignoringRequestMatchers?.also { csrf.ignoringRequestMatchers(*ignoringRequestMatchers!!) }
if (disabled) {
csrf.disable()
}
}
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.servlet
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.security.web.access.AccessDeniedHandler
import org.springframework.security.web.util.matcher.RequestMatcher
import java.util.*
/**
* A Kotlin DSL to configure [HttpSecurity] exception handling using idiomatic Kotlin
* code.
*
* @author Eleftheria Stein
* @since 5.3
* @property accessDeniedPage the URL to the access denied page
* @property accessDeniedHandler the [AccessDeniedHandler] to use
* @property authenticationEntryPoint the [AuthenticationEntryPoint] to use
*/
class ExceptionHandlingDsl {
var accessDeniedPage: String? = null
var accessDeniedHandler: AccessDeniedHandler? = null
var authenticationEntryPoint: AuthenticationEntryPoint? = null
private var defaultDeniedHandlerMappings: LinkedHashMap<RequestMatcher, AccessDeniedHandler> = linkedMapOf()
private var defaultEntryPointMappings: LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> = linkedMapOf()
private var disabled = false
/**
* Sets a default [AccessDeniedHandler] to be used which prefers being
* invoked for the provided [RequestMatcher].
*
* @param deniedHandler the [AccessDeniedHandler] to use
* @param preferredMatcher the [RequestMatcher] for this default
* [AccessDeniedHandler]
*/
fun defaultAccessDeniedHandlerFor(deniedHandler: AccessDeniedHandler, preferredMatcher: RequestMatcher) {
defaultDeniedHandlerMappings[preferredMatcher] = deniedHandler
}
/**
* Sets a default [AuthenticationEntryPoint] to be used which prefers being
* invoked for the provided [RequestMatcher].
*
* @param entryPoint the [AuthenticationEntryPoint] to use
* @param preferredMatcher the [RequestMatcher] for this default
* [AccessDeniedHandler]
*/
fun defaultAuthenticationEntryPointFor(entryPoint: AuthenticationEntryPoint, preferredMatcher: RequestMatcher) {
defaultEntryPointMappings[preferredMatcher] = entryPoint
}
/**
* Disable exception handling.
*/
fun disable() {
disabled = true
}
internal fun get(): (ExceptionHandlingConfigurer<HttpSecurity>) -> Unit {
return { exceptionHandling ->
accessDeniedPage?.also { exceptionHandling.accessDeniedPage(accessDeniedPage) }
accessDeniedHandler?.also { exceptionHandling.accessDeniedHandler(accessDeniedHandler) }
authenticationEntryPoint?.also { exceptionHandling.authenticationEntryPoint(authenticationEntryPoint) }
defaultDeniedHandlerMappings.forEach { (matcher, handler) ->
exceptionHandling.defaultAccessDeniedHandlerFor(handler, matcher)
}
defaultEntryPointMappings.forEach { (matcher, entryPoint) ->
exceptionHandling.defaultAuthenticationEntryPointFor(entryPoint, matcher)
}
if (disabled) {
exceptionHandling.disable()
}
}
}
}

View File

@ -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.servlet
import org.springframework.security.config.annotation.web.HttpSecurityBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
/**
* A Kotlin DSL to configure [HttpSecurity] form login using idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property loginPage the login page to redirect to if authentication is required (i.e.
* "/login")
* @property authenticationSuccessHandler the [AuthenticationSuccessHandler] used after
* authentication success
* @property authenticationFailureHandler the [AuthenticationFailureHandler] used after
* authentication success
* @property failureUrl the URL to send users if authentication fails
* @property loginProcessingUrl the URL to validate the credentials
* @property permitAll whether to grant access to the urls for [failureUrl] as well as
* for the [HttpSecurityBuilder], the [loginPage] and [loginProcessingUrl] for every user
*/
class FormLoginDsl {
var loginPage: String? = null
var authenticationSuccessHandler: AuthenticationSuccessHandler? = null
var authenticationFailureHandler: AuthenticationFailureHandler? = null
var failureUrl: String? = null
var loginProcessingUrl: String? = null
var permitAll: Boolean? = null
private var defaultSuccessUrlOption: Pair<String, Boolean>? = null
/**
* Grants access to the urls for [failureUrl] as well as for the [HttpSecurityBuilder], the
* [loginPage] and [loginProcessingUrl] for every user.
*/
fun permitAll() {
permitAll = true
}
/**
* Specifies where users will be redirected after authenticating successfully if
* they have not visited a secured page prior to authenticating or [alwaysUse]
* is true.
*
* @param defaultSuccessUrl the default success url
* @param alwaysUse true if the [defaultSuccessUrl] should be used after
* authentication despite if a protected page had been previously visited
*/
fun defaultSuccessUrl(defaultSuccessUrl: String, alwaysUse: Boolean) {
defaultSuccessUrlOption = Pair(defaultSuccessUrl, alwaysUse)
}
internal fun get(): (FormLoginConfigurer<HttpSecurity>) -> Unit {
return { login ->
loginPage?.also { login.loginPage(loginPage) }
failureUrl?.also { login.failureUrl(failureUrl) }
loginProcessingUrl?.also { login.loginProcessingUrl(loginProcessingUrl) }
permitAll?.also { login.permitAll(permitAll!!) }
defaultSuccessUrlOption?.also {
login.defaultSuccessUrl(defaultSuccessUrlOption!!.first, defaultSuccessUrlOption!!.second)
}
authenticationSuccessHandler?.also { login.successHandler(authenticationSuccessHandler) }
authenticationFailureHandler?.also { login.failureHandler(authenticationFailureHandler) }
}
}
}

View File

@ -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.servlet
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.config.web.servlet.headers.*
import org.springframework.security.web.header.writers.*
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
/**
* A Kotlin DSL to configure [HttpSecurity] headers using idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property defaultsDisabled whether all of the default headers should be included in the response
*/
class HeadersDsl {
private var contentTypeOptions: ((HeadersConfigurer<HttpSecurity>.ContentTypeOptionsConfig) -> Unit)? = null
private var xssProtection: ((HeadersConfigurer<HttpSecurity>.XXssConfig) -> Unit)? = null
private var cacheControl: ((HeadersConfigurer<HttpSecurity>.CacheControlConfig) -> Unit)? = null
private var hsts: ((HeadersConfigurer<HttpSecurity>.HstsConfig) -> Unit)? = null
private var frameOptions: ((HeadersConfigurer<HttpSecurity>.FrameOptionsConfig) -> Unit)? = null
private var hpkp: ((HeadersConfigurer<HttpSecurity>.HpkpConfig) -> Unit)? = null
private var contentSecurityPolicy: ((HeadersConfigurer<HttpSecurity>.ContentSecurityPolicyConfig) -> Unit)? = null
private var referrerPolicy: ((HeadersConfigurer<HttpSecurity>.ReferrerPolicyConfig) -> Unit)? = null
private var featurePolicyDirectives: String? = null
var defaultsDisabled: Boolean? = null
/**
* Configures the [XContentTypeOptionsHeaderWriter] 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: ContentTypeOptionsDsl.() -> Unit) {
this.contentTypeOptions = ContentTypeOptionsDsl().apply(contentTypeOptionsConfig).get()
}
/**
* <strong>Note this is not comprehensive XSS protection!</strong>
*
* <p>
* Allows customizing the [XXssProtectionHeaderWriter] 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: XssProtectionConfigDsl.() -> Unit) {
this.xssProtection = XssProtectionConfigDsl().apply(xssProtectionConfig).get()
}
/**
* Allows customizing the [CacheControlHeadersWriter]. 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 header
*/
fun cacheControl(cacheControlConfig: CacheControlDsl.() -> Unit) {
this.cacheControl = CacheControlDsl().apply(cacheControlConfig).get()
}
/**
* Allows customizing the [HstsHeaderWriter] 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 httpStrictTransportSecurity(hstsConfig: HttpStrictTransportSecurityDsl.() -> Unit) {
this.hsts = HttpStrictTransportSecurityDsl().apply(hstsConfig).get()
}
/**
* Allows customizing the [XFrameOptionsHeaderWriter] which add the X-Frame-Options
* header.
*
* @param frameOptionsConfig the customization to apply to the header
*/
fun frameOptions(frameOptionsConfig: FrameOptionsDsl.() -> Unit) {
this.frameOptions = FrameOptionsDsl().apply(frameOptionsConfig).get()
}
/**
* Allows customizing the [HpkpHeaderWriter] which provides support for <a
* href="https://tools.ietf.org/html/rfc7469">HTTP Public Key Pinning (HPKP)</a>.
*
* @param hpkpConfig the customization to apply to the header
*/
fun httpPublicKeyPinning(hpkpConfig: HttpPublicKeyPinningDsl.() -> Unit) {
this.hpkp = HttpPublicKeyPinningDsl().apply(hpkpConfig).get()
}
/**
* Allows configuration for <a href="https://www.w3.org/TR/CSP2/">Content Security Policy (CSP) Level 2</a>.
*
* <p>
* Calling this method automatically enables (includes) the Content-Security-Policy header in the response
* using the supplied security policy directive(s).
* </p>
*
* @param contentSecurityPolicyConfig the customization to apply to the header
*/
fun contentSecurityPolicy(contentSecurityPolicyConfig: ContentSecurityPolicyDsl.() -> Unit) {
this.contentSecurityPolicy = ContentSecurityPolicyDsl().apply(contentSecurityPolicyConfig).get()
}
/**
* Allows configuration for <a href="https://www.w3.org/TR/referrer-policy/">Referrer Policy</a>.
*
* <p>
* Configuration is provided to the [ReferrerPolicyHeaderWriter] 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: ReferrerPolicyDsl.() -> Unit) {
this.referrerPolicy = ReferrerPolicyDsl().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
}
internal fun get(): (HeadersConfigurer<HttpSecurity>) -> Unit {
return { headers ->
defaultsDisabled?.also {
if (defaultsDisabled!!) {
headers.defaultsDisabled()
}
}
contentTypeOptions?.also {
headers.contentTypeOptions(contentTypeOptions)
}
xssProtection?.also {
headers.xssProtection(xssProtection)
}
cacheControl?.also {
headers.cacheControl(cacheControl)
}
hsts?.also {
headers.httpStrictTransportSecurity(hsts)
}
frameOptions?.also {
headers.frameOptions(frameOptions)
}
hpkp?.also {
headers.httpPublicKeyPinning(hpkp)
}
contentSecurityPolicy?.also {
headers.contentSecurityPolicy(contentSecurityPolicy)
}
referrerPolicy?.also {
headers.referrerPolicy(referrerPolicy)
}
featurePolicyDirectives?.also {
headers.featurePolicy(featurePolicyDirectives)
}
}
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.servlet
import org.springframework.security.authentication.AuthenticationDetailsSource
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
import javax.servlet.http.HttpServletRequest
/**
* A Kotlin DSL to configure [HttpSecurity] basic authentication using idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property realmName the HTTP Basic realm to use. If [authenticationEntryPoint]
* has been invoked, invoking this method will result in an error.
* @property authenticationEntryPoint the [AuthenticationEntryPoint] to be populated on
* [BasicAuthenticationFilter] in the event that authentication fails.
* @property authenticationDetailsSource the custom [AuthenticationDetailsSource] to use for
* basic authentication.
*/
class HttpBasicDsl {
var realmName: String? = null
var authenticationEntryPoint: AuthenticationEntryPoint? = null
var authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, *>? = null
private var disabled = false
/**
* Disables HTTP basic authentication
*/
fun disable() {
disabled = true
}
internal fun get(): (HttpBasicConfigurer<HttpSecurity>) -> Unit {
return { httpBasic ->
realmName?.also { httpBasic.realmName(realmName) }
authenticationEntryPoint?.also { httpBasic.authenticationEntryPoint(authenticationEntryPoint) }
authenticationDetailsSource?.also { httpBasic.authenticationDetailsSource(authenticationDetailsSource) }
if (disabled) {
httpBasic.disable()
}
}
}
}

View File

@ -0,0 +1,651 @@
/*
* 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.servlet
import org.springframework.context.ApplicationContext
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
import org.springframework.security.web.util.matcher.RequestMatcher
import org.springframework.util.ClassUtils
import javax.servlet.http.HttpServletRequest
/**
* Configures [HttpSecurity] using a [HttpSecurity Kotlin DSL][HttpSecurityDsl].
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* authorizeRequests {
* request("/public", permitAll)
* request(anyRequest, authenticated)
* }
* formLogin {
* loginPage = "/log-in"
* }
* }
* }
* }
* ```
*
* @author Eleftheria Stein
* @since 5.3
* @param httpConfiguration the configurations to apply to [HttpSecurity]
*/
operator fun HttpSecurity.invoke(httpConfiguration: HttpSecurityDsl.() -> Unit) =
HttpSecurityDsl(this, httpConfiguration).build()
/**
* An [HttpSecurity] Kotlin DSL created by [`http { }`][invoke]
* in order to configure [HttpSecurity] using idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @param http the [HttpSecurity] which all configurations will be applied to
* @param init the configurations to apply to the provided [HttpSecurity]
*/
class HttpSecurityDsl(private val http: HttpSecurity, private val init: HttpSecurityDsl.() -> Unit) {
private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector"
/**
* Allows configuring the [HttpSecurity] to only be invoked when matching the
* provided pattern.
* If Spring MVC is on the classpath, it will use an MVC matcher.
* If Spring MVC is not an the classpath, it will use an ant matcher.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* securityMatcher("/private/&ast;&ast;")
* formLogin {
* loginPage = "/log-in"
* }
* }
* }
* }
* ```
*
* @param pattern one or more patterns used to determine whether this
* configuration should be invoked.
*/
fun securityMatcher(vararg pattern: String) {
val mvcPresent = ClassUtils.isPresent(
HANDLER_MAPPING_INTROSPECTOR,
AuthorizeRequestsDsl::class.java.classLoader)
this.http.requestMatchers {
if (mvcPresent) {
it.mvcMatchers(*pattern)
} else {
it.antMatchers(*pattern)
}
}
}
/**
* Allows configuring the [HttpSecurity] to only be invoked when matching the
* provided [RequestMatcher].
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* securityMatcher(AntPathRequestMatcher("/private/&ast;&ast;"))
* formLogin {
* loginPage = "/log-in"
* }
* }
* }
* }
* ```
*
* @param requestMatcher one or more [RequestMatcher] used to determine whether
* this configuration should be invoked.
*/
fun securityMatcher(vararg requestMatcher: RequestMatcher) {
this.http.requestMatchers {
it.requestMatchers(*requestMatcher)
}
}
/**
* Enables form based authentication.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* formLogin {
* loginPage = "/log-in"
* }
* }
* }
* }
* ```
*
* @param formLoginConfiguration custom configurations to be applied
* to the form based authentication
* @see [FormLoginDsl]
*/
fun formLogin(formLoginConfiguration: FormLoginDsl.() -> Unit) {
val loginCustomizer = FormLoginDsl().apply(formLoginConfiguration).get()
this.http.formLogin(loginCustomizer)
}
/**
* Allows restricting access based upon the [HttpServletRequest]
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* authorizeRequests {
* request("/public", permitAll)
* request(anyRequest, authenticated)
* }
* }
* }
* }
* ```
*
* @param authorizeRequestsConfiguration custom configuration that specifies
* access for requests
* @see [AuthorizeRequestsDsl]
*/
fun authorizeRequests(authorizeRequestsConfiguration: AuthorizeRequestsDsl.() -> Unit) {
val authorizeRequestsCustomizer = AuthorizeRequestsDsl().apply(authorizeRequestsConfiguration).get()
this.http.authorizeRequests(authorizeRequestsCustomizer)
}
/**
* Enables HTTP basic authentication.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* httpBasic {
* realmName = "Custom Realm"
* }
* }
* }
* }
* ```
*
* @param httpBasicConfiguration custom configurations to be applied to the
* HTTP basic authentication
* @see [HttpBasicDsl]
*/
fun httpBasic(httpBasicConfiguration: HttpBasicDsl.() -> Unit) {
val httpBasicCustomizer = HttpBasicDsl().apply(httpBasicConfiguration).get()
this.http.httpBasic(httpBasicCustomizer)
}
/**
* Allows configuring response headers.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* headers {
* referrerPolicy {
* policy = ReferrerPolicy.SAME_ORIGIN
* }
* }
* }
* }
* }
* ```
*
* @param headersConfiguration custom configurations to configure the
* response headers
* @see [HeadersDsl]
*/
fun headers(headersConfiguration: HeadersDsl.() -> Unit) {
val headersCustomizer = HeadersDsl().apply(headersConfiguration).get()
this.http.headers(headersCustomizer)
}
/**
* Enables CORS.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* cors {
* disable()
* }
* }
* }
* }
* ```
*
* @param corsConfiguration custom configurations to configure the
* response headers
* @see [CorsDsl]
*/
fun cors(corsConfiguration: CorsDsl.() -> Unit) {
val corsCustomizer = CorsDsl().apply(corsConfiguration).get()
this.http.cors(corsCustomizer)
}
/**
* Allows configuring session management.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* sessionManagement {
* invalidSessionUrl = "/invalid-session"
* sessionConcurrency {
* maximumSessions = 1
* }
* }
* }
* }
* }
* ```
*
* @param sessionManagementConfiguration custom configurations to configure
* session management
* @see [SessionManagementDsl]
*/
fun sessionManagement(sessionManagementConfiguration: SessionManagementDsl.() -> Unit) {
val sessionManagementCustomizer = SessionManagementDsl().apply(sessionManagementConfiguration).get()
this.http.sessionManagement(sessionManagementCustomizer)
}
/**
* Allows configuring a port mapper.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* portMapper {
* map(80, 443)
* }
* }
* }
* }
* ```
*
* @param portMapperConfiguration custom configurations to configure
* the port mapper
* @see [PortMapperDsl]
*/
fun portMapper(portMapperConfiguration: PortMapperDsl.() -> Unit) {
val portMapperCustomizer = PortMapperDsl().apply(portMapperConfiguration).get()
this.http.portMapper(portMapperCustomizer)
}
/**
* Allows configuring channel security based upon the [HttpServletRequest]
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* requiresChannel {
* secure("/public", requiresInsecure)
* secure(anyRequest, requiresSecure)
* }
* }
* }
* }
* ```
*
* @param requiresChannelConfiguration custom configuration that specifies
* channel security
* @see [RequiresChannelDsl]
*/
fun requiresChannel(requiresChannelConfiguration: RequiresChannelDsl.() -> Unit) {
val requiresChannelCustomizer = RequiresChannelDsl().apply(requiresChannelConfiguration).get()
this.http.requiresChannel(requiresChannelCustomizer)
}
/**
* Adds X509 based pre authentication to an application
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* x509 { }
* }
* }
* }
* ```
*
* @param x509Configuration custom configuration to apply to the
* X509 based pre authentication
* @see [X509Dsl]
*/
fun x509(x509Configuration: X509Dsl.() -> Unit) {
val x509Customizer = X509Dsl().apply(x509Configuration).get()
this.http.x509(x509Customizer)
}
/**
* Enables request caching. Specifically this ensures that requests that
* are saved (i.e. after authentication is required) are later replayed.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* requestCache { }
* }
* }
* }
* ```
*
* @param requestCacheConfiguration custom configuration to apply to the
* request cache
* @see [RequestCacheDsl]
*/
fun requestCache(requestCacheConfiguration: RequestCacheDsl.() -> Unit) {
val requestCacheCustomizer = RequestCacheDsl().apply(requestCacheConfiguration).get()
this.http.requestCache(requestCacheCustomizer)
}
/**
* Allows configuring exception handling.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* exceptionHandling {
* accessDeniedPage = "/access-denied"
* }
* }
* }
* }
* ```
*
* @param exceptionHandlingConfiguration custom configuration to apply to the
* exception handling
* @see [ExceptionHandlingDsl]
*/
fun exceptionHandling(exceptionHandlingConfiguration: ExceptionHandlingDsl.() -> Unit) {
val exceptionHandlingCustomizer = ExceptionHandlingDsl().apply(exceptionHandlingConfiguration).get()
this.http.exceptionHandling(exceptionHandlingCustomizer)
}
/**
* Enables CSRF protection.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* csrf { }
* }
* }
* }
* ```
*
* @param csrfConfiguration custom configuration to apply to CSRF
* @see [CsrfDsl]
*/
fun csrf(csrfConfiguration: CsrfDsl.() -> Unit) {
val csrfCustomizer = CsrfDsl().apply(csrfConfiguration).get()
this.http.csrf(csrfCustomizer)
}
/**
* Provides logout support.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* logout {
* logoutUrl = "/log-out"
* }
* }
* }
* }
* ```
*
* @param logoutConfiguration custom configuration to apply to logout
* @see [LogoutDsl]
*/
fun logout(logoutConfiguration: LogoutDsl.() -> Unit) {
val logoutCustomizer = LogoutDsl().apply(logoutConfiguration).get()
this.http.logout(logoutCustomizer)
}
/**
* Configures authentication support using a SAML 2.0 Service Provider.
* A [RelyingPartyRegistrationRepository] is required and must be registered with
* the [ApplicationContext] or configured via
* [Saml2Dsl.relyingPartyRegistrationRepository]
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* saml2Login {
* relyingPartyRegistration = getSaml2RelyingPartyRegistration()
* }
* }
* }
* }
* ```
*
* @param saml2LoginConfiguration custom configuration to configure the
* SAML2 service provider
* @see [Saml2Dsl]
*/
fun saml2Login(saml2LoginConfiguration: Saml2Dsl.() -> Unit) {
val saml2LoginCustomizer = Saml2Dsl().apply(saml2LoginConfiguration).get()
this.http.saml2Login(saml2LoginCustomizer)
}
/**
* Allows configuring how an anonymous user is represented.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* anonymous {
* authorities = listOf(SimpleGrantedAuthority("ROLE_ANON"))
* }
* }
* }
* }
* ```
*
* @param anonymousConfiguration custom configuration to configure the
* anonymous user
* @see [AnonymousDsl]
*/
fun anonymous(anonymousConfiguration: AnonymousDsl.() -> Unit) {
val anonymousCustomizer = AnonymousDsl().apply(anonymousConfiguration).get()
this.http.anonymous(anonymousCustomizer)
}
/**
* Configures authentication support using an OAuth 2.0 and/or OpenID Connect 1.0 Provider.
* A [ClientRegistrationRepository] is required and must be registered as a Bean or
* configured via [OAuth2LoginDsl.clientRegistrationRepository]
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* oauth2Login {
* clientRegistrationRepository = getClientRegistrationRepository()
* }
* }
* }
* }
* ```
*
* @param oauth2LoginConfiguration custom configuration to configure the
* OAuth 2.0 Login
* @see [OAuth2LoginDsl]
*/
fun oauth2Login(oauth2LoginConfiguration: OAuth2LoginDsl.() -> Unit) {
val oauth2LoginCustomizer = OAuth2LoginDsl().apply(oauth2LoginConfiguration).get()
this.http.oauth2Login(oauth2LoginCustomizer)
}
/**
* Configures OAuth 2.0 client support.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* oauth2Client { }
* }
* }
* }
* ```
*
* @param oauth2ClientConfiguration custom configuration to configure the
* OAuth 2.0 client support
* @see [OAuth2ClientDsl]
*/
fun oauth2Client(oauth2ClientConfiguration: OAuth2ClientDsl.() -> Unit) {
val oauth2ClientCustomizer = OAuth2ClientDsl().apply(oauth2ClientConfiguration).get()
this.http.oauth2Client(oauth2ClientCustomizer)
}
/**
* Configures OAuth 2.0 resource server support.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* http {
* oauth2ResourceServer {
* jwt { }
* }
* }
* }
* }
* ```
*
* @param oauth2ResourceServerConfiguration custom configuration to configure the
* OAuth 2.0 resource server support
* @see [OAuth2ResourceServerDsl]
*/
fun oauth2ResourceServer(oauth2ResourceServerConfiguration: OAuth2ResourceServerDsl.() -> Unit) {
val oauth2ResourceServerCustomizer = OAuth2ResourceServerDsl().apply(oauth2ResourceServerConfiguration).get()
this.http.oauth2ResourceServer(oauth2ResourceServerCustomizer)
}
/**
* Apply all configurations to the provided [HttpSecurity]
*/
internal fun build() {
init()
}
}

View File

@ -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.servlet
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer
import org.springframework.security.core.Authentication
import org.springframework.security.web.access.AccessDeniedHandler
import org.springframework.security.web.authentication.logout.LogoutHandler
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler
import org.springframework.security.web.util.matcher.RequestMatcher
import java.util.*
import javax.servlet.http.HttpSession
/**
* A Kotlin DSL to configure [HttpSecurity] logout support
* using idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property clearAuthentication whether the [SecurityContextLogoutHandler] should clear
* the [Authentication] at the time of logout.
* @property clearAuthentication whether to invalidate the [HttpSession] at the time of logout.
* @property logoutUrl the URL that triggers log out to occur.
* @property logoutRequestMatcher the [RequestMatcher] that triggers log out to occur.
* @property logoutSuccessUrl the URL to redirect to after logout has occurred.
* @property logoutSuccessHandler the [LogoutSuccessHandler] to use after logout has occurred.
* If this is specified, [logoutSuccessUrl] is ignored.
*/
class LogoutDsl {
var clearAuthentication: Boolean? = null
var invalidateHttpSession: Boolean? = null
var logoutUrl: String? = null
var logoutRequestMatcher: RequestMatcher? = null
var logoutSuccessUrl: String? = null
var logoutSuccessHandler: LogoutSuccessHandler? = null
var permitAll: Boolean? = null
private var logoutHandlers = mutableListOf<LogoutHandler>()
private var deleteCookies: Array<out String>? = null
private var defaultLogoutSuccessHandlerMappings: LinkedHashMap<RequestMatcher, LogoutSuccessHandler> = linkedMapOf()
private var disabled = false
/**
* Adds a [LogoutHandler]. The [SecurityContextLogoutHandler] is added as
* the last [LogoutHandler] by default.
*
* @param logoutHandler the [LogoutHandler] to add
*/
fun addLogoutHandler(logoutHandler: LogoutHandler) {
this.logoutHandlers.add(logoutHandler)
}
/**
* Allows specifying the names of cookies to be removed on logout success.
*
* @param cookieNamesToClear the names of cookies to be removed on logout success.
*/
fun deleteCookies(vararg cookieNamesToClear: String) {
this.deleteCookies = cookieNamesToClear
}
/**
* Sets a default [LogoutSuccessHandler] to be used which prefers being
* invoked for the provided [RequestMatcher].
*
* @param logoutHandler the [LogoutSuccessHandler] to use
* @param preferredMatcher the [RequestMatcher] for this default
* [AccessDeniedHandler]
*/
fun defaultLogoutSuccessHandlerFor(logoutHandler: LogoutSuccessHandler, preferredMatcher: RequestMatcher) {
defaultLogoutSuccessHandlerMappings[preferredMatcher] = logoutHandler
}
/**
* Disables logout
*/
fun disable() {
disabled = true
}
/**
* Grants access to the [logoutSuccessUrl] and the [logoutUrl] for every user.
*/
fun permitAll() {
permitAll = true
}
internal fun get(): (LogoutConfigurer<HttpSecurity>) -> Unit {
return { logout ->
clearAuthentication?.also { logout.clearAuthentication(clearAuthentication!!) }
invalidateHttpSession?.also { logout.invalidateHttpSession(invalidateHttpSession!!) }
logoutUrl?.also { logout.logoutUrl(logoutUrl) }
logoutRequestMatcher?.also { logout.logoutRequestMatcher(logoutRequestMatcher) }
logoutSuccessUrl?.also { logout.logoutSuccessUrl(logoutSuccessUrl) }
logoutSuccessHandler?.also { logout.logoutSuccessHandler(logoutSuccessHandler) }
deleteCookies?.also { logout.deleteCookies(*deleteCookies!!) }
permitAll?.also { logout.permitAll(permitAll!!) }
defaultLogoutSuccessHandlerMappings.forEach { (matcher, handler) ->
logout.defaultLogoutSuccessHandlerFor(handler, matcher)
}
logoutHandlers.forEach { logoutHandler ->
logout.addLogoutHandler(logoutHandler)
}
if (disabled) {
logout.disable()
}
}
}
}

View File

@ -0,0 +1,97 @@
/*
* 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.servlet
/*
* 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.
*/
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.web.servlet.oauth2.client.AuthorizationCodeGrantDsl
import org.springframework.security.config.web.servlet.oauth2.login.AuthorizationEndpointDsl
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2ClientConfigurer
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository
/**
* A Kotlin DSL to configure [HttpSecurity] OAuth 2.0 client support using idiomatic
* Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property clientRegistrationRepository the repository of client registrations.
* @property authorizedClientRepository the repository for authorized client(s).
* @property authorizedClientService the service for authorized client(s).
*/
class OAuth2ClientDsl {
var clientRegistrationRepository: ClientRegistrationRepository? = null
var authorizedClientRepository: OAuth2AuthorizedClientRepository? = null
var authorizedClientService: OAuth2AuthorizedClientService? = null
private var authorizationCodeGrant: ((OAuth2ClientConfigurer<HttpSecurity>.AuthorizationCodeGrantConfigurer) -> Unit)? = null
/**
* Configures the OAuth 2.0 Authorization Code Grant.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* httpSecurity(http) {
* oauth2Client {
* authorizationCodeGrant {
* authorizationRequestResolver = getAuthorizationRequestResolver()
* }
* }
* }
* }
* }
* ```
*
* @param authorizationCodeGrantConfig custom configurations to configure the authorization
* code grant
* @see [AuthorizationEndpointDsl]
*/
fun authorizationCodeGrant(authorizationCodeGrantConfig: AuthorizationCodeGrantDsl.() -> Unit) {
this.authorizationCodeGrant = AuthorizationCodeGrantDsl().apply(authorizationCodeGrantConfig).get()
}
internal fun get(): (OAuth2ClientConfigurer<HttpSecurity>) -> Unit {
return { oauth2Client ->
clientRegistrationRepository?.also { oauth2Client.clientRegistrationRepository(clientRegistrationRepository) }
authorizedClientRepository?.also { oauth2Client.authorizedClientRepository(authorizedClientRepository) }
authorizedClientService?.also { oauth2Client.authorizedClientService(authorizedClientService) }
authorizationCodeGrant?.also { oauth2Client.authorizationCodeGrant(authorizationCodeGrant) }
}
}
}

View File

@ -0,0 +1,225 @@
/*
* 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.servlet
import org.springframework.security.config.annotation.web.HttpSecurityBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.web.servlet.oauth2.login.AuthorizationEndpointDsl
import org.springframework.security.config.web.servlet.oauth2.login.RedirectionEndpointDsl
import org.springframework.security.config.web.servlet.oauth2.login.TokenEndpointDsl
import org.springframework.security.config.web.servlet.oauth2.login.UserInfoEndpointDsl
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
/**
* A Kotlin DSL to configure [HttpSecurity] OAuth 2.0 login using idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property clientRegistrationRepository the repository of client registrations.
* @property authorizedClientRepository the repository for authorized client(s).
* @property authorizedClientService the service for authorized client(s).
* @property loginPage the login page to redirect to if authentication is required (i.e.
* "/login")
* @property authenticationSuccessHandler the [AuthenticationSuccessHandler] used after
* authentication success
* @property authenticationFailureHandler the [AuthenticationFailureHandler] used after
* authentication success
* @property failureUrl the URL to send users if authentication fails
* @property loginProcessingUrl the URL to validate the credentials
* @property permitAll whether to grant access to the urls for [failureUrl] as well as
* for the [HttpSecurityBuilder], the [loginPage] and [loginProcessingUrl] for every user
*/
class OAuth2LoginDsl {
var clientRegistrationRepository: ClientRegistrationRepository? = null
var authorizedClientRepository: OAuth2AuthorizedClientRepository? = null
var authorizedClientService: OAuth2AuthorizedClientService? = null
var loginPage: String? = null
var authenticationSuccessHandler: AuthenticationSuccessHandler? = null
var authenticationFailureHandler: AuthenticationFailureHandler? = null
var failureUrl: String? = null
var loginProcessingUrl: String? = null
var permitAll: Boolean? = null
private var defaultSuccessUrlOption: Pair<String, Boolean>? = null
private var authorizationEndpoint: ((OAuth2LoginConfigurer<HttpSecurity>.AuthorizationEndpointConfig) -> Unit)? = null
private var tokenEndpoint: ((OAuth2LoginConfigurer<HttpSecurity>.TokenEndpointConfig) -> Unit)? = null
private var redirectionEndpoint: ((OAuth2LoginConfigurer<HttpSecurity>.RedirectionEndpointConfig) -> Unit)? = null
private var userInfoEndpoint: ((OAuth2LoginConfigurer<HttpSecurity>.UserInfoEndpointConfig) -> Unit)? = null
/**
* Grants access to the urls for [failureUrl] as well as for the [HttpSecurityBuilder], the
* [loginPage] and [loginProcessingUrl] for every user.
*/
fun permitAll() {
permitAll = true
}
/**
* Specifies where users will be redirected after authenticating successfully if
* they have not visited a secured page prior to authenticating or [alwaysUse]
* is true.
*
* @param defaultSuccessUrl the default success url
* @param alwaysUse true if the [defaultSuccessUrl] should be used after
* authentication despite if a protected page had been previously visited
*/
fun defaultSuccessUrl(defaultSuccessUrl: String, alwaysUse: Boolean) {
defaultSuccessUrlOption = Pair(defaultSuccessUrl, alwaysUse)
}
/**
* Configures the Authorization Server's Authorization Endpoint.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* httpSecurity(http) {
* oauth2Login {
* authorizationEndpoint {
* baseUri = "/auth"
* }
* }
* }
* }
* }
* ```
*
* @param authorizationEndpointConfig custom configurations to configure the authorization
* endpoint
* @see [AuthorizationEndpointDsl]
*/
fun authorizationEndpoint(authorizationEndpointConfig: AuthorizationEndpointDsl.() -> Unit) {
this.authorizationEndpoint = AuthorizationEndpointDsl().apply(authorizationEndpointConfig).get()
}
/**
* Configures the Authorization Server's Token Endpoint.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* httpSecurity(http) {
* oauth2Login {
* tokenEndpoint {
* accessTokenResponseClient = getAccessTokenResponseClient()
* }
* }
* }
* }
* }
* ```
*
* @param tokenEndpointConfig custom configurations to configure the token
* endpoint
* @see [TokenEndpointDsl]
*/
fun tokenEndpoint(tokenEndpointConfig: TokenEndpointDsl.() -> Unit) {
this.tokenEndpoint = TokenEndpointDsl().apply(tokenEndpointConfig).get()
}
/**
* Configures the Authorization Server's Redirection Endpoint.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* httpSecurity(http) {
* oauth2Login {
* redirectionEndpoint {
* baseUri = "/home"
* }
* }
* }
* }
* }
* ```
*
* @param redirectionEndpointConfig custom configurations to configure the redirection
* endpoint
* @see [RedirectionEndpointDsl]
*/
fun redirectionEndpoint(redirectionEndpointConfig: RedirectionEndpointDsl.() -> Unit) {
this.redirectionEndpoint = RedirectionEndpointDsl().apply(redirectionEndpointConfig).get()
}
/**
* Configures the Authorization Server's UserInfo Endpoint.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* httpSecurity(http) {
* oauth2Login {
* userInfoEndpoint {
* userService = getUserService()
* }
* }
* }
* }
* }
* ```
*
* @param userInfoEndpointConfig custom configurations to configure the user info
* endpoint
* @see [UserInfoEndpointDsl]
*/
fun userInfoEndpoint(userInfoEndpointConfig: UserInfoEndpointDsl.() -> Unit) {
this.userInfoEndpoint = UserInfoEndpointDsl().apply(userInfoEndpointConfig).get()
}
internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>) -> Unit {
return { oauth2Login ->
clientRegistrationRepository?.also { oauth2Login.clientRegistrationRepository(clientRegistrationRepository) }
authorizedClientRepository?.also { oauth2Login.authorizedClientRepository(authorizedClientRepository) }
authorizedClientService?.also { oauth2Login.authorizedClientService(authorizedClientService) }
loginPage?.also { oauth2Login.loginPage(loginPage) }
failureUrl?.also { oauth2Login.failureUrl(failureUrl) }
loginProcessingUrl?.also { oauth2Login.loginProcessingUrl(loginProcessingUrl) }
permitAll?.also { oauth2Login.permitAll(permitAll!!) }
defaultSuccessUrlOption?.also {
oauth2Login.defaultSuccessUrl(defaultSuccessUrlOption!!.first, defaultSuccessUrlOption!!.second)
}
authenticationSuccessHandler?.also { oauth2Login.successHandler(authenticationSuccessHandler) }
authenticationFailureHandler?.also { oauth2Login.failureHandler(authenticationFailureHandler) }
authorizationEndpoint?.also { oauth2Login.authorizationEndpoint(authorizationEndpoint) }
tokenEndpoint?.also { oauth2Login.tokenEndpoint(tokenEndpoint) }
redirectionEndpoint?.also { oauth2Login.redirectionEndpoint(redirectionEndpoint) }
userInfoEndpoint?.also { oauth2Login.userInfoEndpoint(userInfoEndpoint) }
}
}
}

View File

@ -0,0 +1,111 @@
/*
* 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.servlet
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.web.servlet.oauth2.resourceserver.JwtDsl
import org.springframework.security.config.web.servlet.oauth2.resourceserver.OpaqueTokenDsl
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.security.web.access.AccessDeniedHandler
/**
* A Kotlin DSL to configure [HttpSecurity] OAuth 2.0 resource server support using
* idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property accessDeniedHandler the [AccessDeniedHandler] to use for requests authenticating
* with <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer Token</a>s.
* @property authenticationEntryPoint the [AuthenticationEntryPoint] to use for requests authenticating
* with <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer Token</a>s.
* @property bearerTokenResolver the [BearerTokenResolver] to use for requests authenticating
* with <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer Token</a>s.
*/
class OAuth2ResourceServerDsl {
var accessDeniedHandler: AccessDeniedHandler? = null
var authenticationEntryPoint: AuthenticationEntryPoint? = null
var bearerTokenResolver: BearerTokenResolver? = null
private var jwt: ((OAuth2ResourceServerConfigurer<HttpSecurity>.JwtConfigurer) -> Unit)? = null
private var opaqueToken: ((OAuth2ResourceServerConfigurer<HttpSecurity>.OpaqueTokenConfigurer) -> Unit)? = null
/**
* Enables JWT-encoded bearer token support.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* httpSecurity(http) {
* oauth2ResourceServer {
* jwt {
* jwkSetUri = "https://example.com/oauth2/jwk"
* }
* }
* }
* }
* }
* ```
*
* @param jwtConfig custom configurations to configure JWT resource server support
* @see [JwtDsl]
*/
fun jwt(jwtConfig: JwtDsl.() -> Unit) {
this.jwt = JwtDsl().apply(jwtConfig).get()
}
/**
* Enables opaque token support.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* httpSecurity(http) {
* oauth2ResourceServer {
* opaqueToken { }
* }
* }
* }
* }
* ```
*
* @param opaqueTokenConfig custom configurations to configure opaque token resource server support
* @see [OpaqueTokenDsl]
*/
fun opaqueToken(opaqueTokenConfig: OpaqueTokenDsl.() -> Unit) {
this.opaqueToken = OpaqueTokenDsl().apply(opaqueTokenConfig).get()
}
internal fun get(): (OAuth2ResourceServerConfigurer<HttpSecurity>) -> Unit {
return { oauth2ResourceServer ->
accessDeniedHandler?.also { oauth2ResourceServer.accessDeniedHandler(accessDeniedHandler) }
authenticationEntryPoint?.also { oauth2ResourceServer.authenticationEntryPoint(authenticationEntryPoint) }
bearerTokenResolver?.also { oauth2ResourceServer.bearerTokenResolver(bearerTokenResolver) }
jwt?.also { oauth2ResourceServer.jwt(jwt) }
opaqueToken?.also { oauth2ResourceServer.opaqueToken(opaqueToken) }
}
}
}

View File

@ -0,0 +1,56 @@
/*
* 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.servlet
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.PortMapperConfigurer
import org.springframework.security.web.PortMapper
/**
* A Kotlin DSL to configure a [PortMapper] for [HttpSecurity] using idiomatic
* Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property portMapper allows specifying the [PortMapper] instance.
*/
class PortMapperDsl {
private val mappings = mutableListOf<Pair<Int, Int>>()
var portMapper: PortMapper? = null
/**
* Adds a mapping to the port mapper.
*
* @param fromPort the HTTP port number to map from
* @param toPort the HTTPS port number to map to
*/
fun map(fromPort: Int, toPort: Int) {
mappings.add(Pair(fromPort, toPort))
}
internal fun get(): (PortMapperConfigurer<HttpSecurity>) -> Unit {
return { portMapperConfig ->
portMapper?.also {
portMapperConfig.portMapper(portMapper)
}
this.mappings.forEach {
portMapperConfig.http(it.first).mapsTo(it.second)
}
}
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.servlet
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer
import org.springframework.security.web.savedrequest.RequestCache
/**
* A Kotlin DSL to enable request caching for [HttpSecurity] using idiomatic
* Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property requestCache allows explicit configuration of the [RequestCache] to be used
*/
class RequestCacheDsl {
var requestCache: RequestCache? = null
internal fun get(): (RequestCacheConfigurer<HttpSecurity>) -> Unit {
return { requestCacheConfig ->
requestCache?.also {
requestCacheConfig.requestCache(requestCache)
}
}
}
}

View File

@ -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.servlet
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.ChannelSecurityConfigurer
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl
import org.springframework.security.web.access.channel.ChannelProcessor
import org.springframework.security.web.util.matcher.AnyRequestMatcher
import org.springframework.security.web.util.matcher.RequestMatcher
import org.springframework.util.ClassUtils
/**
* A Kotlin DSL to configure [HttpSecurity] channel security using idiomatic
* Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property channelProcessors the [ChannelProcessor] instances to use in
* [ChannelDecisionManagerImpl]
*/
class RequiresChannelDsl : AbstractRequestMatcherDsl() {
private val channelSecurityRules = mutableListOf<AuthorizationRule>()
private val HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector"
private val MVC_PRESENT = ClassUtils.isPresent(
HANDLER_MAPPING_INTROSPECTOR,
RequiresChannelDsl::class.java.classLoader)
var channelProcessors: List<ChannelProcessor>? = null
/**
* Adds a channel security rule.
*
* @param matches the [RequestMatcher] to match incoming requests against
* @param attribute the configuration attribute to secure the matching request
* (i.e. "REQUIRES_SECURE_CHANNEL")
*/
fun secure(matches: RequestMatcher = AnyRequestMatcher.INSTANCE,
attribute: String = "REQUIRES_SECURE_CHANNEL") {
channelSecurityRules.add(MatcherAuthorizationRule(matches, attribute))
}
/**
* Adds a request authorization rule for an endpoint matching the provided
* pattern.
* If Spring MVC is not an the classpath, it will use an ant matcher.
* If Spring MVC is on the classpath, it will use an MVC matcher.
* The MVC will use the same rules that Spring MVC uses for matching.
* For example, often times a mapping of the path "/path" will match on
* "/path", "/path/", "/path.html", etc.
* If the current request will not be processed by Spring MVC, a reasonable default
* using the pattern as an ant pattern will be used.
*
* @param pattern the pattern to match incoming requests against.
* @param attribute the configuration attribute to secure the matching request
* (i.e. "REQUIRES_SECURE_CHANNEL")
*/
fun secure(pattern: String, attribute: String = "REQUIRES_SECURE_CHANNEL") {
if (MVC_PRESENT) {
channelSecurityRules.add(PatternAuthorizationRule(pattern, PatternType.MVC, null, attribute))
} else {
channelSecurityRules.add(PatternAuthorizationRule(pattern, PatternType.ANT, null, attribute))
}
}
/**
* Adds a request authorization rule for an endpoint matching the provided
* pattern.
* If Spring MVC is not an the classpath, it will use an ant matcher.
* If Spring MVC is on the classpath, it will use an MVC matcher.
* The MVC will use the same rules that Spring MVC uses for matching.
* For example, often times a mapping of the path "/path" will match on
* "/path", "/path/", "/path.html", etc.
* If the current request will not be processed by Spring MVC, a reasonable default
* using the pattern as an ant pattern will be used.
*
* @param pattern the pattern to match incoming requests against.
* @param servletPath the servlet path to match incoming requests against. This
* only applies when using an MVC pattern matcher.
* @param attribute the configuration attribute to secure the matching request
* (i.e. "REQUIRES_SECURE_CHANNEL")
*/
fun secure(pattern: String, servletPath: String, attribute: String = "REQUIRES_SECURE_CHANNEL") {
if (MVC_PRESENT) {
channelSecurityRules.add(PatternAuthorizationRule(pattern, PatternType.MVC, servletPath, attribute))
} else {
channelSecurityRules.add(PatternAuthorizationRule(pattern, PatternType.ANT, servletPath, attribute))
}
}
/**
* Specify channel security is active.
*/
val requiresSecure = "REQUIRES_SECURE_CHANNEL"
/**
* Specify channel security is inactive.
*/
val requiresInsecure = "REQUIRES_INSECURE_CHANNEL"
internal fun get(): (ChannelSecurityConfigurer<HttpSecurity>.ChannelRequestMatcherRegistry) -> Unit {
return { channelSecurity ->
channelProcessors?.also { channelSecurity.channelProcessors(channelProcessors) }
channelSecurityRules.forEach { rule ->
when (rule) {
is MatcherAuthorizationRule -> channelSecurity.requestMatchers(rule.matcher).requires(rule.rule)
is PatternAuthorizationRule -> {
when (rule.patternType) {
PatternType.ANT -> channelSecurity.antMatchers(rule.pattern).requires(rule.rule)
PatternType.MVC -> {
val mvcMatchersRequiresChannel = channelSecurity.mvcMatchers(rule.pattern)
rule.servletPath?.also { mvcMatchersRequiresChannel.servletPath(rule.servletPath) }
mvcMatchersRequiresChannel.requires(rule.rule)
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,91 @@
/*
* 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.servlet
import org.springframework.security.config.annotation.web.HttpSecurityBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.saml2.Saml2LoginConfigurer
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
/**
* A Kotlin DSL to configure [HttpSecurity] SAML2 login using idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property relyingPartyRegistrationRepository the [RelyingPartyRegistrationRepository] of relying parties,
* each party representing a service provider, SP and this host, and identity provider, IDP pair that
* communicate with each other.
* @property loginPage the login page to redirect to if authentication is required (i.e.
* "/login")
* @property authenticationSuccessHandler the [AuthenticationSuccessHandler] used after
* authentication success
* @property authenticationFailureHandler the [AuthenticationFailureHandler] used after
* authentication success
* @property failureUrl the URL to send users if authentication fails
* @property loginProcessingUrl the URL to validate the credentials
* @property permitAll whether to grant access to the urls for [failureUrl] as well as
* for the [HttpSecurityBuilder], the [loginPage] and [loginProcessingUrl] for every user
*/
class Saml2Dsl {
var relyingPartyRegistrationRepository: RelyingPartyRegistrationRepository? = null
var loginPage: String? = null
var authenticationSuccessHandler: AuthenticationSuccessHandler? = null
var authenticationFailureHandler: AuthenticationFailureHandler? = null
var failureUrl: String? = null
var loginProcessingUrl: String? = null
var permitAll: Boolean? = null
private var defaultSuccessUrlOption: Pair<String, Boolean>? = null
/**
* Grants access to the urls for [failureUrl] as well as for the [HttpSecurityBuilder], the
* [loginPage] and [loginProcessingUrl] for every user.
*/
fun permitAll() {
permitAll = true
}
/**
* Specifies where users will be redirected after authenticating successfully if
* they have not visited a secured page prior to authenticating or [alwaysUse]
* is true.
*
* @param defaultSuccessUrl the default success url
* @param alwaysUse true if the [defaultSuccessUrl] should be used after
* authentication despite if a protected page had been previously visited
*/
fun defaultSuccessUrl(defaultSuccessUrl: String, alwaysUse: Boolean) {
defaultSuccessUrlOption = Pair(defaultSuccessUrl, alwaysUse)
}
internal fun get(): (Saml2LoginConfigurer<HttpSecurity>) -> Unit {
return { saml2Login ->
relyingPartyRegistrationRepository?.also { saml2Login.relyingPartyRegistrationRepository(relyingPartyRegistrationRepository) }
loginPage?.also { saml2Login.loginPage(loginPage) }
failureUrl?.also { saml2Login.failureUrl(failureUrl) }
loginProcessingUrl?.also { saml2Login.loginProcessingUrl(loginProcessingUrl) }
permitAll?.also { saml2Login.permitAll(permitAll!!) }
defaultSuccessUrlOption?.also {
saml2Login.defaultSuccessUrl(defaultSuccessUrlOption!!.first, defaultSuccessUrlOption!!.second)
}
authenticationSuccessHandler?.also { saml2Login.successHandler(authenticationSuccessHandler) }
authenticationFailureHandler?.also { saml2Login.failureHandler(authenticationFailureHandler) }
}
}
}

View File

@ -0,0 +1,117 @@
/*
* 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.servlet
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.web.servlet.session.SessionConcurrencyDsl
import org.springframework.security.config.web.servlet.session.SessionFixationDsl
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.web.authentication.AuthenticationFailureHandler
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy
import org.springframework.security.web.session.InvalidSessionStrategy
/**
* A Kotlin DSL to configure [HttpSecurity] session management using idiomatic
* Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
*/
class SessionManagementDsl {
var invalidSessionUrl: String? = null
var invalidSessionStrategy: InvalidSessionStrategy? = null
var sessionAuthenticationErrorUrl: String? = null
var sessionAuthenticationFailureHandler: AuthenticationFailureHandler? = null
var enableSessionUrlRewriting: Boolean? = null
var sessionCreationPolicy: SessionCreationPolicy? = null
var sessionAuthenticationStrategy: SessionAuthenticationStrategy? = null
private var sessionFixation: ((SessionManagementConfigurer<HttpSecurity>.SessionFixationConfigurer) -> Unit)? = null
private var sessionConcurrency: ((SessionManagementConfigurer<HttpSecurity>.ConcurrencyControlConfigurer) -> Unit)? = null
/**
* Enables session fixation protection.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* httpSecurity(http) {
* sessionManagement {
* sessionFixation { }
* }
* }
* }
* }
* ```
*
* @param sessionFixationConfig custom configurations to configure session fixation
* protection
* @see [SessionFixationDsl]
*/
fun sessionFixation(sessionFixationConfig: SessionFixationDsl.() -> Unit) {
this.sessionFixation = SessionFixationDsl().apply(sessionFixationConfig).get()
}
/**
* Controls the behaviour of multiple sessions for a user.
*
* Example:
*
* ```
* @EnableWebSecurity
* class SecurityConfig : WebSecurityConfigurerAdapter() {
*
* override fun configure(http: HttpSecurity) {
* httpSecurity(http) {
* sessionManagement {
* sessionConcurrency {
* maximumSessions = 1
* maxSessionsPreventsLogin = true
* }
* }
* }
* }
* }
* ```
*
* @param sessionConcurrencyConfig custom configurations to configure concurrency
* control
* @see [SessionConcurrencyDsl]
*/
fun sessionConcurrency(sessionConcurrencyConfig: SessionConcurrencyDsl.() -> Unit) {
this.sessionConcurrency = SessionConcurrencyDsl().apply(sessionConcurrencyConfig).get()
}
internal fun get(): (SessionManagementConfigurer<HttpSecurity>) -> Unit {
return { sessionManagement ->
invalidSessionUrl?.also { sessionManagement.invalidSessionUrl(invalidSessionUrl) }
invalidSessionStrategy?.also { sessionManagement.invalidSessionStrategy(invalidSessionStrategy) }
sessionAuthenticationErrorUrl?.also { sessionManagement.sessionAuthenticationErrorUrl(sessionAuthenticationErrorUrl) }
sessionAuthenticationFailureHandler?.also { sessionManagement.sessionAuthenticationFailureHandler(sessionAuthenticationFailureHandler) }
enableSessionUrlRewriting?.also { sessionManagement.enableSessionUrlRewriting(enableSessionUrlRewriting!!) }
sessionCreationPolicy?.also { sessionManagement.sessionCreationPolicy(sessionCreationPolicy) }
sessionAuthenticationStrategy?.also { sessionManagement.sessionAuthenticationStrategy(sessionAuthenticationStrategy) }
sessionFixation?.also { sessionManagement.sessionFixation(sessionFixation) }
sessionConcurrency?.also { sessionManagement.sessionConcurrency(sessionConcurrency) }
}
}
}

View File

@ -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.servlet
import org.springframework.security.authentication.AuthenticationDetailsSource
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.X509Configurer
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor
import javax.servlet.http.HttpServletRequest
/**
* A Kotlin DSL to configure [HttpSecurity] X509 based pre authentication
* using idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property x509AuthenticationFilter the entire [X509AuthenticationFilter]. If
* this is specified, the properties on [X509Configurer] will not be populated
* on the {@link X509AuthenticationFilter}.
* @property x509PrincipalExtractor the [X509PrincipalExtractor]
* @property authenticationDetailsSource the [X509PrincipalExtractor]
* @property userDetailsService shortcut for invoking
* [authenticationUserDetailsService] with a [UserDetailsByNameServiceWrapper]
* @property authenticationUserDetailsService the [AuthenticationUserDetailsService] to use
* @property subjectPrincipalRegex the regex to extract the principal from the certificate
*/
class X509Dsl {
var x509AuthenticationFilter: X509AuthenticationFilter? = null
var x509PrincipalExtractor: X509PrincipalExtractor? = null
var authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails>? = null
var userDetailsService: UserDetailsService? = null
var authenticationUserDetailsService: AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>? = null
var subjectPrincipalRegex: String? = null
internal fun get(): (X509Configurer<HttpSecurity>) -> Unit {
return { x509 ->
x509AuthenticationFilter?.also { x509.x509AuthenticationFilter(x509AuthenticationFilter) }
x509PrincipalExtractor?.also { x509.x509PrincipalExtractor(x509PrincipalExtractor) }
authenticationDetailsSource?.also { x509.authenticationDetailsSource(authenticationDetailsSource) }
userDetailsService?.also { x509.userDetailsService(userDetailsService) }
authenticationUserDetailsService?.also { x509.authenticationUserDetailsService(authenticationUserDetailsService) }
subjectPrincipalRegex?.also { x509.subjectPrincipalRegex(subjectPrincipalRegex) }
}
}
}

View File

@ -0,0 +1,46 @@
/*
* 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.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
/**
* A Kotlin DSL to configure the [HttpSecurity] cache control headers using idiomatic
* Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
*/
class CacheControlDsl {
private var disabled = false
/**
* Disable cache control headers.
*/
fun disable() {
disabled = true
}
internal fun get(): (HeadersConfigurer<HttpSecurity>.CacheControlConfig) -> Unit {
return { cacheControl ->
if (disabled) {
cacheControl.disable()
}
}
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
/**
* A Kotlin DSL to configure the [HttpSecurity] Content-Security-Policy header using
* idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property policyDirectives the security policy directive(s) to be used in the response header.
* @property reportOnly includes the Content-Security-Policy-Report-Only header in the response.
*/
class ContentSecurityPolicyDsl {
var policyDirectives: String? = null
var reportOnly: Boolean? = null
internal fun get(): (HeadersConfigurer<HttpSecurity>.ContentSecurityPolicyConfig) -> Unit {
return { contentSecurityPolicy ->
policyDirectives?.also {
contentSecurityPolicy.policyDirectives(policyDirectives)
}
reportOnly?.also {
if (reportOnly!!) {
contentSecurityPolicy.reportOnly()
}
}
}
}
}

View File

@ -0,0 +1,46 @@
/*
* 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.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
/**
* A Kotlin DSL to configure [HttpSecurity] X-Content-Type-Options header using idiomatic
* Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
*/
class ContentTypeOptionsDsl {
private var disabled = false
/**
* Disable the X-Content-Type-Options header.
*/
fun disable() {
disabled = true
}
internal fun get(): (HeadersConfigurer<HttpSecurity>.ContentTypeOptionsConfig) -> Unit {
return { contentTypeOptions ->
if (disabled) {
contentTypeOptions.disable()
}
}
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
/**
* A Kotlin DSL to configure the [HttpSecurity] X-Frame-Options header using
* idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property sameOrigin allow any request that comes from the same origin to frame this
* application.
* @property deny deny framing any content from this application.
*/
class FrameOptionsDsl {
var sameOrigin: Boolean? = null
var deny: Boolean? = null
private var disabled = false
/**
* Disable the X-Frame-Options header.
*/
fun disable() {
disabled = true
}
internal fun get(): (HeadersConfigurer<HttpSecurity>.FrameOptionsConfig) -> Unit {
return { frameOptions ->
sameOrigin?.also {
if (sameOrigin!!) {
frameOptions.sameOrigin()
}
}
deny?.also {
if (deny!!) {
frameOptions.deny()
}
}
if (disabled) {
frameOptions.disable()
}
}
}
}

View File

@ -0,0 +1,75 @@
/*
* 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.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
/**
* A Kotlin DSL to configure the [HttpSecurity] HTTP Public Key Pinning header using
* idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property pins the value for the pin- directive of the Public-Key-Pins header.
* @property maxAgeInSeconds the value (in seconds) for the max-age directive of the
* Public-Key-Pins header.
* @property includeSubDomains if true, the pinning policy applies to this pinned host
* as well as any subdomains of the host's domain name.
* @property reportOnly if true, the browser should not terminate the connection with
* the server.
* @property reportUri the URI to which the browser should report pin validation failures.
*/
class HttpPublicKeyPinningDsl {
var pins: Map<String, String>? = null
var maxAgeInSeconds: Long? = null
var includeSubDomains: Boolean? = null
var reportOnly: Boolean? = null
var reportUri: String? = null
private var disabled = false
/**
* Disable the HTTP Public Key Pinning header.
*/
fun disable() {
disabled = true
}
internal fun get(): (HeadersConfigurer<HttpSecurity>.HpkpConfig) -> Unit {
return { hpkp ->
pins?.also {
hpkp.withPins(pins)
}
maxAgeInSeconds?.also {
hpkp.maxAgeInSeconds(maxAgeInSeconds!!)
}
includeSubDomains?.also {
hpkp.includeSubDomains(includeSubDomains!!)
}
reportOnly?.also {
hpkp.reportOnly(reportOnly!!)
}
reportUri?.also {
hpkp.reportUri(reportUri)
}
if (disabled) {
hpkp.disable()
}
}
}
}

View File

@ -0,0 +1,63 @@
/*
* 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.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.web.util.matcher.RequestMatcher
/**
* A Kotlin DSL to configure the [HttpSecurity] HTTP Strict Transport Security header using
* idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property maxAgeInSeconds the value (in seconds) for the max-age directive of the
* Strict-Transport-Security header.
* @property requestMatcher the [RequestMatcher] used to determine if the
* "Strict-Transport-Security" header should be added. If true the header is added,
* else the header is not added.
* @property includeSubDomains if true, subdomains should be considered HSTS Hosts too.
* @property preload if true, preload will be included in HSTS Header.
*/
class HttpStrictTransportSecurityDsl {
var maxAgeInSeconds: Long? = null
var requestMatcher: RequestMatcher? = null
var includeSubDomains: Boolean? = null
var preload: Boolean? = null
private var disabled = false
/**
* Disable the HTTP Strict Transport Security header.
*/
fun disable() {
disabled = true
}
internal fun get(): (HeadersConfigurer<HttpSecurity>.HstsConfig) -> Unit {
return { hsts ->
maxAgeInSeconds?.also { hsts.maxAgeInSeconds(maxAgeInSeconds!!) }
requestMatcher?.also { hsts.requestMatcher(requestMatcher) }
includeSubDomains?.also { hsts.includeSubDomains(includeSubDomains!!) }
preload?.also { hsts.preload(preload!!) }
if (disabled) {
hsts.disable()
}
}
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter
/**
* A Kotlin DSL to configure the [HttpSecurity] referrer policy header using
* idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property policy the policy to be used in the response header.
*/
class ReferrerPolicyDsl {
var policy: ReferrerPolicyHeaderWriter.ReferrerPolicy? = null
internal fun get(): (HeadersConfigurer<HttpSecurity>.ReferrerPolicyConfig) -> Unit {
return { referrerPolicy ->
policy?.also {
referrerPolicy.policy(policy)
}
}
}
}

View File

@ -0,0 +1,55 @@
/*
* 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.servlet.headers
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
/**
* A Kotlin DSL to configure the [HttpSecurity] XSS protection header using
* idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property block whether to specify the mode as blocked
* @property xssProtectionEnabled if true, the header value will contain a value of 1.
* If false, will explicitly disable specify that X-XSS-Protection is disabled.
*/
class XssProtectionConfigDsl {
var block: Boolean? = null
var xssProtectionEnabled: Boolean? = null
private var disabled = false
/**
* Do not include the X-XSS-Protection header in the response.
*/
fun disable() {
disabled = true
}
internal fun get(): (HeadersConfigurer<HttpSecurity>.XXssConfig) -> Unit {
return { xssProtection ->
block?.also { xssProtection.block(block!!) }
xssProtectionEnabled?.also { xssProtection.xssProtectionEnabled(xssProtectionEnabled!!) }
if (disabled) {
xssProtection.disable()
}
}
}
}

View File

@ -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.servlet.oauth2.client
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2ClientConfigurer
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
/**
* A Kotlin DSL to configure OAuth 2.0 Authorization Code Grant.
*
* @author Eleftheria Stein
* @since 5.3
* @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]'s.
* @property authorizationRequestRepository the repository used for storing [OAuth2AuthorizationRequest]'s.
* @property accessTokenResponseClient the client used for requesting the access token credential
* from the Token Endpoint.
*/
class AuthorizationCodeGrantDsl {
var authorizationRequestResolver: OAuth2AuthorizationRequestResolver? = null
var authorizationRequestRepository: AuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
var accessTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>? = null
internal fun get(): (OAuth2ClientConfigurer<HttpSecurity>.AuthorizationCodeGrantConfigurer) -> Unit {
return { authorizationCodeGrant ->
authorizationRequestResolver?.also { authorizationCodeGrant.authorizationRequestResolver(authorizationRequestResolver) }
authorizationRequestRepository?.also { authorizationCodeGrant.authorizationRequestRepository(authorizationRequestRepository) }
accessTokenResponseClient?.also { authorizationCodeGrant.accessTokenResponseClient(accessTokenResponseClient) }
}
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.servlet.oauth2.login
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
/**
* A Kotlin DSL to configure the Authorization Server's Authorization Endpoint using
* idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property baseUri the base URI used for authorization requests.
* @property authorizationRequestResolver the resolver used for resolving [OAuth2AuthorizationRequest]'s.
* @property authorizationRequestRepository the repository used for storing [OAuth2AuthorizationRequest]'s.
*/
class AuthorizationEndpointDsl {
var baseUri: String? = null
var authorizationRequestResolver: OAuth2AuthorizationRequestResolver? = null
var authorizationRequestRepository: AuthorizationRequestRepository<OAuth2AuthorizationRequest>? = null
internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>.AuthorizationEndpointConfig) -> Unit {
return { authorizationEndpoint ->
baseUri?.also { authorizationEndpoint.baseUri(baseUri) }
authorizationRequestResolver?.also { authorizationEndpoint.authorizationRequestResolver(authorizationRequestResolver) }
authorizationRequestRepository?.also { authorizationEndpoint.authorizationRequestRepository(authorizationRequestRepository) }
}
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.servlet.oauth2.login
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer
/**
* A Kotlin DSL to configure the Authorization Server's Redirection Endpoint using
* idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property baseUri the URI where the authorization response will be processed.
*/
class RedirectionEndpointDsl {
var baseUri: String? = null
internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>.RedirectionEndpointConfig) -> Unit {
return { redirectionEndpoint ->
baseUri?.also { redirectionEndpoint.baseUri(baseUri) }
}
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.servlet.oauth2.login
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
/**
* A Kotlin DSL to configure the Authorization Server's Token Endpoint using
* idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property accessTokenResponseClient the client used for requesting the access token credential
* from the Token Endpoint.
*/
class TokenEndpointDsl {
var accessTokenResponseClient: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>? = null
internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>.TokenEndpointConfig) -> Unit {
return { tokenEndpoint ->
accessTokenResponseClient?.also { tokenEndpoint.accessTokenResponseClient(accessTokenResponseClient) }
}
}
}

View File

@ -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.servlet.oauth2.login
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest
import org.springframework.security.oauth2.client.registration.ClientRegistration
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.security.oauth2.core.user.OAuth2User
/**
* A Kotlin DSL to configure the Authorization Server's UserInfo Endpoint using
* idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property userService the OAuth 2.0 service used for obtaining the user attributes of the End-User
* from the UserInfo Endpoint.
* @property oidcUserService the OpenID Connect 1.0 service used for obtaining the user attributes of the
* End-User from the UserInfo Endpoint.
* @property userAuthoritiesMapper the [GrantedAuthoritiesMapper] used for mapping [OAuth2User.getAuthorities]
*/
class UserInfoEndpointDsl {
var userService: OAuth2UserService<OAuth2UserRequest, OAuth2User>? = null
var oidcUserService: OAuth2UserService<OidcUserRequest, OidcUser>? = null
var userAuthoritiesMapper: GrantedAuthoritiesMapper? = null
private var customUserTypePair: Pair<Class<out OAuth2User>, String>? = null
/**
* Sets a custom [OAuth2User] type and associates it to the provided
* client [ClientRegistration.getRegistrationId] registration identifier.
*
* @param customUserType a custom [OAuth2User] type
* @param clientRegistrationId the client registration identifier
*/
fun customUserType(customUserType: Class<out OAuth2User>, clientRegistrationId: String) {
customUserTypePair = Pair(customUserType, clientRegistrationId)
}
internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>.UserInfoEndpointConfig) -> Unit {
return { userInfoEndpoint ->
userService?.also { userInfoEndpoint.userService(userService) }
oidcUserService?.also { userInfoEndpoint.oidcUserService(oidcUserService) }
userAuthoritiesMapper?.also { userInfoEndpoint.userAuthoritiesMapper(userAuthoritiesMapper) }
customUserTypePair?.also { userInfoEndpoint.customUserType(customUserTypePair!!.first, customUserTypePair!!.second) }
}
}
}

View File

@ -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.servlet.oauth2.resourceserver
import org.springframework.core.convert.converter.Converter
import org.springframework.security.authentication.AbstractAuthenticationToken
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.jwt.JwtDecoder
/**
* A Kotlin DSL to configure JWT Resource Server Support using idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property jwtAuthenticationConverter the [Converter] to use for converting a [Jwt] into
* an [AbstractAuthenticationToken].
* @property jwtDecoder the [JwtDecoder] to use.
* @property jwkSetUri configures a [JwtDecoder] using a
* <a target="_blank" href="https://tools.ietf.org/html/rfc7517">JSON Web Key (JWK)</a> URL
*/
class JwtDsl {
var jwtAuthenticationConverter: Converter<Jwt, out AbstractAuthenticationToken>? = null
var jwtDecoder: JwtDecoder? = null
var jwkSetUri: String? = null
internal fun get(): (OAuth2ResourceServerConfigurer<HttpSecurity>.JwtConfigurer) -> Unit {
return { jwt ->
jwtAuthenticationConverter?.also { jwt.jwtAuthenticationConverter(jwtAuthenticationConverter) }
jwtDecoder?.also { jwt.decoder(jwtDecoder) }
jwkSetUri?.also { jwt.jwkSetUri(jwkSetUri) }
}
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.servlet.oauth2.resourceserver
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector
/**
* A Kotlin DSL to configure JWT Resource Server Support using idiomatic Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property introspectionUri the URI of the Introspection endpoint.
* @property introspector the [OpaqueTokenIntrospector] to use.
*/
class OpaqueTokenDsl {
var introspectionUri: String? = null
var introspector: OpaqueTokenIntrospector? = null
private var clientCredentials: Pair<String, String>? = 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)
}
internal fun get(): (OAuth2ResourceServerConfigurer<HttpSecurity>.OpaqueTokenConfigurer) -> Unit {
return { opaqueToken ->
introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
introspector?.also { opaqueToken.introspector(introspector) }
clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
}
}
}

View File

@ -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.servlet.session
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
import org.springframework.security.core.session.SessionRegistry
import org.springframework.security.web.session.SessionInformationExpiredStrategy
/**
* A Kotlin DSL to configure the behaviour of multiple sessions using idiomatic
* Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
* @property maximumSessions controls the maximum number of sessions for a user.
* @property expiredUrl the URL to redirect to if a user tries to access a resource and
* their session has been expired due to too many sessions for the current user.
* @property expiredSessionStrategy determines the behaviour when an expired session
* is detected.
* @property maxSessionsPreventsLogin if true, prevents a user from authenticating when the
* [maximumSessions] has been reached. Otherwise (default), the user who authenticates
* is allowed access and an existing user's session is expired.
* @property sessionRegistry the [SessionRegistry] implementation used.
*
*/
class SessionConcurrencyDsl {
var maximumSessions: Int? = null
var expiredUrl: String? = null
var expiredSessionStrategy: SessionInformationExpiredStrategy? = null
var maxSessionsPreventsLogin: Boolean? = null
var sessionRegistry: SessionRegistry? = null
internal fun get(): (SessionManagementConfigurer<HttpSecurity>.ConcurrencyControlConfigurer) -> Unit {
return { sessionConcurrencyControl ->
maximumSessions?.also {
sessionConcurrencyControl.maximumSessions(maximumSessions!!)
}
expiredUrl?.also {
sessionConcurrencyControl.expiredUrl(expiredUrl)
}
expiredSessionStrategy?.also {
sessionConcurrencyControl.expiredSessionStrategy(expiredSessionStrategy)
}
maxSessionsPreventsLogin?.also {
sessionConcurrencyControl.maxSessionsPreventsLogin(maxSessionsPreventsLogin!!)
}
sessionRegistry?.also {
sessionConcurrencyControl.sessionRegistry(sessionRegistry)
}
}
}
}

View File

@ -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.servlet.session
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpSession
/**
* A Kotlin DSL to configure session fixation protection using idiomatic
* Kotlin code.
*
* @author Eleftheria Stein
* @since 5.3
*/
class SessionFixationDsl {
private var strategy: SessionFixationStrategy? = null
/**
* Specifies that a new session should be created, but the session attributes from
* the original [HttpSession] should not be retained.
*/
fun newSession() {
this.strategy = SessionFixationStrategy.NEW
}
/**
* Specifies that a new session should be created and the session attributes from
* the original [HttpSession] should be retained.
*/
fun migrateSession() {
this.strategy = SessionFixationStrategy.MIGRATE
}
/**
* Specifies that the Servlet container-provided session fixation protection
* should be used. When a session authenticates, the Servlet method
* [HttpServletRequest.changeSessionId] is called to change the session ID
* and retain all session attributes.
*/
fun changeSessionId() {
this.strategy = SessionFixationStrategy.CHANGE_ID
}
/**
* Specifies that no session fixation protection should be enabled.
*/
fun none() {
this.strategy = SessionFixationStrategy.NONE
}
internal fun get(): (SessionManagementConfigurer<HttpSecurity>.SessionFixationConfigurer) -> Unit {
return { sessionFixation ->
strategy?.also {
when (strategy) {
SessionFixationStrategy.NEW -> sessionFixation.newSession()
SessionFixationStrategy.MIGRATE -> sessionFixation.migrateSession()
SessionFixationStrategy.CHANGE_ID -> sessionFixation.changeSessionId()
SessionFixationStrategy.NONE -> sessionFixation.none()
}
}
}
}
}
private enum class SessionFixationStrategy {
NEW, MIGRATE, CHANGE_ID, NONE
}

View File

@ -0,0 +1,163 @@
/*
* 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.servlet
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.authentication.AnonymousAuthenticationToken
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.config.annotation.EnableWebMvc
import java.util.*
/**
* Tests for [AnonymousDsl]
*
* @author Eleftheria Stein
*/
class AnonymousDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `anonymous when custom principal then custom principal used`() {
this.spring.register(PrincipalConfig::class.java, PrincipalController::class.java).autowire()
this.mockMvc.get("/principal")
.andExpect {
content { string("principal") }
}
}
@EnableWebSecurity
@EnableWebMvc
open class PrincipalConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
anonymous {
principal = "principal"
}
}
}
}
@Test
fun `anonymous when custom key then custom key used`() {
this.spring.register(KeyConfig::class.java, PrincipalController::class.java).autowire()
this.mockMvc.get("/key")
.andExpect {
content { string("key".hashCode().toString()) }
}
}
@EnableWebSecurity
@EnableWebMvc
open class KeyConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
anonymous {
key = "key"
}
}
}
}
@Test
fun `anonymous when disabled then responds with forbidden`() {
this.spring.register(AnonymousDisabledConfig::class.java, PrincipalController::class.java).autowire()
this.mockMvc.get("/principal")
.andExpect {
content { string("") }
}
}
@EnableWebSecurity
@EnableWebMvc
open class AnonymousDisabledConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
anonymous {
disable()
}
}
}
}
@Test
fun `anonymous when custom authorities then authorities used`() {
this.spring.register(AnonymousAuthoritiesConfig::class.java, PrincipalController::class.java).autowire()
this.mockMvc.get("/principal")
.andExpect {
status { isOk }
}
}
@EnableWebSecurity
@EnableWebMvc
open class AnonymousAuthoritiesConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
anonymous {
authorities = listOf(SimpleGrantedAuthority("TEST"))
}
authorizeRequests {
authorize(anyRequest, hasAuthority("TEST"))
}
}
}
}
@RestController
internal class PrincipalController {
@GetMapping("/principal")
fun principal(@AuthenticationPrincipal principal: String?): String? {
return principal
}
@GetMapping("/key")
fun key(): String {
return anonymousToken()
.map { it.keyHash }
.map { it.toString() }
.orElse(null)
}
private fun anonymousToken(): Optional<AnonymousAuthenticationToken> {
return Optional.of(SecurityContextHolder.getContext())
.map { it.authentication }
.filter { it is AnonymousAuthenticationToken }
.map { AnonymousAuthenticationToken::class.java.cast(it) }
}
}
}

View File

@ -0,0 +1,211 @@
/*
* 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.servlet
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.web.util.matcher.RegexRequestMatcher
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Tests for [AuthorizeRequestsDsl]
*
* @author Eleftheria Stein
*/
class AuthorizeRequestsDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `request when secured by regex matcher then responds with forbidden`() {
this.spring.register(AuthorizeRequestsByRegexConfig::class.java).autowire()
this.mockMvc.get("/private")
.andExpect {
status { isForbidden }
}
}
@Test
fun `request when allowed by regex matcher then responds with ok`() {
this.spring.register(AuthorizeRequestsByRegexConfig::class.java).autowire()
this.mockMvc.get("/path")
.andExpect {
status { isOk }
}
}
@EnableWebSecurity
open class AuthorizeRequestsByRegexConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(RegexRequestMatcher("/path", null), permitAll)
authorize(RegexRequestMatcher(".*", null), authenticated)
}
}
}
@RestController
internal class PathController {
@RequestMapping("/path")
fun path() {
}
}
}
@Test
fun `request when secured by mvc then responds with forbidden`() {
this.spring.register(AuthorizeRequestsByMvcConfig::class.java).autowire()
this.mockMvc.get("/private")
.andExpect {
status { isForbidden }
}
}
@Test
fun `request when allowed by mvc then responds with OK`() {
this.spring.register(AuthorizeRequestsByMvcConfig::class.java).autowire()
this.mockMvc.get("/path")
.andExpect {
status { isOk }
}
this.mockMvc.get("/path.html")
.andExpect {
status { isOk }
}
this.mockMvc.get("/path/")
.andExpect {
status { isOk }
}
}
@EnableWebSecurity
@EnableWebMvc
open class AuthorizeRequestsByMvcConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize("/path", permitAll)
authorize("/**", authenticated)
}
}
}
@RestController
internal class PathController {
@RequestMapping("/path")
fun path() {
}
}
}
@Test
fun `request when secured by mvc path variables then responds based on path variable value`() {
this.spring.register(MvcMatcherPathVariablesConfig::class.java).autowire()
this.mockMvc.get("/user/user")
.andExpect {
status { isOk }
}
this.mockMvc.get("/user/deny")
.andExpect {
status { isForbidden }
}
}
@EnableWebSecurity
@EnableWebMvc
open class MvcMatcherPathVariablesConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize("/user/{userName}", "#userName == 'user'")
}
}
}
@RestController
internal class PathController {
@RequestMapping("/user/{user}")
fun path(@PathVariable user: String) {
}
}
}
@Test
fun `request when secured by mvc with servlet path then responds based on servlet path`() {
this.spring.register(MvcMatcherServletPathConfig::class.java).autowire()
this.mockMvc.perform(MockMvcRequestBuilders.get("/spring/path")
.with { request ->
request.servletPath = "/spring"
request
})
.andExpect(status().isForbidden)
this.mockMvc.perform(MockMvcRequestBuilders.get("/other/path")
.with { request ->
request.servletPath = "/other"
request
})
.andExpect(status().isOk)
}
@EnableWebSecurity
@EnableWebMvc
open class MvcMatcherServletPathConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize("/path",
"/spring",
denyAll)
}
}
}
@RestController
internal class PathController {
@RequestMapping("/path")
fun path() {
}
}
}
}

View File

@ -0,0 +1,138 @@
/*
* 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.servlet
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.BeanCreationException
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.http.HttpHeaders
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.cors.CorsConfiguration
import org.springframework.web.cors.CorsConfigurationSource
import org.springframework.web.cors.UrlBasedCorsConfigurationSource
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Tests for [CorsDsl]
*
* @author Eleftheria Stein
*/
class CorsDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `CORS when no MVC then exception`() {
assertThatThrownBy { this.spring.register(DefaultCorsConfig::class.java).autowire() }
.isInstanceOf(BeanCreationException::class.java)
.hasMessageContaining("Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext")
}
@EnableWebSecurity
open class DefaultCorsConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
cors { }
}
}
}
@Test
fun `CORS when CORS configuration source bean then responds with CORS header`() {
this.spring.register(CorsCrossOriginConfig::class.java).autowire()
this.mockMvc.get("/")
{
header(HttpHeaders.ORIGIN, "https://example.com")
}.andExpect {
header { exists("Access-Control-Allow-Origin") }
}
}
@EnableWebMvc
@EnableWebSecurity
open class CorsCrossOriginConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
cors { }
}
}
@Bean
open fun corsConfigurationSource(): CorsConfigurationSource {
val source = UrlBasedCorsConfigurationSource()
val corsConfiguration = CorsConfiguration()
corsConfiguration.allowedOrigins = listOf("*")
corsConfiguration.allowedMethods = listOf(
RequestMethod.GET.name,
RequestMethod.POST.name)
source.registerCorsConfiguration("/**", corsConfiguration)
return source
}
}
@Test
fun `CORS when disabled then response does not include CORS header`() {
this.spring.register(CorsDisabledConfig::class.java).autowire()
this.mockMvc.get("/")
{
header(HttpHeaders.ORIGIN, "https://example.com")
}.andExpect {
header { doesNotExist("Access-Control-Allow-Origin") }
}
}
@EnableWebMvc
@EnableWebSecurity
open class CorsDisabledConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http.cors()
http {
cors {
disable()
}
}
}
@Bean
open fun corsConfigurationSource(): CorsConfigurationSource {
val source = UrlBasedCorsConfigurationSource()
val corsConfiguration = CorsConfiguration()
corsConfiguration.allowedOrigins = listOf("*")
corsConfiguration.allowedMethods = listOf(
RequestMethod.GET.name,
RequestMethod.POST.name)
source.registerCorsConfiguration("/**", corsConfiguration)
return source
}
}
}

View File

@ -0,0 +1,265 @@
/*
* 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.servlet
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.core.Authentication
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy
import org.springframework.security.web.csrf.CsrfTokenRepository
import org.springframework.security.web.csrf.DefaultCsrfToken
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.post
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
/**
* Tests for [CsrfDsl]
*
* @author Eleftheria Stein
*/
class CsrfDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `POST when CSRF enabled and no CSRF token then forbidden`() {
this.spring.register(DefaultCsrfConfig::class.java).autowire()
this.mockMvc.post("/test1")
.andExpect {
status { isForbidden }
}
}
@Test
fun `POST when CSRF enabled and CSRF token then status OK`() {
this.spring.register(DefaultCsrfConfig::class.java, BasicController::class.java).autowire()
this.mockMvc.post("/test1") {
with(csrf())
}.andExpect {
status { isOk }
}
}
@EnableWebSecurity
open class DefaultCsrfConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
csrf { }
}
}
}
@Test
fun `POST when CSRF disabled and no CSRF token then status OK`() {
this.spring.register(CsrfDisabledConfig::class.java, BasicController::class.java).autowire()
this.mockMvc.post("/test1")
.andExpect {
status { isOk }
}
}
@EnableWebSecurity
open class CsrfDisabledConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
csrf {
disable()
}
}
}
}
@Test
fun `CSRF when custom CSRF token repository then repo used`() {
`when`(CustomRepositoryConfig.REPO.loadToken(any<HttpServletRequest>()))
.thenReturn(DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "token"))
this.spring.register(CustomRepositoryConfig::class.java).autowire()
this.mockMvc.get("/test1")
verify(CustomRepositoryConfig.REPO).loadToken(any<HttpServletRequest>())
}
@EnableWebSecurity
open class CustomRepositoryConfig : WebSecurityConfigurerAdapter() {
companion object {
var REPO: CsrfTokenRepository = mock(CsrfTokenRepository::class.java)
}
override fun configure(http: HttpSecurity) {
http {
csrf {
csrfTokenRepository = REPO
}
}
}
}
@Test
fun `CSRF when require CSRF protection matcher then CSRF protection on matching requests`() {
this.spring.register(RequireCsrfProtectionMatcherConfig::class.java, BasicController::class.java).autowire()
this.mockMvc.post("/test1")
.andExpect {
status { isForbidden }
}
this.mockMvc.post("/test2")
.andExpect {
status { isOk }
}
}
@EnableWebSecurity
open class RequireCsrfProtectionMatcherConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
csrf {
requireCsrfProtectionMatcher = AntPathRequestMatcher("/test1")
}
}
}
}
@Test
fun `CSRF when custom session authentication strategy then strategy used`() {
this.spring.register(CustomStrategyConfig::class.java).autowire()
this.mockMvc.perform(formLogin())
verify(CustomStrategyConfig.STRATEGY, atLeastOnce())
.onAuthentication(any(Authentication::class.java), any(HttpServletRequest::class.java), any(HttpServletResponse::class.java))
}
@EnableWebSecurity
open class CustomStrategyConfig : WebSecurityConfigurerAdapter() {
companion object {
var STRATEGY: SessionAuthenticationStrategy = mock(SessionAuthenticationStrategy::class.java)
}
override fun configure(http: HttpSecurity) {
http {
formLogin { }
csrf {
sessionAuthenticationStrategy = STRATEGY
}
}
}
@Bean
override fun userDetailsService(): UserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(userDetails)
}
}
@Test
fun `CSRF when ignoring request matchers then CSRF disabled on matching requests`() {
this.spring.register(IgnoringRequestMatchersConfig::class.java, BasicController::class.java).autowire()
this.mockMvc.post("/test1")
.andExpect {
status { isForbidden }
}
this.mockMvc.post("/test2")
.andExpect {
status { isOk }
}
}
@EnableWebSecurity
open class IgnoringRequestMatchersConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
csrf {
requireCsrfProtectionMatcher = AntPathRequestMatcher("/**")
ignoringRequestMatchers(AntPathRequestMatcher("/test2"))
}
}
}
}
@Test
fun `CSRF when ignoring ant matchers then CSRF disabled on matching requests`() {
this.spring.register(IgnoringAntMatchersConfig::class.java, BasicController::class.java).autowire()
this.mockMvc.post("/test1")
.andExpect {
status { isForbidden }
}
this.mockMvc.post("/test2")
.andExpect {
status { isOk }
}
}
@EnableWebSecurity
open class IgnoringAntMatchersConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
csrf {
requireCsrfProtectionMatcher = AntPathRequestMatcher("/**")
ignoringAntMatchers("/test2")
}
}
}
}
@RestController
internal class BasicController {
@PostMapping("/test1")
fun test1() {
}
@PostMapping("/test2")
fun test2() {
}
}
}

View File

@ -0,0 +1,252 @@
/*
* 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.servlet
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.core.userdetails.User.withUsername
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
import org.springframework.security.web.access.AccessDeniedHandlerImpl
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Tests for [ExceptionHandlingDsl]
*
* @author Eleftheria Stein
*/
class ExceptionHandlingDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `request when exception handling enabled then returns forbidden`() {
this.spring.register(ExceptionHandlingConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
status { isForbidden }
}
}
@EnableWebSecurity
@EnableWebMvc
open class ExceptionHandlingConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
exceptionHandling { }
}
}
}
@Test(expected = AccessDeniedException::class)
fun `request when exception handling disabled then throws exception`() {
this.spring.register(ExceptionHandlingDisabledConfig::class.java).autowire()
this.mockMvc.get("/")
}
@EnableWebSecurity
open class ExceptionHandlingDisabledConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
exceptionHandling {
disable()
}
}
}
}
@Test
fun `exception handling when custom access denied page then redirects to custom page`() {
this.spring.register(AccessDeniedPageConfig::class.java).autowire()
this.mockMvc.get("/admin") {
with(user(withUsername("user").password("password").roles("USER").build()))
}.andExpect {
status { isForbidden }
forwardedUrl("/access-denied")
}
}
@EnableWebSecurity
@EnableWebMvc
open class AccessDeniedPageConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize("/admin", hasAuthority("ROLE_ADMIN"))
authorize(anyRequest, authenticated)
}
exceptionHandling {
accessDeniedPage = "/access-denied"
}
}
}
}
@Test
fun `exception handling when custom access denied handler then handler used`() {
this.spring.register(AccessDeniedHandlerConfig::class.java).autowire()
this.mockMvc.get("/admin") {
with(user(withUsername("user").password("password").roles("USER").build()))
}.andExpect {
status { isForbidden }
forwardedUrl("/access-denied")
}
}
@EnableWebSecurity
@EnableWebMvc
open class AccessDeniedHandlerConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
val customAccessDeniedHandler = AccessDeniedHandlerImpl()
customAccessDeniedHandler.setErrorPage("/access-denied")
http {
authorizeRequests {
authorize("/admin", hasAuthority("ROLE_ADMIN"))
authorize(anyRequest, authenticated)
}
exceptionHandling {
accessDeniedHandler = customAccessDeniedHandler
}
}
}
}
@Test
fun `exception handling when default access denied handler for page then handlers used`() {
this.spring.register(AccessDeniedHandlerForConfig::class.java).autowire()
this.mockMvc.get("/admin1") {
with(user(withUsername("user").password("password").roles("USER").build()))
}.andExpect {
status { isForbidden }
forwardedUrl("/access-denied1")
}
this.mockMvc.get("/admin2") {
with(user(withUsername("user").password("password").roles("USER").build()))
}.andExpect {
status { isForbidden }
forwardedUrl("/access-denied2")
}
}
@EnableWebSecurity
@EnableWebMvc
open class AccessDeniedHandlerForConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
val customAccessDeniedHandler1 = AccessDeniedHandlerImpl()
customAccessDeniedHandler1.setErrorPage("/access-denied1")
val customAccessDeniedHandler2 = AccessDeniedHandlerImpl()
customAccessDeniedHandler2.setErrorPage("/access-denied2")
http {
authorizeRequests {
authorize("/admin1", hasAuthority("ROLE_ADMIN"))
authorize("/admin2", hasAuthority("ROLE_ADMIN"))
authorize(anyRequest, authenticated)
}
exceptionHandling {
defaultAccessDeniedHandlerFor(customAccessDeniedHandler1, AntPathRequestMatcher("/admin1"))
defaultAccessDeniedHandlerFor(customAccessDeniedHandler2, AntPathRequestMatcher("/admin2"))
}
}
}
}
@Test
fun `exception handling when custom authentication entry point then entry point used`() {
this.spring.register(AuthenticationEntryPointConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
status { isFound }
redirectedUrl("http://localhost/custom-login")
}
}
@EnableWebSecurity
@EnableWebMvc
open class AuthenticationEntryPointConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
exceptionHandling {
authenticationEntryPoint = LoginUrlAuthenticationEntryPoint("/custom-login")
}
}
}
}
@Test
fun `exception handling when authentication entry point for page then entry points used`() {
this.spring.register(AuthenticationEntryPointForConfig::class.java).autowire()
this.mockMvc.get("/secured1")
.andExpect {
status { isFound }
redirectedUrl("http://localhost/custom-login1")
}
this.mockMvc.get("/secured2")
.andExpect {
status { isFound }
redirectedUrl("http://localhost/custom-login2")
}
}
@EnableWebSecurity
@EnableWebMvc
open class AuthenticationEntryPointForConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
val customAuthenticationEntryPoint1 = LoginUrlAuthenticationEntryPoint("/custom-login1")
val customAuthenticationEntryPoint2 = LoginUrlAuthenticationEntryPoint("/custom-login2")
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
exceptionHandling {
defaultAuthenticationEntryPointFor(customAuthenticationEntryPoint1, AntPathRequestMatcher("/secured1"))
defaultAuthenticationEntryPointFor(customAuthenticationEntryPoint2, AntPathRequestMatcher("/secured2"))
}
}
}
}
}

View File

@ -0,0 +1,291 @@
/*
* 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.servlet
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.core.userdetails.User
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler
import org.springframework.stereotype.Controller
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.GetMapping
/**
* Tests for [FormLoginDsl]
*
* @author Eleftheria Stein
*/
class FormLoginDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `login page when form login configured then default login page created`() {
this.spring.register(FormLoginConfig::class.java, UserConfig::class.java).autowire()
this.mockMvc.get("/login")
.andExpect {
status { isOk }
}
}
@Test
fun `login when success then redirects to home`() {
this.spring.register(FormLoginConfig::class.java, UserConfig::class.java).autowire()
this.mockMvc.perform(formLogin())
.andExpect {
status().isFound
redirectedUrl("/")
}
}
@Test
fun `login when failure then redirects to login page with error`() {
this.spring.register(FormLoginConfig::class.java, UserConfig::class.java).autowire()
this.mockMvc.perform(formLogin().password("invalid"))
.andExpect {
status().isFound
redirectedUrl("/login?error")
}
}
@EnableWebSecurity
open class FormLoginConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
formLogin {}
}
}
}
@Test
fun `request when secure then redirects to default login page`() {
this.spring.register(AllSecuredConfig::class.java, UserConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
status { isFound }
redirectedUrl("http://localhost/login")
}
}
@EnableWebSecurity
open class AllSecuredConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
formLogin {}
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
}
}
@Test
fun `request when secure and custom login page then redirects to custom login page`() {
this.spring.register(LoginPageConfig::class.java, UserConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
status { isFound }
redirectedUrl("http://localhost/log-in")
}
}
@EnableWebSecurity
open class LoginPageConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
formLogin {
loginPage = "/log-in"
}
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
}
}
@Test
fun `login when custom success handler then used`() {
this.spring.register(SuccessHandlerConfig::class.java, UserConfig::class.java).autowire()
this.mockMvc.perform(formLogin())
.andExpect {
status().isFound
redirectedUrl("/success")
}
}
@EnableWebSecurity
open class SuccessHandlerConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
formLogin {
authenticationSuccessHandler = SimpleUrlAuthenticationSuccessHandler("/success")
}
}
}
}
@Test
fun `login when custom failure handler then used`() {
this.spring.register(FailureHandlerConfig::class.java, UserConfig::class.java).autowire()
this.mockMvc.perform(formLogin().password("invalid"))
.andExpect {
status().isFound
redirectedUrl("/failure")
}
}
@EnableWebSecurity
open class FailureHandlerConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
formLogin {
authenticationFailureHandler = SimpleUrlAuthenticationFailureHandler("/failure")
}
}
}
}
@Test
fun `login when custom failure url then used`() {
this.spring.register(FailureHandlerConfig::class.java, UserConfig::class.java).autowire()
this.mockMvc.perform(formLogin().password("invalid"))
.andExpect {
status().isFound
redirectedUrl("/failure")
}
}
@EnableWebSecurity
open class FailureUrlConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
formLogin {
failureUrl = "/failure"
}
}
}
}
@Test
fun `login when custom login processing url then used`() {
this.spring.register(LoginProcessingUrlConfig::class.java, UserConfig::class.java).autowire()
this.mockMvc.perform(formLogin("/custom"))
.andExpect {
status().isFound
redirectedUrl("/")
}
}
@EnableWebSecurity
open class LoginProcessingUrlConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
formLogin {
loginProcessingUrl = "/custom"
}
}
}
}
@Test
fun `login when default success url then redirected to url`() {
this.spring.register(DefaultSuccessUrlConfig::class.java, UserConfig::class.java).autowire()
this.mockMvc.perform(formLogin())
.andExpect {
status().isFound
redirectedUrl("/custom")
}
}
@EnableWebSecurity
open class DefaultSuccessUrlConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
formLogin {
defaultSuccessUrl("/custom", true)
}
}
}
}
@Test
fun `login when permit all then login page not protected`() {
this.spring.register(PermitAllConfig::class.java, UserConfig::class.java).autowire()
this.mockMvc.get("/custom/login")
.andExpect {
status { isOk }
}
}
@EnableWebSecurity
open class PermitAllConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
formLogin {
loginPage = "/custom/login"
permitAll()
}
}
}
@Controller
class LoginController {
@GetMapping("/custom/login")
fun loginPage() {}
}
}
@Configuration
open class UserConfig {
@Autowired
fun configureGlobal(auth: AuthenticationManagerBuilder) {
auth
.inMemoryAuthentication()
.withUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER"))
}
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.servlet
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpHeaders
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
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.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [HeadersDsl]
*
* @author Eleftheria Stein
*/
class HeadersDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `headers when defaults enabled then default headers in response`() {
this.spring.register(DefaultHeadersConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff") }
header { string(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name) }
header { string(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains") }
header { string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate") }
header { string(HttpHeaders.EXPIRES, "0") }
header { string(HttpHeaders.PRAGMA, "no-cache") }
header { string(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block") }
}
}
@EnableWebSecurity
open class DefaultHeadersConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers { }
}
}
}
@Test
fun `headers when feature policy configured then header in response`() {
this.spring.register(FeaturePolicyConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header { string("Feature-Policy", "geolocation 'self'") }
}
}
@EnableWebSecurity
open class FeaturePolicyConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
featurePolicy(policyDirectives = "geolocation 'self'")
}
}
}
}
}

View File

@ -0,0 +1,205 @@
/*
* 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.servlet
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.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationDetailsSource
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.core.AuthenticationException
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
/**
* Tests for [HttpBasicDsl]
*
* @author Eleftheria Stein
*/
class HttpBasicDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `http basic when configured then insecure request cannot access`() {
this.spring.register(HttpBasicConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
status { isUnauthorized }
}
}
@Test
fun `http basic when configured then response includes basic challenge`() {
this.spring.register(HttpBasicConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header { string("WWW-Authenticate", "Basic realm=\"Realm\"") }
}
}
@Test
fun `http basic when valid user then permitted`() {
this.spring.register(HttpBasicConfig::class.java, UserConfig::class.java, MainController::class.java).autowire()
this.mockMvc.get("/") {
with(httpBasic("user", "password"))
}.andExpect {
status { isOk }
}
}
@EnableWebSecurity
open class HttpBasicConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
httpBasic {}
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
}
}
@Test
fun httpBasicWhenCustomRealmThenUsed() {
this.spring.register(CustomRealmConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header { string("WWW-Authenticate", "Basic realm=\"Custom Realm\"") }
}
}
@EnableWebSecurity
open class CustomRealmConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
httpBasic {
realmName = "Custom Realm"
}
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
}
}
@Test
fun `http basic when custom authentication entry point then used`() {
this.spring.register(CustomAuthenticationEntryPointConfig::class.java).autowire()
this.mockMvc.get("/")
verify<AuthenticationEntryPoint>(CustomAuthenticationEntryPointConfig.ENTRY_POINT)
.commence(any(HttpServletRequest::class.java),
any(HttpServletResponse::class.java),
any(AuthenticationException::class.java))
}
@EnableWebSecurity
open class CustomAuthenticationEntryPointConfig : WebSecurityConfigurerAdapter() {
companion object {
var ENTRY_POINT: AuthenticationEntryPoint = mock(AuthenticationEntryPoint::class.java)
}
override fun configure(http: HttpSecurity) {
http {
httpBasic {
authenticationEntryPoint = ENTRY_POINT
}
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
}
}
@Test
fun `http basic when custom authentication details source then used`() {
this.spring.register(CustomAuthenticationDetailsSourceConfig::class.java,
UserConfig::class.java, MainController::class.java).autowire()
this.mockMvc.get("/") {
with(httpBasic("username", "password"))
}
verify(CustomAuthenticationDetailsSourceConfig.AUTHENTICATION_DETAILS_SOURCE)
.buildDetails(any(HttpServletRequest::class.java))
}
@EnableWebSecurity
open class CustomAuthenticationDetailsSourceConfig : WebSecurityConfigurerAdapter() {
companion object {
var AUTHENTICATION_DETAILS_SOURCE = mock(AuthenticationDetailsSource::class.java) as AuthenticationDetailsSource<HttpServletRequest, *>
}
override fun configure(http: HttpSecurity) {
http {
httpBasic {
authenticationDetailsSource = AUTHENTICATION_DETAILS_SOURCE
}
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
}
}
@Configuration
open class UserConfig {
@Bean
open fun userDetailsService(): UserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(userDetails)
}
}
@RestController
class MainController {
@GetMapping("/")
fun main() {
}
}
}

View File

@ -0,0 +1,215 @@
/*
* 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.servlet
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpHeaders
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
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.util.matcher.RegexRequestMatcher
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.post
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Tests for [HttpSecurityDsl]
*
* @author Eleftheria Stein
*/
class HttpSecurityDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `post when default security configured then CSRF prevents the request`() {
this.spring.register(DefaultSecurityConfig::class.java).autowire()
this.mockMvc.post("/")
.andExpect {
status { isForbidden }
}
}
@Test
fun `when default security configured then default headers are in the response`() {
this.spring.register(DefaultSecurityConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header {
string(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff")
}
header {
string(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name)
}
header {
string(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")
}
header {
string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")
}
header {
string(HttpHeaders.EXPIRES, "0")
}
header {
string(HttpHeaders.PRAGMA, "no-cache")
}
header {
string(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block")
}
}
}
@EnableWebSecurity
open class DefaultSecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {}
}
@Configuration
open class UserConfig {
@Bean
open fun userDetailsService(): UserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(userDetails)
}
}
}
@Test
fun `request when it does not match the security request matcher then the security rules do not apply`() {
this.spring.register(SecurityRequestMatcherConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
status { isNotFound }
}
}
@Test
fun `request when it matches the security request matcher then the security rules apply`() {
this.spring.register(SecurityRequestMatcherConfig::class.java).autowire()
this.mockMvc.get("/path")
.andExpect {
status { isForbidden }
}
}
@EnableWebSecurity
open class SecurityRequestMatcherConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
securityMatcher(RegexRequestMatcher("/path", null))
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
}
}
@Test
fun `request when it does not match the security pattern matcher then the security rules do not apply`() {
this.spring.register(SecurityPatternMatcherConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
status { isNotFound }
}
}
@Test
fun `request when it matches the security pattern matcher then the security rules apply`() {
this.spring.register(SecurityPatternMatcherConfig::class.java).autowire()
this.mockMvc.get("/path")
.andExpect {
status { isForbidden }
}
}
@EnableWebSecurity
@EnableWebMvc
open class SecurityPatternMatcherConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
securityMatcher("/path")
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
}
}
@Test
fun `security pattern matcher when used with security request matcher then both apply`() {
this.spring.register(MultiMatcherConfig::class.java).autowire()
this.mockMvc.get("/path1")
.andExpect {
status { isForbidden }
}
this.mockMvc.get("/path2")
.andExpect {
status { isForbidden }
}
this.mockMvc.get("/path3")
.andExpect {
status { isNotFound }
}
}
@EnableWebSecurity
@EnableWebMvc
open class MultiMatcherConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
securityMatcher("/path1")
securityMatcher(RegexRequestMatcher("/path2", null))
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
}
}
}

View File

@ -0,0 +1,310 @@
/*
* 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.servlet
import org.assertj.core.api.Assertions.assertThat
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.mock.web.MockHttpSession
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
import org.springframework.security.web.authentication.logout.LogoutHandler
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.post
/**
* Tests for [LogoutDsl]
*
* @author Eleftheria Stein
*/
class LogoutDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `logout when custom logout url then custom url used`() {
this.spring.register(CustomLogoutUrlConfig::class.java).autowire()
this.mockMvc.post("/custom/logout") {
with(csrf())
}.andExpect {
status { isFound }
redirectedUrl("/login?logout")
}
}
@EnableWebSecurity
open class CustomLogoutUrlConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
logout {
logoutUrl = "/custom/logout"
}
}
}
}
@Test
fun `logout when custom logout request matcher then custom request matcher used`() {
this.spring.register(CustomLogoutRequestMatcherConfig::class.java).autowire()
this.mockMvc.post("/custom/logout") {
with(csrf())
}.andExpect {
status { isFound }
redirectedUrl("/login?logout")
}
}
@EnableWebSecurity
open class CustomLogoutRequestMatcherConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
logout {
logoutRequestMatcher = AntPathRequestMatcher("/custom/logout")
}
}
}
}
@Test
fun `logout when custom success url then redirects to success url`() {
this.spring.register(SuccessUrlConfig::class.java).autowire()
this.mockMvc.post("/logout") {
with(csrf())
}.andExpect {
status { isFound }
redirectedUrl("/login")
}
}
@EnableWebSecurity
open class SuccessUrlConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
logout {
logoutSuccessUrl = "/login"
}
}
}
}
@Test
fun `logout when custom success handler then redirects to success url`() {
this.spring.register(SuccessHandlerConfig::class.java).autowire()
this.mockMvc.post("/logout") {
with(csrf())
}.andExpect {
status { isFound }
redirectedUrl("/")
}
}
@EnableWebSecurity
open class SuccessHandlerConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
logout {
logoutSuccessHandler = SimpleUrlLogoutSuccessHandler()
}
}
}
}
@Test
fun `logout when permit all then logout allowed`() {
this.spring.register(PermitAllConfig::class.java).autowire()
this.mockMvc.post("/custom/logout") {
with(csrf())
}.andExpect {
status { isFound }
redirectedUrl("/login?logout")
}
}
@EnableWebSecurity
open class PermitAllConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
logout {
logoutUrl = "/custom/logout"
permitAll()
}
}
}
}
@Test
fun `logout when clear authentication false then authentication not cleared`() {
this.spring.register(ClearAuthenticationFalseConfig::class.java).autowire()
val currentContext = SecurityContextHolder.createEmptyContext()
currentContext.authentication = TestingAuthenticationToken("user", "password", "ROLE_USER")
val currentSession = MockHttpSession()
currentSession.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, currentContext)
this.mockMvc.post("/logout") {
with(csrf())
session = currentSession
}.andExpect {
status { isFound }
redirectedUrl("/login?logout")
}
assertThat(currentContext.authentication).isNotNull
}
@EnableWebSecurity
open class ClearAuthenticationFalseConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
logout {
clearAuthentication = false
}
}
}
}
@Test
fun `logout when invalidate http session false then session not invalidated`() {
this.spring.register(InvalidateHttpSessionFalseConfig::class.java).autowire()
val currentSession = MockHttpSession()
this.mockMvc.post("/logout") {
with(csrf())
session = currentSession
}.andExpect {
status { isFound }
redirectedUrl("/login?logout")
}
assertThat(currentSession.isInvalid).isFalse()
}
@EnableWebSecurity
open class InvalidateHttpSessionFalseConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
logout {
invalidateHttpSession = false
}
}
}
}
@Test
fun `logout when delete cookies then cookies are cleared`() {
this.spring.register(DeleteCookiesConfig::class.java).autowire()
this.mockMvc.post("/logout") {
with(csrf())
}.andExpect {
status { isFound }
redirectedUrl("/login?logout")
cookie { maxAge("remove", 0) }
}
}
@EnableWebSecurity
open class DeleteCookiesConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
logout {
deleteCookies("remove")
}
}
}
}
@Test
fun `logout when default logout success handler for request then custom handler used`() {
this.spring.register(DefaultLogoutSuccessHandlerForConfig::class.java).autowire()
this.mockMvc.post("/logout/default") {
with(csrf())
}.andExpect {
status { isFound }
redirectedUrl("/login?logout")
}
this.mockMvc.post("/logout/custom") {
with(csrf())
}.andExpect {
status { isFound }
redirectedUrl("/")
}
}
@EnableWebSecurity
open class DefaultLogoutSuccessHandlerForConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
logout {
logoutRequestMatcher = AntPathRequestMatcher("/logout/**")
defaultLogoutSuccessHandlerFor(SimpleUrlLogoutSuccessHandler(), AntPathRequestMatcher("/logout/custom"))
}
}
}
}
@Test
fun `logout when custom logout handler then custom handler used`() {
this.spring.register(CustomLogoutHandlerConfig::class.java).autowire()
this.mockMvc.post("/logout") {
with(csrf())
}
verify(CustomLogoutHandlerConfig.HANDLER).logout(any(), any(), any())
}
@EnableWebSecurity
open class CustomLogoutHandlerConfig : WebSecurityConfigurerAdapter() {
companion object {
var HANDLER: LogoutHandler = mock(LogoutHandler::class.java)
}
override fun configure(http: HttpSecurity) {
http {
logout {
addLogoutHandler(HANDLER)
}
}
}
}
}

View File

@ -0,0 +1,144 @@
/*
* 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.servlet
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.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository
import org.springframework.security.oauth2.core.OAuth2AccessToken
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [OAuth2ClientDsl]
*
* @author Eleftheria Stein
*/
class OAuth2ClientDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `oauth2Client when custom client registration repository then bean is not required`() {
this.spring.register(ClientRepoConfig::class.java).autowire()
}
@EnableWebSecurity
open class ClientRepoConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
oauth2Client {
clientRegistrationRepository = InMemoryClientRegistrationRepository(
CommonOAuth2Provider.GOOGLE
.getBuilder("google").clientId("clientId").clientSecret("clientSecret")
.build()
)
}
}
}
}
@Test
fun `oauth2Client when custom authorized client repository then repository used`() {
this.spring.register(ClientRepositoryConfig::class.java, ClientConfig::class.java).autowire()
val authorizationRequest = OAuth2AuthorizationRequest
.authorizationCode()
.state("test")
.clientId("clientId")
.authorizationUri("https://test")
.redirectUri("http://localhost/callback")
.attributes(mapOf(Pair(OAuth2ParameterNames.REGISTRATION_ID, "registrationId")))
.build()
`when`(ClientRepositoryConfig.REQUEST_REPOSITORY.loadAuthorizationRequest(any()))
.thenReturn(authorizationRequest)
`when`(ClientRepositoryConfig.REQUEST_REPOSITORY.removeAuthorizationRequest(any(), any()))
.thenReturn(authorizationRequest)
`when`(ClientRepositoryConfig.CLIENT.getTokenResponse(any()))
.thenReturn(OAuth2AccessTokenResponse
.withToken("token")
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.build())
this.mockMvc.get("/callback") {
param("state", "test")
param("code", "123")
}
verify(ClientRepositoryConfig.CLIENT_REPOSITORY).saveAuthorizedClient(any(), any(), any(), any())
}
@EnableWebSecurity
open class ClientRepositoryConfig : WebSecurityConfigurerAdapter() {
companion object {
var REQUEST_REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
var CLIENT: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> = mock(OAuth2AccessTokenResponseClient::class.java) as OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
var CLIENT_REPOSITORY: OAuth2AuthorizedClientRepository = mock(OAuth2AuthorizedClientRepository::class.java)
}
override fun configure(http: HttpSecurity) {
http {
oauth2Client {
authorizedClientRepository = CLIENT_REPOSITORY
authorizationCodeGrant {
authorizationRequestRepository = REQUEST_REPOSITORY
accessTokenResponseClient = CLIENT
}
}
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
}
}
@Configuration
open class ClientConfig {
@Bean
open fun clientRegistrationRepository(): ClientRegistrationRepository {
return InMemoryClientRegistrationRepository(
CommonOAuth2Provider.GOOGLE
.getBuilder("google")
.registrationId("registrationId")
.clientId("clientId")
.clientSecret("clientSecret")
.build()
)
}
}
}

View File

@ -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.servlet
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
/**
* Tests for [OAuth2LoginDsl]
*
* @author Eleftheria Stein
*/
class OAuth2LoginDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `oauth2Login when custom client registration repository then bean is not required`() {
this.spring.register(ClientRepoConfig::class.java).autowire()
}
@EnableWebSecurity
open class ClientRepoConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
oauth2Login {
clientRegistrationRepository = InMemoryClientRegistrationRepository(
CommonOAuth2Provider.GOOGLE
.getBuilder("google").clientId("clientId").clientSecret("clientSecret")
.build()
)
}
}
}
}
@Test
fun `login page when oAuth2Login configured then default login page created`() {
this.spring.register(OAuth2LoginConfig::class.java, ClientConfig::class.java).autowire()
this.mockMvc.get("/login")
.andExpect {
status { isOk }
}
}
@EnableWebSecurity
open class OAuth2LoginConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
oauth2Login { }
}
}
}
@Test
fun `login page when custom login page then redirected to custom page`() {
this.spring.register(LoginPageConfig::class.java, ClientConfig::class.java).autowire()
this.mockMvc.get("/custom-login")
.andExpect {
status { isOk }
}
}
@EnableWebSecurity
open class LoginPageConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
oauth2Login {
loginPage = "/custom-login"
}
}
}
@RestController
class LoginController {
@GetMapping("/custom-login")
fun loginPage() { }
}
}
@Configuration
open class ClientConfig {
@Bean
open fun clientRegistrationRepository(): ClientRegistrationRepository {
return InMemoryClientRegistrationRepository(
CommonOAuth2Provider.GOOGLE
.getBuilder("google").clientId("clientId").clientSecret("clientSecret")
.build()
)
}
}
}

View File

@ -0,0 +1,156 @@
/*
* 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.servlet
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames.SUB
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.jwt.JwtDecoder
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.security.web.access.AccessDeniedHandler
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [OAuth2ResourceServerDsl]
*
* @author Eleftheria Stein
*/
class OAuth2ResourceServerDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `oauth2Resource server when custom entry point then entry point used`() {
this.spring.register(EntryPointConfig::class.java).autowire()
this.mockMvc.get("/")
verify(EntryPointConfig.ENTRY_POINT).commence(any(), any(), any())
}
@EnableWebSecurity
open class EntryPointConfig : WebSecurityConfigurerAdapter() {
companion object {
var ENTRY_POINT: AuthenticationEntryPoint = mock(AuthenticationEntryPoint::class.java)
}
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
authenticationEntryPoint = ENTRY_POINT
jwt { }
}
}
}
@Bean
open fun jwtDecoder(): JwtDecoder {
return mock(JwtDecoder::class.java)
}
}
@Test
fun `oauth2Resource server when custom bearer token resolver then resolver used`() {
this.spring.register(BearerTokenResolverConfig::class.java).autowire()
this.mockMvc.get("/")
verify(BearerTokenResolverConfig.RESOLVER).resolve(any())
}
@EnableWebSecurity
open class BearerTokenResolverConfig : WebSecurityConfigurerAdapter() {
companion object {
var RESOLVER: BearerTokenResolver = mock(BearerTokenResolver::class.java)
}
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
bearerTokenResolver = RESOLVER
jwt { }
}
}
}
@Bean
open fun jwtDecoder(): JwtDecoder {
return mock(JwtDecoder::class.java)
}
}
@Test
fun `oauth2Resource server when custom access denied handler then handler used`() {
this.spring.register(AccessDeniedHandlerConfig::class.java).autowire()
`when`(AccessDeniedHandlerConfig.DECODER.decode(anyString())).thenReturn(
Jwt.withTokenValue("token")
.header("alg", "none")
.claim(SUB, "user")
.build())
this.mockMvc.get("/") {
header("Authorization", "Bearer token")
}
verify(AccessDeniedHandlerConfig.DENIED_HANDLER).handle(any(), any(), any())
}
@EnableWebSecurity
open class AccessDeniedHandlerConfig : WebSecurityConfigurerAdapter() {
companion object {
var DENIED_HANDLER: AccessDeniedHandler = mock(AccessDeniedHandler::class.java)
var DECODER: JwtDecoder = mock(JwtDecoder::class.java)
}
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, denyAll)
}
oauth2ResourceServer {
accessDeniedHandler = DENIED_HANDLER
jwt { }
}
}
}
@Bean
open fun jwtDecoder(): JwtDecoder {
return DECODER
}
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.servlet
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.web.PortMapperImpl
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import java.util.*
/**
* Tests for [PortMapperDsl]
*
* @author Eleftheria Stein
*/
class PortMapperDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `port mapper when specifying map then redirects to https port`() {
this.spring.register(PortMapperMapConfig::class.java).autowire()
this.mockMvc.get("http://localhost:543")
.andExpect {
redirectedUrl("https://localhost:123")
}
}
@EnableWebSecurity
open class PortMapperMapConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
requiresChannel {
secure(anyRequest, requiresSecure)
}
portMapper {
map(543, 123)
}
}
}
}
@Test
fun `port mapper when specifying custom mapper then redirects to https port`() {
this.spring.register(CustomPortMapperConfig::class.java).autowire()
this.mockMvc.get("http://localhost:543")
.andExpect {
redirectedUrl("https://localhost:123")
}
}
@EnableWebSecurity
open class CustomPortMapperConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
val customPortMapper = PortMapperImpl()
customPortMapper.setPortMappings(Collections.singletonMap("543", "123"))
http {
requiresChannel {
secure(anyRequest, requiresSecure)
}
portMapper {
portMapper = customPortMapper
}
}
}
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.servlet
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin
import org.springframework.security.web.savedrequest.NullRequestCache
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl
/**
* Tests for [RequestCacheDsl]
*
* @author Eleftheria Stein
*/
class RequestCacheDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `GET when request cache enabled then redirected to cached page`() {
this.spring.register(RequestCacheConfig::class.java).autowire()
this.mockMvc.get("/test")
this.mockMvc.perform(formLogin())
.andExpect {
redirectedUrl("http://localhost/test")
}
}
@EnableWebSecurity
open class RequestCacheConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
requestCache { }
formLogin { }
}
}
}
@Test
fun `GET when custom request cache then custom request cache used`() {
this.spring.register(CustomRequestCacheConfig::class.java).autowire()
this.mockMvc.get("/test")
this.mockMvc.perform(formLogin())
.andExpect {
redirectedUrl("/")
}
}
@EnableWebSecurity
open class CustomRequestCacheConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
requestCache {
requestCache = NullRequestCache()
}
formLogin { }
}
}
}
}

View File

@ -0,0 +1,138 @@
/*
* 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.servlet
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.web.access.channel.ChannelProcessor
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.servlet.config.annotation.EnableWebMvc
/**
* Tests for [RequiresChannelDsl]
*
* @author Eleftheria Stein
*/
class RequiresChannelDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `requires channel when requires secure then redirects to https`() {
this.spring.register(RequiresSecureConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
redirectedUrl("https://localhost/")
}
}
@EnableWebSecurity
open class RequiresSecureConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
requiresChannel {
secure(anyRequest, requiresSecure)
}
}
}
}
@Test
fun `request when channel matches mvc with servlet path then redirects based on servlet path`() {
this.spring.register(MvcMatcherServletPathConfig::class.java).autowire()
this.mockMvc.perform(MockMvcRequestBuilders.get("/spring/path")
.with { request ->
request.servletPath = "/spring"
request
})
.andExpect(status().isFound)
.andExpect(redirectedUrl("https://localhost/spring/path"))
this.mockMvc.perform(MockMvcRequestBuilders.get("/other/path")
.with { request ->
request.servletPath = "/other"
request
})
.andExpect(MockMvcResultMatchers.status().isOk)
}
@EnableWebSecurity
@EnableWebMvc
open class MvcMatcherServletPathConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
requiresChannel {
secure("/path",
"/spring",
requiresSecure)
}
}
}
@RestController
internal class PathController {
@RequestMapping("/path")
fun path() {
}
}
}
@Test
fun `requires channel when channel processors configured then channel processors used`() {
`when`(ChannelProcessorsConfig.CHANNEL_PROCESSOR.supports(any())).thenReturn(true)
this.spring.register(ChannelProcessorsConfig::class.java).autowire()
this.mockMvc.get("/")
verify(ChannelProcessorsConfig.CHANNEL_PROCESSOR).supports(any())
}
@EnableWebSecurity
open class ChannelProcessorsConfig : WebSecurityConfigurerAdapter() {
companion object {
var CHANNEL_PROCESSOR: ChannelProcessor = mock(ChannelProcessor::class.java)
}
override fun configure(http: HttpSecurity) {
http {
requiresChannel {
channelProcessors = listOf(CHANNEL_PROCESSOR)
secure(anyRequest, requiresSecure)
}
}
}
}
}

View File

@ -0,0 +1,105 @@
/*
* 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.servlet
import org.assertj.core.api.Assertions
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.BeanCreationException
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.core.io.ClassPathResource
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.saml2.credentials.Saml2X509Credential
import org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.VERIFICATION
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration
import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
/**
* Tests for [Saml2Dsl]
*
* @author Eleftheria Stein
*/
class Saml2DslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `saml2Login when no relying party registration repository then exception`() {
Assertions.assertThatThrownBy { this.spring.register(Saml2LoginNoRelyingPArtyRegistrationRepoConfig::class.java).autowire() }
.isInstanceOf(BeanCreationException::class.java)
.hasMessageContaining("relyingPartyRegistrationRepository cannot be null")
}
@EnableWebSecurity
open class Saml2LoginNoRelyingPArtyRegistrationRepoConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
saml2Login { }
}
}
}
@Test
fun `login page when saml2Configured then default login page created`() {
this.spring.register(Saml2LoginConfig::class.java).autowire()
this.mockMvc.get("/login")
.andExpect {
status { isOk }
}
}
@EnableWebSecurity
open class Saml2LoginConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
saml2Login {
relyingPartyRegistrationRepository =
InMemoryRelyingPartyRegistrationRepository(
RelyingPartyRegistration.withRegistrationId("samlId")
.remoteIdpEntityId("entityId")
.assertionConsumerServiceUrlTemplate("{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI)
.credentials { c -> c.add(Saml2X509Credential(loadCert("rod.cer"), VERIFICATION)) }
.idpWebSsoUrl("ssoUrl")
.build()
)
}
}
}
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
}
}
}
}

View File

@ -0,0 +1,218 @@
/*
* 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.servlet
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.annotation.Bean
import org.springframework.mock.web.MockHttpSession
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.core.Authentication
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
import org.springframework.security.web.authentication.session.SessionAuthenticationException
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
/**
* Tests for [SessionManagementDsl]
*
* @author Eleftheria Stein
*/
class SessionManagementDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `session management when invalid session url then redirected to url`() {
this.spring.register(InvalidSessionUrlConfig::class.java).autowire()
this.mockMvc.perform(get("/")
.with { request ->
request.isRequestedSessionIdValid = false
request.requestedSessionId = "id"
request
})
.andExpect(status().isFound)
.andExpect(redirectedUrl("/invalid"))
}
@EnableWebSecurity
open class InvalidSessionUrlConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
sessionManagement {
invalidSessionUrl = "/invalid"
}
}
}
}
@Test
fun `session management when invalid session strategy then strategy used`() {
this.spring.register(InvalidSessionStrategyConfig::class.java).autowire()
this.mockMvc.perform(get("/")
.with { request ->
request.isRequestedSessionIdValid = false
request.requestedSessionId = "id"
request
})
.andExpect(status().isFound)
.andExpect(redirectedUrl("/invalid"))
}
@EnableWebSecurity
open class InvalidSessionStrategyConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
sessionManagement {
invalidSessionStrategy = SimpleRedirectInvalidSessionStrategy("/invalid")
}
}
}
}
@Test
fun `session management when session authentication error url then redirected to url`() {
this.spring.register(SessionAuthenticationErrorUrlConfig::class.java).autowire()
val session = mock(MockHttpSession::class.java)
`when`(session.changeSessionId()).thenThrow(SessionAuthenticationException::class.java)
this.mockMvc.perform(get("/")
.with(authentication(mock(Authentication::class.java)))
.session(session))
.andExpect(status().isFound)
.andExpect(redirectedUrl("/session-auth-error"))
}
@EnableWebSecurity
open class SessionAuthenticationErrorUrlConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
sessionManagement {
sessionAuthenticationErrorUrl = "/session-auth-error"
}
}
}
}
@Test
fun `session management when session authentication failure handler then handler used`() {
this.spring.register(SessionAuthenticationFailureHandlerConfig::class.java).autowire()
val session = mock(MockHttpSession::class.java)
`when`(session.changeSessionId()).thenThrow(SessionAuthenticationException::class.java)
this.mockMvc.perform(get("/")
.with(authentication(mock(Authentication::class.java)))
.session(session))
.andExpect(status().isFound)
.andExpect(redirectedUrl("/session-auth-error"))
}
@EnableWebSecurity
open class SessionAuthenticationFailureHandlerConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
sessionManagement {
sessionAuthenticationFailureHandler = SimpleUrlAuthenticationFailureHandler("/session-auth-error")
}
}
}
}
@Test
fun `session management when stateless policy then does not store session`() {
this.spring.register(StatelessSessionManagementConfig::class.java).autowire()
val result = this.mockMvc.perform(get("/"))
.andReturn()
assertThat(result.request.getSession(false)).isNull()
}
@EnableWebSecurity
open class StatelessSessionManagementConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
sessionManagement {
sessionCreationPolicy = SessionCreationPolicy.STATELESS
}
}
}
}
@Test
fun `session management when session authentication strategy then strategy used`() {
this.spring.register(SessionAuthenticationStrategyConfig::class.java).autowire()
this.mockMvc.perform(get("/")
.with(authentication(mock(Authentication::class.java)))
.session(mock(MockHttpSession::class.java)))
verify(this.spring.getContext().getBean(SessionAuthenticationStrategy::class.java))
.onAuthentication(any(Authentication::class.java),
any(HttpServletRequest::class.java), any(HttpServletResponse::class.java))
}
@EnableWebSecurity
open class SessionAuthenticationStrategyConfig : WebSecurityConfigurerAdapter() {
var mockSessionAuthenticationStrategy: SessionAuthenticationStrategy = mock(SessionAuthenticationStrategy::class.java)
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
sessionManagement {
sessionAuthenticationStrategy = mockSessionAuthenticationStrategy
}
}
}
@Bean
open fun sessionAuthenticationStrategy(): SessionAuthenticationStrategy {
return this.mockSessionAuthenticationStrategy
}
}
}

View File

@ -0,0 +1,221 @@
/*
* 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.servlet
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.mock
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.core.io.ClassPathResource
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
/**
* Tests for [X509Dsl]
*
* @author Eleftheria Stein
*/
class X509DslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `x509 when configured with defaults then user authenticated`() {
this.spring.register(X509Config::class.java).autowire()
val certificate = loadCert<X509Certificate>("rod.cer")
this.mockMvc.perform(get("/")
.with(x509(certificate)))
.andExpect(authenticated().withUsername("rod"))
}
@EnableWebSecurity
open class X509Config : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
x509 { }
}
}
@Bean
override fun userDetailsService(): UserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("rod")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(userDetails)
}
}
@Test
fun `x509 when configured with regex then user authenticated`() {
this.spring.register(X509RegexConfig::class.java).autowire()
val certificate = loadCert<X509Certificate>("rodatexampledotcom.cer")
this.mockMvc.perform(get("/")
.with(x509(certificate)))
.andExpect(authenticated().withUsername("rod"))
}
@EnableWebSecurity
open class X509RegexConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
x509 {
subjectPrincipalRegex = "CN=(.*?)@example.com(?:,|$)"
}
}
}
@Bean
override fun userDetailsService(): UserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("rod")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(userDetails)
}
}
@Test
fun `x509 when user details service configured then user details service used`() {
this.spring.register(UserDetailsServiceConfig::class.java).autowire()
val certificate = loadCert<X509Certificate>("rod.cer")
this.mockMvc.perform(get("/")
.with(x509(certificate)))
.andExpect(authenticated().withUsername("rod"))
}
@EnableWebSecurity
open class UserDetailsServiceConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
val userDetails = User.withDefaultPasswordEncoder()
.username("rod")
.password("password")
.roles("USER")
.build()
val customUserDetailsService = InMemoryUserDetailsManager(userDetails)
http {
x509 {
userDetailsService = customUserDetailsService
}
}
}
@Bean
override fun userDetailsService(): UserDetailsService {
return mock(UserDetailsService::class.java)
}
}
@Test
fun `x509 when authentication user details service configured then custom user details service used`() {
this.spring.register(AuthenticationUserDetailsServiceConfig::class.java).autowire()
val certificate = loadCert<X509Certificate>("rod.cer")
this.mockMvc.perform(get("/")
.with(x509(certificate)))
.andExpect(authenticated().withUsername("rod"))
}
@EnableWebSecurity
open class AuthenticationUserDetailsServiceConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
val userDetails = User.withDefaultPasswordEncoder()
.username("rod")
.password("password")
.roles("USER")
.build()
val customUserDetailsService = InMemoryUserDetailsManager(userDetails)
val customSource = UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>()
customSource.setUserDetailsService(customUserDetailsService)
http {
x509 {
authenticationUserDetailsService = customSource
}
}
}
@Bean
override fun userDetailsService(): UserDetailsService {
return mock(UserDetailsService::class.java)
}
}
@Test
fun `x509 when configured with principal extractor then principal extractor used`() {
this.spring.register(X509PrincipalExtractorConfig::class.java).autowire()
val certificate = loadCert<X509Certificate>("rodatexampledotcom.cer")
this.mockMvc.perform(get("/")
.with(x509(certificate)))
.andExpect(authenticated().withUsername("rod"))
}
@EnableWebSecurity
open class X509PrincipalExtractorConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
val principalExtractor = SubjectDnX509PrincipalExtractor()
principalExtractor.setSubjectDnRegex("CN=(.*?)@example.com(?:,|$)")
http {
x509 {
x509PrincipalExtractor = principalExtractor
}
}
}
@Bean
override fun userDetailsService(): UserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("rod")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(userDetails)
}
}
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
}
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.servlet.headers
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpHeaders
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.config.test.SpringTestRule
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [CacheControlDsl]
*
* @author Eleftheria Stein
*/
class CacheControlDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `headers when cache control configured then cache control headers in response`() {
this.spring.register(CacheControlConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header { string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate") }
header { string(HttpHeaders.EXPIRES, "0") }
header { string(HttpHeaders.PRAGMA, "no-cache") }
}
}
@EnableWebSecurity
open class CacheControlConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
cacheControl { }
}
}
}
}
@Test
fun `headers when cache control disabled then no cache control headers in response`() {
this.spring.register(CacheControlDisabledConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header { doesNotExist(HttpHeaders.CACHE_CONTROL) }
header { doesNotExist(HttpHeaders.EXPIRES) }
header { doesNotExist(HttpHeaders.PRAGMA) }
}
}
@EnableWebSecurity
open class CacheControlDisabledConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
cacheControl {
disable()
}
}
}
}
}
}

View File

@ -0,0 +1,116 @@
/*
* 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.servlet.headers
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [ContentSecurityPolicyDsl]
*
* @author Eleftheria Stein
*/
class ContentSecurityPolicyDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `headers when content security policy configured then header in response`() {
this.spring.register(ContentSecurityPolicyConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, "default-src 'self'") }
}
}
@EnableWebSecurity
open class ContentSecurityPolicyConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
contentSecurityPolicy { }
}
}
}
}
@Test
fun `headers when content security policy configured with custom policy directives then custom directives in header`() {
this.spring.register(CustomPolicyDirectivesConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, "default-src 'self'; script-src trustedscripts.example.com") }
}
}
@EnableWebSecurity
open class CustomPolicyDirectivesConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
contentSecurityPolicy {
policyDirectives = "default-src 'self'; script-src trustedscripts.example.com"
}
}
}
}
}
@Test
fun `headers when report only content security policy report only header in response`() {
this.spring.register(ReportOnlyConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY_REPORT_ONLY, "default-src 'self'") }
}
}
@EnableWebSecurity
open class ReportOnlyConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
contentSecurityPolicy {
reportOnly = true
}
}
}
}
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.servlet.headers
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [ContentTypeOptionsDsl]
*
* @author Eleftheria Stein
*/
class ContentTypeOptionsDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `headers when content type options configured then X-Content-Type-Options header in response`() {
this.spring.register(ContentTypeOptionsConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header { string(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff") }
}
}
@EnableWebSecurity
open class ContentTypeOptionsConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
contentTypeOptions { }
}
}
}
}
@Test
fun `headers when content type options disabled then X-Content-Type-Options header not in response`() {
this.spring.register(ContentTypeOptionsDisabledConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header { doesNotExist(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS) }
}
}
@EnableWebSecurity
open class ContentTypeOptionsDisabledConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
contentTypeOptions {
disable()
}
}
}
}
}
}

View File

@ -0,0 +1,167 @@
/*
* 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.servlet.headers
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [FrameOptionsDsl]
*
* @author Eleftheria Stein
*/
class FrameOptionsDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `headers when frame options configured then frame options deny header`() {
this.spring.register(FrameOptionsConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name) }
}
}
@EnableWebSecurity
open class FrameOptionsConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
frameOptions { }
}
}
}
}
@Test
fun `headers when frame options deny configured then frame options deny header`() {
this.spring.register(FrameOptionsDenyConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name) }
}
}
@EnableWebSecurity
open class FrameOptionsDenyConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
frameOptions {
deny = true
}
}
}
}
}
@Test
fun `headers when frame options same origin configured then frame options same origin header`() {
this.spring.register(FrameOptionsSameOriginConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN.name) }
}
}
@EnableWebSecurity
open class FrameOptionsSameOriginConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
frameOptions {
sameOrigin = true
}
}
}
}
}
@Test
fun `headers when frame options same origin and deny configured then frame options deny header`() {
this.spring.register(FrameOptionsSameOriginAndDenyConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, XFrameOptionsHeaderWriter.XFrameOptionsMode.DENY.name) }
}
}
@EnableWebSecurity
open class FrameOptionsSameOriginAndDenyConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
frameOptions {
sameOrigin = true
deny = true
}
}
}
}
}
@Test
fun `headers when frame options disabled then no frame options header in response`() {
this.spring.register(FrameOptionsDisabledConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { doesNotExist(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS) }
}
}
@EnableWebSecurity
open class FrameOptionsDisabledConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
frameOptions {
disable()
}
}
}
}
}
}

View File

@ -0,0 +1,230 @@
/*
* 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.servlet.headers
import org.assertj.core.api.Assertions
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.config.web.servlet.invoke
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [HttpPublicKeyPinningDsl]
*
* @author Eleftheria Stein
*/
class HttpPublicKeyPinningDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
private val HPKP_RO_HEADER_NAME = "Public-Key-Pins-Report-Only"
private val HPKP_HEADER_NAME = "Public-Key-Pins"
@Test
fun `headers when HPKP configured and no pin then no headers in response`() {
this.spring.register(HpkpNoPinConfig::class.java).autowire()
val result = this.mockMvc.get("/") {
secure = true
}.andReturn()
Assertions.assertThat(result.response.headerNames).isEmpty()
}
@EnableWebSecurity
open class HpkpNoPinConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
httpPublicKeyPinning { }
}
}
}
}
@Test
fun `headers when HPKP configured with pin then header in response`() {
this.spring.register(HpkpPinConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(HPKP_RO_HEADER_NAME, "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"") }
}
}
@EnableWebSecurity
open class HpkpPinConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
httpPublicKeyPinning {
pins = mapOf(Pair("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"))
}
}
}
}
}
@Test
fun `headers when HPKP configured with maximum age then maximum age in header`() {
this.spring.register(HpkpMaxAgeConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(HPKP_RO_HEADER_NAME, "max-age=604800 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"") }
}
}
@EnableWebSecurity
open class HpkpMaxAgeConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
httpPublicKeyPinning {
pins = mapOf(Pair("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"))
maxAgeInSeconds = 604800
}
}
}
}
}
@Test
fun `headers when HPKP configured with report only false then public key pins header in response`() {
this.spring.register(HpkpReportOnlyFalseConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(HPKP_HEADER_NAME, "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"") }
}
}
@EnableWebSecurity
open class HpkpReportOnlyFalseConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
httpPublicKeyPinning {
pins = mapOf(Pair("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"))
reportOnly = false
}
}
}
}
}
@Test
fun `headers when HPKP configured with include subdomains then include subdomains in header`() {
this.spring.register(HpkpIncludeSubdomainsConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header {
string(HPKP_RO_HEADER_NAME,
"max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; includeSubDomains")
}
}
}
@EnableWebSecurity
open class HpkpIncludeSubdomainsConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
httpPublicKeyPinning {
pins = mapOf(Pair("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"))
includeSubDomains = true
}
}
}
}
}
@Test
fun `headers when HPKP configured with report uri then report uri in header`() {
this.spring.register(HpkpReportUriConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header {
string(HPKP_RO_HEADER_NAME,
"max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; report-uri=\"https://example.com\"")
}
}
}
@EnableWebSecurity
open class HpkpReportUriConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
httpPublicKeyPinning {
pins = mapOf(Pair("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"))
reportUri = "https://example.com"
}
}
}
}
}
@Test
fun `headers when HPKP disabled then no HPKP header in response`() {
this.spring.register(HpkpDisabledConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header {
doesNotExist(HPKP_RO_HEADER_NAME)
}
}
}
@EnableWebSecurity
open class HpkpDisabledConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
httpPublicKeyPinning {
disable()
}
}
}
}
}
}

View File

@ -0,0 +1,167 @@
/*
* 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.servlet.headers
import org.assertj.core.api.Assertions
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
import org.springframework.security.web.util.matcher.AntPathRequestMatcher
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [HttpStrictTransportSecurityDsl]
*
* @author Eleftheria Stein
*/
class HttpStrictTransportSecurityDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `headers when hsts configured then headers in response`() {
this.spring.register(HstsConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains") }
}
}
@EnableWebSecurity
open class HstsConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
httpStrictTransportSecurity { }
}
}
}
}
@Test
fun `headers when hsts configured with preload then preload in header`() {
this.spring.register(HstsPreloadConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains ; preload") }
}
}
@EnableWebSecurity
open class HstsPreloadConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
httpStrictTransportSecurity {
preload = true
}
}
}
}
}
@Test
fun `headers when hsts configured with max age then max age in header`() {
this.spring.register(HstsMaxAgeConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, "max-age=1 ; includeSubDomains") }
}
}
@EnableWebSecurity
open class HstsMaxAgeConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
httpStrictTransportSecurity {
maxAgeInSeconds = 1
}
}
}
}
}
@Test
fun `headers when hsts configured and does not match then hsts header not in response`() {
this.spring.register(HstsCustomMatcherConfig::class.java).autowire()
val result = this.mockMvc.get("/") {
secure = true
}.andReturn()
Assertions.assertThat(result.response.headerNames).isEmpty()
}
@EnableWebSecurity
open class HstsCustomMatcherConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
httpStrictTransportSecurity {
requestMatcher = AntPathRequestMatcher("/secure/**")
}
}
}
}
}
@Test
fun `request when hsts disabled then hsts header not in response`() {
this.spring.register(HstsDisabledConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { doesNotExist(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY) }
}
}
@EnableWebSecurity
open class HstsDisabledConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
httpStrictTransportSecurity {
disable()
}
}
}
}
}
}

View File

@ -0,0 +1,89 @@
/*
* 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.servlet.headers
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [ReferrerPolicyDsl]
*
* @author Eleftheria Stein
*/
class ReferrerPolicyDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `headers when referrer policy configured then header in response`() {
this.spring.register(ReferrerPolicyConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header { string("Referrer-Policy", ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER.policy) }
}
}
@EnableWebSecurity
open class ReferrerPolicyConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
referrerPolicy { }
}
}
}
}
@Test
fun `headers when referrer policy configured with custom policy then custom policy in header`() {
this.spring.register(ReferrerPolicyCustomPolicyConfig::class.java).autowire()
this.mockMvc.get("/")
.andExpect {
header { string("Referrer-Policy", ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN.policy) }
}
}
@EnableWebSecurity
open class ReferrerPolicyCustomPolicyConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
referrerPolicy {
policy = ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN
}
}
}
}
}
}

View File

@ -0,0 +1,140 @@
/*
* 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.servlet.headers
import org.junit.Rule
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [XssProtectionConfigDsl]
*
* @author Eleftheria Stein
*/
class XssProtectionConfigDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `headers when XSS protection configured then header in response`() {
this.spring.register(XssProtectionConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block") }
}
}
@EnableWebSecurity
open class XssProtectionConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
xssProtection { }
}
}
}
}
@Test
fun `headers when XSS protection with block false then mode is not block in header`() {
this.spring.register(XssProtectionBlockFalseConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1") }
}
}
@EnableWebSecurity
open class XssProtectionBlockFalseConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
xssProtection {
block = false
}
}
}
}
}
@Test
fun `headers when XSS protection disabled then X-XSS-Protection header is 0`() {
this.spring.register(XssProtectionDisabledConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { string(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0") }
}
}
@EnableWebSecurity
open class XssProtectionDisabledConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
defaultsDisabled = true
xssProtection {
xssProtectionEnabled = false
}
}
}
}
}
@Test
fun `headers when XSS protection disabled then X-XSS-Protection header not in response`() {
this.spring.register(XssProtectionDisabledFunctionConfig::class.java).autowire()
this.mockMvc.get("/") {
secure = true
}.andExpect {
header { doesNotExist(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION) }
}
}
@EnableWebSecurity
open class XssProtectionDisabledFunctionConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
headers {
xssProtection {
disable()
}
}
}
}
}
}

View File

@ -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.servlet.oauth2.client
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.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver
import org.springframework.security.oauth2.core.OAuth2AccessToken
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [AuthorizationCodeGrantDsl]
*
* @author Eleftheria Stein
*/
class AuthorizationCodeGrantDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `oauth2Client when custom authorization request repository then repository used`() {
this.spring.register(RequestRepositoryConfig::class.java, ClientConfig::class.java).autowire()
this.mockMvc.get("/callback") {
param("state", "test")
param("code", "123")
}
verify(RequestRepositoryConfig.REQUEST_REPOSITORY).loadAuthorizationRequest(any())
}
@EnableWebSecurity
open class RequestRepositoryConfig : WebSecurityConfigurerAdapter() {
companion object {
var REQUEST_REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = Mockito.mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
}
override fun configure(http: HttpSecurity) {
http {
oauth2Client {
authorizationCodeGrant {
authorizationRequestRepository = REQUEST_REPOSITORY
}
}
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
}
}
@Test
fun `oauth2Client when custom access token response client then client used`() {
this.spring.register(AuthorizedClientConfig::class.java, ClientConfig::class.java).autowire()
val authorizationRequest = getOAuth2AuthorizationRequest()
Mockito.`when`(AuthorizedClientConfig.REQUEST_REPOSITORY.loadAuthorizationRequest(any()))
.thenReturn(authorizationRequest)
Mockito.`when`(AuthorizedClientConfig.REQUEST_REPOSITORY.removeAuthorizationRequest(any(), any()))
.thenReturn(authorizationRequest)
Mockito.`when`(AuthorizedClientConfig.CLIENT.getTokenResponse(any()))
.thenReturn(OAuth2AccessTokenResponse
.withToken("token")
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.build())
this.mockMvc.get("/callback") {
param("state", "test")
param("code", "123")
}
verify(AuthorizedClientConfig.CLIENT).getTokenResponse(any())
}
@EnableWebSecurity
open class AuthorizedClientConfig : WebSecurityConfigurerAdapter() {
companion object {
var REQUEST_REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = Mockito.mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
var CLIENT: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> = Mockito.mock(OAuth2AccessTokenResponseClient::class.java) as OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
}
override fun configure(http: HttpSecurity) {
http {
oauth2Client {
authorizationCodeGrant {
authorizationRequestRepository = REQUEST_REPOSITORY
accessTokenResponseClient = CLIENT
}
}
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
}
}
@Test
fun `oauth2Client when custom authorization request resolver then request resolver used`() {
this.spring.register(RequestResolverConfig::class.java, ClientConfig::class.java).autowire()
this.mockMvc.get("/callback") {
param("state", "test")
param("code", "123")
}
verify(RequestResolverConfig.REQUEST_RESOLVER).resolve(any())
}
@EnableWebSecurity
open class RequestResolverConfig : WebSecurityConfigurerAdapter() {
companion object {
var REQUEST_RESOLVER: OAuth2AuthorizationRequestResolver = Mockito.mock(OAuth2AuthorizationRequestResolver::class.java)
}
override fun configure(http: HttpSecurity) {
http {
oauth2Client {
authorizationCodeGrant {
authorizationRequestResolver = REQUEST_RESOLVER
}
}
authorizeRequests {
authorize(anyRequest, authenticated)
}
}
}
}
@Configuration
open class ClientConfig {
@Bean
open fun clientRegistrationRepository(): ClientRegistrationRepository {
return InMemoryClientRegistrationRepository(
CommonOAuth2Provider.GOOGLE
.getBuilder("google")
.registrationId("registrationId")
.clientId("clientId")
.clientSecret("clientSecret")
.build()
)
}
}
private fun getOAuth2AuthorizationRequest(): OAuth2AuthorizationRequest? {
return OAuth2AuthorizationRequest
.authorizationCode()
.state("test")
.clientId("clientId")
.authorizationUri("https://test")
.redirectUri("http://localhost/callback")
.attributes(mapOf(Pair(OAuth2ParameterNames.REGISTRATION_ID, "registrationId")))
.build()
}
}

View File

@ -0,0 +1,144 @@
/*
* 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.servlet.oauth2.login
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.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [AuthorizationEndpointDsl]
*
* @author Eleftheria Stein
*/
class AuthorizationEndpointDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `oauth2Login when custom client registration repository then repository used`() {
this.spring.register(ResolverConfig::class.java, ClientConfig::class.java).autowire()
this.mockMvc.get("/oauth2/authorization/google")
verify(ResolverConfig.RESOLVER).resolve(any())
}
@EnableWebSecurity
open class ResolverConfig : WebSecurityConfigurerAdapter() {
companion object {
var RESOLVER: OAuth2AuthorizationRequestResolver = Mockito.mock(OAuth2AuthorizationRequestResolver::class.java)
}
override fun configure(http: HttpSecurity) {
http {
oauth2Login {
authorizationEndpoint {
authorizationRequestResolver = RESOLVER
}
}
}
}
}
@Test
fun `oauth2Login when custom authorization request repository then repository used`() {
this.spring.register(RequestRepoConfig::class.java, ClientConfig::class.java).autowire()
this.mockMvc.get("/oauth2/authorization/google")
verify(RequestRepoConfig.REPOSITORY).saveAuthorizationRequest(any(), any(), any())
}
@EnableWebSecurity
open class RequestRepoConfig : WebSecurityConfigurerAdapter() {
companion object {
var REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = Mockito.mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
}
override fun configure(http: HttpSecurity) {
http {
oauth2Login {
authorizationEndpoint {
authorizationRequestRepository = REPOSITORY
}
}
}
}
}
@Test
fun `oauth2Login when custom authorization uri repository then uri used`() {
this.spring.register(AuthorizationUriConfig::class.java, ClientConfig::class.java).autowire()
this.mockMvc.get("/connect/google")
verify(AuthorizationUriConfig.REPOSITORY).saveAuthorizationRequest(any(), any(), any())
}
@EnableWebSecurity
open class AuthorizationUriConfig : WebSecurityConfigurerAdapter() {
companion object {
var REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = Mockito.mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
}
override fun configure(http: HttpSecurity) {
http {
oauth2Login {
authorizationEndpoint {
authorizationRequestRepository = REPOSITORY
baseUri = "/connect"
}
}
}
}
}
@Configuration
open class ClientConfig {
@Bean
open fun clientRegistrationRepository(): ClientRegistrationRepository {
return InMemoryClientRegistrationRepository(
CommonOAuth2Provider.GOOGLE
.getBuilder("google").clientId("clientId").clientSecret("clientSecret")
.build()
)
}
}
}

View File

@ -0,0 +1,142 @@
/*
* 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.servlet.oauth2.login
import org.junit.Rule
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
import org.springframework.security.oauth2.core.OAuth2AccessToken
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
import org.springframework.security.oauth2.core.user.DefaultOAuth2User
import org.springframework.security.oauth2.core.user.OAuth2User
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import java.util.*
/**
* Tests for [RedirectionEndpointDsl]
*
* @author Eleftheria Stein
*/
class RedirectionEndpointDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `oauth2Login when redirection endpoint configured then custom redirection endpoing used`() {
this.spring.register(UserServiceConfig::class.java, ClientConfig::class.java).autowire()
val registrationId = "registrationId"
val attributes = HashMap<String, Any>()
attributes[OAuth2ParameterNames.REGISTRATION_ID] = registrationId
val authorizationRequest = OAuth2AuthorizationRequest
.authorizationCode()
.state("test")
.clientId("clientId")
.authorizationUri("https://test")
.redirectUri("http://localhost/callback")
.attributes(attributes)
.build()
Mockito.`when`(UserServiceConfig.REPOSITORY.removeAuthorizationRequest(ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(authorizationRequest)
Mockito.`when`(UserServiceConfig.CLIENT.getTokenResponse(ArgumentMatchers.any()))
.thenReturn(OAuth2AccessTokenResponse
.withToken("token")
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.build())
Mockito.`when`(UserServiceConfig.USER_SERVICE.loadUser(ArgumentMatchers.any()))
.thenReturn(DefaultOAuth2User(listOf(SimpleGrantedAuthority("ROLE_USER")), mapOf(Pair("user", "user")), "user"))
this.mockMvc.get("/callback") {
param("code", "auth-code")
param("state", "test")
}.andExpect {
redirectedUrl("/")
}
}
@EnableWebSecurity
open class UserServiceConfig : WebSecurityConfigurerAdapter() {
companion object {
var USER_SERVICE: OAuth2UserService<OAuth2UserRequest, OAuth2User> = mock(OAuth2UserService::class.java) as OAuth2UserService<OAuth2UserRequest, OAuth2User>
var CLIENT: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> = mock(OAuth2AccessTokenResponseClient::class.java) as OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
var REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
}
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2Login {
userInfoEndpoint {
userService = USER_SERVICE
}
tokenEndpoint {
accessTokenResponseClient = CLIENT
}
authorizationEndpoint {
authorizationRequestRepository = REPOSITORY
}
redirectionEndpoint {
baseUri = "/callback"
}
}
}
}
}
@Configuration
open class ClientConfig {
@Bean
open fun clientRegistrationRepository(): ClientRegistrationRepository {
return InMemoryClientRegistrationRepository(
CommonOAuth2Provider.GOOGLE
.getBuilder("google")
.registrationId("registrationId")
.clientId("clientId")
.clientSecret("clientSecret")
.build()
)
}
}
}

View File

@ -0,0 +1,128 @@
/*
* 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.servlet.oauth2.login
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.mock
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
import org.springframework.security.oauth2.core.OAuth2AccessToken
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import java.util.*
/**
* Tests for [TokenEndpointDsl]
*
* @author Eleftheria Stein
*/
class TokenEndpointDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `oauth2Login when custom access token response client then client used`() {
this.spring.register(TokenConfig::class.java, ClientConfig::class.java).autowire()
val registrationId = "registrationId"
val attributes = HashMap<String, Any>()
attributes[OAuth2ParameterNames.REGISTRATION_ID] = registrationId
val authorizationRequest = OAuth2AuthorizationRequest
.authorizationCode()
.state("test")
.clientId("clientId")
.authorizationUri("https://test")
.redirectUri("http://localhost/login/oauth2/code/google")
.attributes(attributes)
.build()
`when`(TokenConfig.REPOSITORY.removeAuthorizationRequest(any(), any()))
.thenReturn(authorizationRequest)
`when`(TokenConfig.CLIENT.getTokenResponse(any())).thenReturn(OAuth2AccessTokenResponse
.withToken("token")
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.build())
this.mockMvc.get("/login/oauth2/code/google") {
param("code", "auth-code")
param("state", "test")
}
Mockito.verify(TokenConfig.CLIENT).getTokenResponse(any())
}
@EnableWebSecurity
open class TokenConfig : WebSecurityConfigurerAdapter() {
companion object {
var CLIENT: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> = mock(OAuth2AccessTokenResponseClient::class.java) as OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
var REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
}
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2Login {
tokenEndpoint {
accessTokenResponseClient = CLIENT
}
authorizationEndpoint {
authorizationRequestRepository = REPOSITORY
}
}
}
}
}
@Configuration
open class ClientConfig {
@Bean
open fun clientRegistrationRepository(): ClientRegistrationRepository {
return InMemoryClientRegistrationRepository(
CommonOAuth2Provider.GOOGLE
.getBuilder("google")
.registrationId("registrationId")
.clientId("clientId")
.clientSecret("clientSecret")
.build()
)
}
}
}

View File

@ -0,0 +1,139 @@
/*
* 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.servlet.oauth2.login
import org.junit.Rule
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository
import org.springframework.security.oauth2.core.OAuth2AccessToken
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames
import org.springframework.security.oauth2.core.user.DefaultOAuth2User
import org.springframework.security.oauth2.core.user.OAuth2User
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import java.util.*
/**
* Tests for [UserInfoEndpointDsl]
*
* @author Eleftheria Stein
*/
class UserInfoEndpointDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `oauth2Login when custom user service then user service used`() {
this.spring.register(UserServiceConfig::class.java, ClientConfig::class.java).autowire()
val registrationId = "registrationId"
val attributes = HashMap<String, Any>()
attributes[OAuth2ParameterNames.REGISTRATION_ID] = registrationId
val authorizationRequest = OAuth2AuthorizationRequest
.authorizationCode()
.state("test")
.clientId("clientId")
.authorizationUri("https://test")
.redirectUri("http://localhost/login/oauth2/code/google")
.attributes(attributes)
.build()
`when`(UserServiceConfig.REPOSITORY.removeAuthorizationRequest(any(), any()))
.thenReturn(authorizationRequest)
`when`(UserServiceConfig.CLIENT.getTokenResponse(any()))
.thenReturn(OAuth2AccessTokenResponse
.withToken("token")
.tokenType(OAuth2AccessToken.TokenType.BEARER)
.build())
`when`(UserServiceConfig.USER_SERVICE.loadUser(any()))
.thenReturn(DefaultOAuth2User(listOf(SimpleGrantedAuthority("ROLE_USER")), mapOf(Pair("user", "user")), "user"))
this.mockMvc.get("/login/oauth2/code/google") {
param("code", "auth-code")
param("state", "test")
}
Mockito.verify(UserServiceConfig.USER_SERVICE).loadUser(any())
}
@EnableWebSecurity
open class UserServiceConfig : WebSecurityConfigurerAdapter() {
companion object {
var USER_SERVICE: OAuth2UserService<OAuth2UserRequest, OAuth2User> = Mockito.mock(OAuth2UserService::class.java) as OAuth2UserService<OAuth2UserRequest, OAuth2User>
var CLIENT: OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> = Mockito.mock(OAuth2AccessTokenResponseClient::class.java) as OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest>
var REPOSITORY: AuthorizationRequestRepository<OAuth2AuthorizationRequest> = Mockito.mock(AuthorizationRequestRepository::class.java) as AuthorizationRequestRepository<OAuth2AuthorizationRequest>
}
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2Login {
userInfoEndpoint {
userService = USER_SERVICE
}
tokenEndpoint {
accessTokenResponseClient = CLIENT
}
authorizationEndpoint {
authorizationRequestRepository = REPOSITORY
}
}
}
}
}
@Configuration
open class ClientConfig {
@Bean
open fun clientRegistrationRepository(): ClientRegistrationRepository {
return InMemoryClientRegistrationRepository(
CommonOAuth2Provider.GOOGLE
.getBuilder("google")
.registrationId("registrationId")
.clientId("clientId")
.clientSecret("clientSecret")
.build()
)
}
}
}

View File

@ -0,0 +1,129 @@
/*
* 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.servlet.oauth2.resourceserver
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.core.convert.converter.Converter
import org.springframework.security.authentication.AbstractAuthenticationToken
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.jwt.JwtDecoder
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
/**
* Tests for [JwtDsl]
*
* @author Eleftheria Stein
*/
class JwtDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `JWT when custom JWT decoder then bean not required`() {
this.spring.register(CustomJwtDecoderConfig::class.java).autowire()
}
@EnableWebSecurity
open class CustomJwtDecoderConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
oauth2ResourceServer {
jwt {
jwtDecoder = mock(JwtDecoder::class.java)
}
}
}
}
}
@Test
fun `JWT when custom jwkSetUri then bean not required`() {
this.spring.register(CustomJwkSetUriConfig::class.java).autowire()
}
@EnableWebSecurity
open class CustomJwkSetUriConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
oauth2ResourceServer {
jwt {
jwkSetUri = "https://jwk-uri"
}
}
}
}
}
@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(
Jwt.withTokenValue("token")
.header("alg", "none")
.claim(IdTokenClaimNames.SUB, "user")
.build())
`when`(CustomJwtAuthenticationConverterConfig.CONVERTER.convert(any()))
.thenReturn(TestingAuthenticationToken("test", "this", "ROLE"))
this.mockMvc.get("/") {
header("Authorization", "Bearer token")
}
verify(CustomJwtAuthenticationConverterConfig.CONVERTER).convert(any())
}
@EnableWebSecurity
open class CustomJwtAuthenticationConverterConfig : WebSecurityConfigurerAdapter() {
companion object {
var CONVERTER: Converter<Jwt, out AbstractAuthenticationToken> = mock(Converter::class.java) as Converter<Jwt, out AbstractAuthenticationToken>
var DECODER: JwtDecoder = mock(JwtDecoder::class.java)
}
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
jwt {
jwtAuthenticationConverter = CONVERTER
}
}
}
}
@Bean
open fun jwtDecoder(): JwtDecoder {
return DECODER
}
}
}

View File

@ -0,0 +1,148 @@
/*
* 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.servlet.oauth2.resourceserver
import org.junit.Rule
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.http.*
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.core.Authentication
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal
import org.springframework.security.oauth2.jwt.JwtClaimNames
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.client.RestOperations
/**
* Tests for [OpaqueTokenDsl]
*
* @author Eleftheria Stein
*/
class OpaqueTokenDslTests {
@Rule
@JvmField
val spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `opaque token when defaults then uses introspection`() {
this.spring.register(DefaultOpaqueConfig::class.java, AuthenticationController::class.java).autowire()
val headers = HttpHeaders()
headers.contentType = MediaType.APPLICATION_JSON
val entity = ResponseEntity("{\n" +
" \"active\" : true,\n" +
" \"sub\": \"test-subject\",\n" +
" \"scope\": \"message:read\",\n" +
" \"exp\": 4683883211\n" +
"}", headers, HttpStatus.OK)
`when`(DefaultOpaqueConfig.REST.exchange(any(RequestEntity::class.java), eq(String::class.java)))
.thenReturn(entity)
this.mockMvc.get("/authenticated") {
header("Authorization", "Bearer token")
}.andExpect {
status { isOk }
content { string("test-subject") }
}
}
@EnableWebSecurity
open class DefaultOpaqueConfig : WebSecurityConfigurerAdapter() {
companion object {
var REST: RestOperations = mock(RestOperations::class.java)
}
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
opaqueToken { }
}
}
}
@Bean
open fun rest(): RestOperations {
return REST
}
@Bean
open fun tokenIntrospectionClient(): NimbusOpaqueTokenIntrospector {
return NimbusOpaqueTokenIntrospector("https://example.org/introspect", REST)
}
}
@Test
fun `opaque token when custom introspector set then introspector used`() {
this.spring.register(CustomIntrospectorConfig::class.java, AuthenticationController::class.java).autowire()
`when`(CustomIntrospectorConfig.INTROSPECTOR.introspect(ArgumentMatchers.anyString()))
.thenReturn(DefaultOAuth2AuthenticatedPrincipal(mapOf(Pair(JwtClaimNames.SUB, "mock-subject")), emptyList()))
this.mockMvc.get("/authenticated") {
header("Authorization", "Bearer token")
}
verify(CustomIntrospectorConfig.INTROSPECTOR).introspect("token")
}
@EnableWebSecurity
open class CustomIntrospectorConfig : WebSecurityConfigurerAdapter() {
companion object {
var INTROSPECTOR: OpaqueTokenIntrospector = mock(OpaqueTokenIntrospector::class.java)
}
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
oauth2ResourceServer {
opaqueToken {
introspector = INTROSPECTOR
}
}
}
}
}
@RestController
class AuthenticationController {
@GetMapping("/authenticated")
fun authenticated(@AuthenticationPrincipal authentication: Authentication): String {
return authentication.name
}
}
}

View File

@ -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.servlet.session
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockHttpSession
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.core.session.SessionInformation
import org.springframework.security.core.session.SessionRegistry
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import java.util.*
/**
* Tests for [SessionConcurrencyDsl]
*
* @author Eleftheria Stein
*/
class SessionConcurrencyDslTests {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `session concurrency when maximum sessions then no more sessions allowed`() {
this.spring.register(MaximumSessionsConfig::class.java, UserDetailsConfig::class.java).autowire()
this.mockMvc.perform(post("/login")
.with(csrf())
.param("username", "user")
.param("password", "password"))
this.mockMvc.perform(post("/login")
.with(csrf())
.param("username", "user")
.param("password", "password"))
.andExpect(status().isFound)
.andExpect(redirectedUrl("/login?error"))
}
@EnableWebSecurity
open class MaximumSessionsConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
sessionManagement {
sessionConcurrency {
maximumSessions = 1
maxSessionsPreventsLogin = true
}
}
formLogin { }
}
}
}
@Test
fun `session concurrency when expired url then redirects to url`() {
this.spring.register(ExpiredUrlConfig::class.java).autowire()
val session = MockHttpSession()
val sessionInformation = SessionInformation("", session.id, Date(0))
sessionInformation.expireNow()
`when`(ExpiredUrlConfig.sessionRegistry.getSessionInformation(any())).thenReturn(sessionInformation)
this.mockMvc.perform(get("/").session(session))
.andExpect(redirectedUrl("/expired-session"))
}
@EnableWebSecurity
open class ExpiredUrlConfig : WebSecurityConfigurerAdapter() {
companion object {
val sessionRegistry: SessionRegistry = mock(SessionRegistry::class.java)
}
override fun configure(http: HttpSecurity) {
http {
sessionManagement {
sessionConcurrency {
maximumSessions = 1
expiredUrl = "/expired-session"
sessionRegistry = sessionRegistry()
}
}
}
}
@Bean
open fun sessionRegistry(): SessionRegistry {
return sessionRegistry
}
}
@Test
fun `session concurrency when expired session strategy then strategy used`() {
this.spring.register(ExpiredSessionStrategyConfig::class.java).autowire()
val session = MockHttpSession()
val sessionInformation = SessionInformation("", session.id, Date(0))
sessionInformation.expireNow()
`when`(ExpiredSessionStrategyConfig.sessionRegistry.getSessionInformation(any())).thenReturn(sessionInformation)
this.mockMvc.perform(get("/").session(session))
.andExpect(redirectedUrl("/expired-session"))
}
@EnableWebSecurity
open class ExpiredSessionStrategyConfig : WebSecurityConfigurerAdapter() {
companion object {
val sessionRegistry: SessionRegistry = mock(SessionRegistry::class.java)
}
override fun configure(http: HttpSecurity) {
http {
sessionManagement {
sessionConcurrency {
maximumSessions = 1
expiredSessionStrategy = SimpleRedirectSessionInformationExpiredStrategy("/expired-session")
sessionRegistry = sessionRegistry()
}
}
}
}
@Bean
open fun sessionRegistry(): SessionRegistry {
return sessionRegistry
}
}
@Configuration
open class UserDetailsConfig {
@Bean
open fun userDetailsService(): UserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(userDetails)
}
}
}

View File

@ -0,0 +1,195 @@
/*
* 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.servlet.session
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.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockHttpSession
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.config.test.SpringTestRule
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
/**
* Tests for [SessionFixationDsl]
*
* @author Eleftheria Stein
*/
class SessionFixationDslTest {
@Rule
@JvmField
var spring = SpringTestRule()
@Autowired
lateinit var mockMvc: MockMvc
@Test
fun `session fixation when strategy is new session then new session created and attributes are not preserved`() {
this.spring.register(NewSessionConfig::class.java, UserDetailsConfig::class.java).autowire()
val givenSession = MockHttpSession()
val givenSessionId = givenSession.id
givenSession.clearAttributes()
givenSession.setAttribute("name", "value")
val result = this.mockMvc.perform(MockMvcRequestBuilders.get("/")
.with(httpBasic("user", "password"))
.session(givenSession))
.andReturn()
val resultingSession = result.request.getSession(false)
assertThat(resultingSession).isNotEqualTo(givenSession)
assertThat(resultingSession!!.id).isNotEqualTo(givenSessionId)
assertThat(resultingSession.getAttribute("name")).isNull()
}
@EnableWebSecurity
open class NewSessionConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
sessionManagement {
sessionFixation {
newSession()
}
}
httpBasic { }
}
}
}
@Test
fun `session fixation when strategy is migrate session then new session created and attributes are preserved`() {
this.spring.register(MigrateSessionConfig::class.java, UserDetailsConfig::class.java).autowire()
val givenSession = MockHttpSession()
val givenSessionId = givenSession.id
givenSession.clearAttributes()
givenSession.setAttribute("name", "value")
val result = this.mockMvc.perform(MockMvcRequestBuilders.get("/")
.with(httpBasic("user", "password"))
.session(givenSession))
.andReturn()
val resultingSession = result.request.getSession(false)
assertThat(resultingSession).isNotEqualTo(givenSession)
assertThat(resultingSession!!.id).isNotEqualTo(givenSessionId)
assertThat(resultingSession.getAttribute("name")).isEqualTo("value")
}
@EnableWebSecurity
open class MigrateSessionConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
sessionManagement {
sessionFixation {
migrateSession()
}
}
httpBasic { }
}
}
}
@Test
fun `session fixation when strategy is change session id then session id changes and attributes preserved`() {
this.spring.register(ChangeSessionIdConfig::class.java, UserDetailsConfig::class.java).autowire()
val givenSession = MockHttpSession()
val givenSessionId = givenSession.id
givenSession.clearAttributes()
givenSession.setAttribute("name", "value")
val result = this.mockMvc.perform(MockMvcRequestBuilders.get("/")
.with(httpBasic("user", "password"))
.session(givenSession))
.andReturn()
val resultingSession = result.request.getSession(false)
assertThat(resultingSession).isEqualTo(givenSession)
assertThat(resultingSession!!.id).isNotEqualTo(givenSessionId)
assertThat(resultingSession.getAttribute("name")).isEqualTo("value")
}
@EnableWebSecurity
open class ChangeSessionIdConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
sessionManagement {
sessionFixation {
changeSessionId()
}
}
httpBasic { }
}
}
}
@Test
fun `session fixation when strategy is none then session does not change`() {
this.spring.register(NoneConfig::class.java, UserDetailsConfig::class.java).autowire()
val givenSession = MockHttpSession()
val givenSessionId = givenSession.id
givenSession.clearAttributes()
givenSession.setAttribute("name", "value")
val result = this.mockMvc.perform(MockMvcRequestBuilders.get("/")
.with(httpBasic("user", "password"))
.session(givenSession))
.andReturn()
val resultingSession = result.request.getSession(false)
assertThat(resultingSession).isEqualTo(givenSession)
assertThat(resultingSession!!.id).isEqualTo(givenSessionId)
assertThat(resultingSession.getAttribute("name")).isEqualTo("value")
}
@EnableWebSecurity
open class NoneConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
sessionManagement {
sessionFixation {
none()
}
}
httpBasic { }
}
}
}
@Configuration
open class UserDetailsConfig {
@Bean
open fun userDetailsService(): UserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(userDetails)
}
}
}

View File

@ -3,3 +3,4 @@ gaeVersion=1.9.76
springBootVersion=2.2.0.RELEASE
version=5.3.0.BUILD-SNAPSHOT
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
kotlinVersion=1.3.61

View File

@ -9,6 +9,9 @@ if (!project.hasProperty("springVersion")) {
if (!project.hasProperty("springDataVersion")) {
ext.springDataVersion = "Moore-SR+"
}
if (!project.hasProperty("kotlinVersion")) {
ext.kotlinVersion = "1.3.61"
}
ext.rsocketVersion = "1.+"
ext.openSamlVersion = "3.+"
@ -29,6 +32,7 @@ dependencies {
management platform("org.springframework:spring-framework-bom:$springVersion")
management platform("io.projectreactor:reactor-bom:$reactorVersion")
management platform("org.springframework.data:spring-data-releasetrain:$springDataVersion")
management platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")
constraints {
management "ch.qos.logback:logback-classic:1.+"
management "com.fasterxml.jackson.core:jackson-databind:2.+"

View File

@ -0,0 +1,31 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("io.spring.convention.spring-sample-boot")
kotlin("jvm")
kotlin("plugin.spring") version "1.3.61"
}
repositories {
mavenCentral()
}
dependencies {
implementation(project(":spring-security-core"))
implementation(project(":spring-security-config"))
implementation(project(":spring-security-web"))
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity5")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation(project(":spring-security-test"))
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright 2002-2019 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.samples
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
/**
* @author Eleftheria Stein
*/
@SpringBootApplication
class KotlinApplication
fun main(args: Array<String>) {
runApplication<KotlinApplication>(*args)
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2002-2019 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.samples.config
import org.springframework.context.annotation.Bean
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.web.servlet.invoke
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.provisioning.InMemoryUserDetailsManager
/**
* @author Eleftheria Stein
*/
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
authorizeRequests {
authorize("/css/**", permitAll)
authorize("/user/**", hasAuthority("ROLE_USER"))
}
formLogin {
loginPage = "/log-in"
}
}
}
@Bean
public override fun userDetailsService(): UserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(userDetails)
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2002-2019 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.samples.web
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
/**
* @author Eleftheria Stein
*/
@Controller
class MainController {
@GetMapping("/")
fun index(): String {
return "index"
}
@GetMapping("/user/index")
fun userIndex(): String {
return "user/index"
}
@GetMapping("/log-in")
fun login(): String {
return "login"
}
}

View File

@ -0,0 +1,6 @@
server:
port: 8080
spring:
thymeleaf:
cache: false

View File

@ -0,0 +1,8 @@
body {
font-family: sans;
font-size: 1em;
}
div.logout {
float: right;
}

View File

@ -0,0 +1,40 @@
<!--
~ Copyright 2002-2019 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.
-->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
<title>Hello Spring Security</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="/static/css/main.css" th:href="@{/css/main.css}" />
</head>
<body>
<div th:fragment="logout" class="logout" sec:authorize="isAuthenticated()">
Logged in user: <span sec:authentication="name"></span> |
Roles: <span sec:authentication="principal.authorities"></span>
<div>
<form action="#" th:action="@{/logout}" method="post">
<input type="submit" value="Logout" />
</form>
</div>
</div>
<h1>Hello Spring Security</h1>
<p>This is an unsecured page, but you can access the secured pages after authenticating.</p>
<ul>
<li>Go to the <a href="/user/index" th:href="@{/user/index}">secured pages</a></li>
</ul>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<title>Login page</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="/static/css/main.css" th:href="@{/css/main.css}" />
</head>
<body>
<h1>Login page</h1>
<p>Example user: user / password</p>
<form th:action="@{/log-in}" method="post">
<label for="username">Username</label>:
<input type="text" id="username" name="username" autofocus="autofocus" /> <br />
<label for="password">Password</label>:
<input type="password" id="password" name="password" /> <br />
<input type="submit" value="Log in" />
</form>
<p><a href="/" th:href="@{/}">Back to home page</a></p>
</body>
</html>

View File

@ -0,0 +1,29 @@
<!--
~ Copyright 2002-2019 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.
-->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<title>Hello Spring Security</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="/static/css/main.css" th:href="@{/css/main.css}" />
</head>
<body>
<div th:substituteby="index::logout"></div>
<h1>This is a secured page!</h1>
<p><a href="/" th:href="@{/}">Back to home page</a></p>
</body>
</html>

View File

@ -0,0 +1,85 @@
/*
* Copyright 2002-2019 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.samples
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.mock.web.MockHttpSession
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated
import org.springframework.test.context.junit4.SpringRunner
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
@RunWith(SpringRunner::class)
@SpringBootTest
@AutoConfigureMockMvc
class KotlinApplicationTests {
@Autowired
private lateinit var mockMvc: MockMvc
@Test
fun `index page is not protected`() {
this.mockMvc.get("/")
.andExpect {
status { isOk }
}
}
@Test
fun `protected page redirects to login`() {
val mvcResult = this.mockMvc.get("/user/index")
.andExpect { status { is3xxRedirection } }
.andReturn()
assertThat(mvcResult.response.redirectedUrl).endsWith("/log-in")
}
@Test
fun `valid user permitted to log in`() {
this.mockMvc.perform(formLogin("/log-in").user("user").password("password"))
.andExpect(authenticated())
}
@Test
fun `invalid user not permitted to log in`() {
this.mockMvc.perform(formLogin("/log-in").user("invalid").password("invalid"))
.andExpect(unauthenticated())
.andExpect(status().is3xxRedirection)
}
@Test
fun `logged in user can access protected page`() {
val mvcResult = this.mockMvc.perform(formLogin("/log-in").user("user").password("password"))
.andExpect(authenticated()).andReturn()
val httpSession = mvcResult.request.getSession(false) as MockHttpSession
this.mockMvc.get("/user/index") {
session = httpSession
}.andExpect {
status { isOk }
}
}
}

View File

@ -2,7 +2,7 @@ rootProject.name = 'spring-security'
FileTree buildFiles = fileTree(rootDir) {
List excludes = gradle.startParameter.projectProperties.get("excludeProjects")?.split(",")
include '**/*.gradle'
include '**/*.gradle', '**/*.gradle.kts'
exclude 'build', '**/gradle', 'settings.gradle', 'buildSrc', '/build.gradle', '.*', 'out'
exclude '**/grails3'
if(excludes) {
@ -14,12 +14,18 @@ String rootDirPath = rootDir.absolutePath + File.separator
buildFiles.each { File buildFile ->
boolean isDefaultName = 'build.gradle'.equals(buildFile.name)
boolean isKotlin = buildFile.name.endsWith(".kts")
if(isDefaultName) {
String buildFilePath = buildFile.parentFile.absolutePath
String projectPath = buildFilePath.replace(rootDirPath, '').replace(File.separator, ':')
include projectPath
} else {
String projectName = buildFile.name.replace('.gradle', '');
String projectName
if (isKotlin) {
projectName = buildFile.name.replace('.gradle.kts', '')
} else {
projectName = buildFile.name.replace('.gradle', '')
}
String projectPath = ':' + projectName;
include projectPath
def project = findProject("${projectPath}")