Skip to content

Commit

Permalink
Idiomatic Kotlin DSL for configuring HTTP security
Browse files Browse the repository at this point in the history
Issue: gh-5558
  • Loading branch information
eleftherias committed Jan 7, 2020
1 parent e306482 commit 2df1099
Show file tree
Hide file tree
Showing 88 changed files with 9,773 additions and 2 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
10 changes: 10 additions & 0 deletions config/spring-security-config.gradle
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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'

Expand Down Expand Up @@ -84,4 +87,11 @@ rncToXsd {
xslFile = new File(rncDir, 'spring-security.xsl')
}

compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs = ["-Xjsr305=strict"]
}
}

build.dependsOn rncToXsd
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
}
}
}
}

Original file line number Diff line number Diff line change
@@ -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()
}
}
}
}
Loading

0 comments on commit 2df1099

Please sign in to comment.