
grails.plugin.springsecurity.rest.SpringSecurityRestGrailsPlugin.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spring-security-rest Show documentation
Show all versions of spring-security-rest Show documentation
Grails plugin to implement token-based, RESTful authentication using Spring Security
The newest version!
/* Copyright 2013-2025 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
*
* http://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 grails.plugin.springsecurity.rest
import com.nimbusds.jose.EncryptionMethod
import com.nimbusds.jose.JWEAlgorithm
import com.nimbusds.jose.JWSAlgorithm
import grails.core.GrailsApplication
import grails.plugin.springsecurity.BeanTypeResolver
import grails.plugin.springsecurity.SecurityFilterPosition
import grails.plugin.springsecurity.SpringSecurityUtils
import grails.plugin.springsecurity.rest.authentication.DefaultRestAuthenticationEventPublisher
import grails.plugin.springsecurity.rest.authentication.NullRestAuthenticationEventPublisher
import grails.plugin.springsecurity.rest.credentials.DefaultJsonPayloadCredentialsExtractor
import grails.plugin.springsecurity.rest.credentials.RequestParamsCredentialsExtractor
import grails.plugin.springsecurity.rest.error.DefaultCallbackErrorHandler
import grails.plugin.springsecurity.rest.oauth.DefaultOauthUserDetailsService
import grails.plugin.springsecurity.rest.token.bearer.BearerTokenAccessDeniedHandler
import grails.plugin.springsecurity.rest.token.bearer.BearerTokenAuthenticationEntryPoint
import grails.plugin.springsecurity.rest.token.bearer.BearerTokenAuthenticationFailureHandler
import grails.plugin.springsecurity.rest.token.bearer.BearerTokenReader
import grails.plugin.springsecurity.rest.token.generation.SecureRandomTokenGenerator
import grails.plugin.springsecurity.rest.token.generation.TokenGenerator
import grails.plugin.springsecurity.rest.token.generation.jwt.AbstractJwtTokenGenerator
import grails.plugin.springsecurity.rest.token.generation.jwt.CustomClaimProvider
import grails.plugin.springsecurity.rest.token.generation.jwt.DefaultRSAKeyProvider
import grails.plugin.springsecurity.rest.token.generation.jwt.EncryptedJwtTokenGenerator
import grails.plugin.springsecurity.rest.token.generation.jwt.FileRSAKeyProvider
import grails.plugin.springsecurity.rest.token.generation.jwt.IssuerClaimProvider
import grails.plugin.springsecurity.rest.token.generation.jwt.SignedJwtTokenGenerator
import grails.plugin.springsecurity.rest.token.reader.HttpHeaderTokenReader
import grails.plugin.springsecurity.rest.token.rendering.DefaultAccessTokenJsonRenderer
import grails.plugin.springsecurity.rest.token.storage.jwt.JwtTokenStorageService
import grails.plugins.Plugin
import groovy.util.logging.Slf4j
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.DelegatingPasswordEncoder
import org.springframework.security.crypto.password.LdapShaPasswordEncoder
import org.springframework.security.crypto.password.Md4PasswordEncoder
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder
import org.springframework.security.crypto.password.NoOpPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder
import org.springframework.security.crypto.password.StandardPasswordEncoder
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder
import org.springframework.security.web.access.AccessDeniedHandlerImpl
import org.springframework.security.web.access.ExceptionTranslationFilter
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint
import org.springframework.security.web.savedrequest.NullRequestCache
@Slf4j
class SpringSecurityRestGrailsPlugin extends Plugin {
// the version or versions of Grails the plugin is designed for
String grailsVersion = '7.0.0 > *'
List loadAfter = ['springSecurityCore']
List pluginExcludes = [
'grails-app/views/**'
]
String title = 'Spring Security REST Plugin'
String author = 'Alvaro Sanchez-Mariscal'
String authorEmail = ''
String description = 'Implements authentication for REST APIs based on Spring Security. It uses a token-based workflow'
def profiles = ['web']
// URL to the plugin's documentation
String documentation = 'https://grails.github.io/grails-spring-security-core'
// Extra (optional) plugin metadata
String license = 'APACHE'
def organization = [name: 'Grails', url: 'https://www.grails.org']
def issueManagement = [system: 'GitHub', url: 'https://github.com/grails/grails-spring-security-core/issues']
def scm = [ url: 'https://github.com/grails/grails-spring-security-core']
GrailsApplication grailsApplication
Closure doWithSpring() { {->
if (!springSecurityPluginsAreActive()){
return
}
def conf = SpringSecurityUtils.securityConfig
SpringSecurityUtils.loadSecondaryConfig 'DefaultRestSecurityConfig'
conf = SpringSecurityUtils.securityConfig
boolean printStatusMessages = (conf.printStatusMessages instanceof Boolean) ? conf.printStatusMessages : true
if (printStatusMessages) {
println "\nConfiguring Spring Security REST ${plugin.version}..."
}
///*
SpringSecurityUtils.registerProvider 'restAuthenticationProvider'
/* restAuthenticationFilter */
if(conf.rest.login.active) {
SpringSecurityUtils.registerFilter 'restAuthenticationFilter', SecurityFilterPosition.FORM_LOGIN_FILTER.order + 1
restAuthenticationFilterRequestMatcher(SpringSecurityRestFilterRequestMatcher, conf.rest.login.endpointUrl)
restAuthenticationFilter(RestAuthenticationFilter) {
authenticationManager = ref('authenticationManager')
authenticationSuccessHandler = ref('restAuthenticationSuccessHandler')
authenticationFailureHandler = ref('restAuthenticationFailureHandler')
authenticationDetailsSource = ref('authenticationDetailsSource')
credentialsExtractor = ref('credentialsExtractor')
endpointUrl = conf.rest.login.endpointUrl
tokenGenerator = ref('tokenGenerator')
tokenStorageService = ref('tokenStorageService')
authenticationEventPublisher = ref('authenticationEventPublisher')
requestMatcher = ref('restAuthenticationFilterRequestMatcher')
}
def paramsClosure = {
usernamePropertyName = conf.rest.login.usernamePropertyName // username
passwordPropertyName = conf.rest.login.passwordPropertyName // password
}
if (conf.rest.login.useRequestParamsCredentials) {
credentialsExtractor(RequestParamsCredentialsExtractor, paramsClosure)
} else if (conf.rest.login.useJsonCredentials) {
credentialsExtractor(DefaultJsonPayloadCredentialsExtractor, paramsClosure)
}
/* restLogoutFilter */
restLogoutFilterRequestMatcher(SpringSecurityRestFilterRequestMatcher, conf.rest.logout.endpointUrl)
restLogoutFilter(RestLogoutFilter) {
endpointUrl = conf.rest.logout.endpointUrl
headerName = conf.rest.token.validation.headerName
tokenStorageService = ref('tokenStorageService')
tokenReader = ref('tokenReader')
requestMatcher = ref('restLogoutFilterRequestMatcher')
}
}
restAuthenticationSuccessHandler(RestAuthenticationSuccessHandler) {
renderer = ref('accessTokenJsonRenderer')
}
accessTokenJsonRenderer(DefaultAccessTokenJsonRenderer) {
usernamePropertyName = conf.rest.token.rendering.usernamePropertyName
tokenPropertyName = conf.rest.token.rendering.tokenPropertyName
authoritiesPropertyName = conf.rest.token.rendering.authoritiesPropertyName
useBearerToken = conf.rest.token.validation.useBearerToken
}
if(conf.rest.token.validation.useBearerToken ) {
tokenReader(BearerTokenReader)
restAuthenticationFailureHandler(BearerTokenAuthenticationFailureHandler){
tokenReader = ref('tokenReader')
}
restAuthenticationEntryPoint(BearerTokenAuthenticationEntryPoint) {
tokenReader = ref('tokenReader')
}
restAccessDeniedHandler(BearerTokenAccessDeniedHandler) {
errorPage = null //403
}
} else {
restAuthenticationEntryPoint(Http403ForbiddenEntryPoint)
tokenReader(HttpHeaderTokenReader) {
headerName = conf.rest.token.validation.headerName
}
restAuthenticationFailureHandler(RestAuthenticationFailureHandler) {
statusCode = conf.rest.login.failureStatusCode?:HttpServletResponse.SC_UNAUTHORIZED
}
restAccessDeniedHandler(AccessDeniedHandlerImpl) {
errorPage = null //403
}
}
/* restTokenValidationFilter */
SpringSecurityUtils.registerFilter 'restTokenValidationFilter', SecurityFilterPosition.ANONYMOUS_FILTER.order + 1
SpringSecurityUtils.registerFilter 'restExceptionTranslationFilter', SecurityFilterPosition.EXCEPTION_TRANSLATION_FILTER.order - 5
restTokenValidationFilterRequestMatcher(SpringSecurityRestFilterRequestMatcher, conf.rest.token.validation.endpointUrl)
restTokenValidationFilter(RestTokenValidationFilter) {
headerName = conf.rest.token.validation.headerName
validationEndpointUrl = conf.rest.token.validation.endpointUrl
active = conf.rest.token.validation.active
tokenReader = ref('tokenReader')
enableAnonymousAccess = conf.rest.token.validation.enableAnonymousAccess
authenticationSuccessHandler = ref('restAuthenticationSuccessHandler')
authenticationFailureHandler = ref('restAuthenticationFailureHandler')
restAuthenticationProvider = ref('restAuthenticationProvider')
authenticationEventPublisher = ref('authenticationEventPublisher')
requestMatcher = ref('restTokenValidationFilterRequestMatcher')
}
restExceptionTranslationFilter(ExceptionTranslationFilter, ref('restAuthenticationEntryPoint'), ref('restRequestCache')) {
accessDeniedHandler = ref('restAccessDeniedHandler')
authenticationTrustResolver = ref('authenticationTrustResolver')
throwableAnalyzer = ref('throwableAnalyzer')
}
restRequestCache(NullRequestCache)
/* tokenGenerator */
tokenGenerator(SecureRandomTokenGenerator)
callbackErrorHandler(DefaultCallbackErrorHandler)
String jwtSecretValue = conf.rest.token.storage.jwt.secret
/* tokenStorageService - defaults to JWT */
jwtService(JwtService) {
jwtSecret = jwtSecretValue
}
tokenStorageService(JwtTokenStorageService) {
jwtService = ref('jwtService')
userDetailsService = ref('userDetailsService')
}
issuerClaimProvider(IssuerClaimProvider) {
issuerName = conf.rest.token.generation.jwt.issuer
}
if (conf.rest.token.storage.jwt.useEncryptedJwt) {
jwtService(JwtService) {
keyProvider = ref('keyProvider')
}
tokenGenerator(EncryptedJwtTokenGenerator) {
jwtTokenStorageService = ref('tokenStorageService')
keyProvider = ref('keyProvider')
defaultExpiration = conf.rest.token.storage.jwt.expiration
defaultRefreshExpiration = conf.rest.token.storage.jwt.refreshExpiration
jweAlgorithm = JWEAlgorithm.parse(conf.rest.token.generation.jwt.jweAlgorithm)
encryptionMethod = EncryptionMethod.parse(conf.rest.token.generation.jwt.encryptionMethod)
}
if (conf.rest.token.storage.jwt.privateKeyPath instanceof CharSequence &&
conf.rest.token.storage.jwt.publicKeyPath instanceof CharSequence) {
keyProvider(FileRSAKeyProvider) {
privateKeyPath = conf.rest.token.storage.jwt.privateKeyPath
publicKeyPath = conf.rest.token.storage.jwt.publicKeyPath
}
} else {
keyProvider(DefaultRSAKeyProvider)
}
} else if (conf.rest.token.storage.jwt.useSignedJwt) {
checkJwtSecret(jwtSecretValue)
tokenGenerator(SignedJwtTokenGenerator) {
jwtTokenStorageService = ref('tokenStorageService')
jwtSecret = jwtSecretValue
defaultExpiration = conf.rest.token.storage.jwt.expiration
defaultRefreshExpiration = conf.rest.token.storage.jwt.refreshExpiration
jwsAlgorithm = JWSAlgorithm.parse(conf.rest.token.generation.jwt.algorithm)
}
}
/* restAuthenticationProvider */
restAuthenticationProvider(RestAuthenticationProvider) {
tokenStorageService = ref('tokenStorageService')
useJwt = true
jwtService = ref('jwtService')
}
/* oauthUserDetailsService */
oauthUserDetailsService(DefaultOauthUserDetailsService) {
userDetailsService = ref('userDetailsService')
preAuthenticationChecks = ref('preAuthenticationChecks')
}
// SecurityEventListener
if (conf.useSecurityEventListener) {
restSecurityEventListener(RestSecurityEventListener)
authenticationEventPublisher(DefaultRestAuthenticationEventPublisher)
} else {
authenticationEventPublisher(NullRestAuthenticationEventPublisher)
}
String algorithm = conf.password.algorithm
Class beanTypeResolverClass = conf.beanTypeResolverClass ?: BeanTypeResolver
def beanTypeResolver = beanTypeResolverClass.newInstance(conf, grailsApplication)
passwordEncoder(beanTypeResolver.resolveType('passwordEncoder', DelegatingPasswordEncoder), algorithm, idToPasswordEncoder(conf))
if (printStatusMessages) {
println '... finished configuring Spring Security REST\n'
}
}}
@Override
void doWithApplicationContext() {
if (!springSecurityPluginsAreActive()){
return
}
def customClaimProvidersList = applicationContext.getBeanNamesForType(CustomClaimProvider).collect {
applicationContext.getBean(it, CustomClaimProvider)
}
log.debug 'customClaimProvidersList = {}', customClaimProvidersList
TokenGenerator tokenGenerator = applicationContext.getBean('tokenGenerator') as TokenGenerator
if (tokenGenerator instanceof AbstractJwtTokenGenerator) {
tokenGenerator.customClaimProviders = customClaimProvidersList
}
}
private void checkJwtSecret(String jwtSecretValue) {
if (!jwtSecretValue &&
!pluginManager.hasGrailsPlugin('springSecurityRestGorm') &&
!pluginManager.hasGrailsPlugin('springSecurityRestGrailsCache') &&
!pluginManager.hasGrailsPlugin('springSecurityRestRedis') &&
!pluginManager.hasGrailsPlugin('springSecurityRestMemcached')) {
throw new Exception('A JWT secret must be defined. Please provide a value for the config property: grails.plugin.springsecurity.rest.token.storage.jwt.secret')
}
}
Map idToPasswordEncoder(ConfigObject conf) {
final String ENCODING_ID_BCRYPT = 'bcrypt'
final String ENCODING_ID_LDAP = 'ldap'
final String ENCODING_ID_MD4 = 'MD4'
final String ENCODING_ID_MD5 = 'MD5'
final String ENCODING_ID_NOOP = 'noop'
final String ENCODING_ID_PBKDF2 = 'pbkdf2'
final String ENCODING_ID_SCRYPT = 'scrypt'
final String ENCODING_ID_ARGON2 = 'argon2'
final String ENCODING_ID_SHA1 = 'SHA-1'
final String ENCODING_IDSHA256 = 'SHA-256'
MessageDigestPasswordEncoder messageDigestPasswordEncoderMD5 = new MessageDigestPasswordEncoder(ENCODING_ID_MD5)
messageDigestPasswordEncoderMD5.encodeHashAsBase64 = conf.password.encodeHashAsBase64 // false
messageDigestPasswordEncoderMD5.iterations = conf.password.hash.iterations // 10000
MessageDigestPasswordEncoder messsageDigestPasswordEncoderSHA1 = new MessageDigestPasswordEncoder(ENCODING_ID_SHA1)
messsageDigestPasswordEncoderSHA1.encodeHashAsBase64 = conf.password.encodeHashAsBase64 // false
messsageDigestPasswordEncoderSHA1.iterations = conf.password.hash.iterations // 10000
MessageDigestPasswordEncoder messsageDigestPasswordEncoderSHA256 = new MessageDigestPasswordEncoder(ENCODING_IDSHA256)
messsageDigestPasswordEncoderSHA256.encodeHashAsBase64 = conf.password.encodeHashAsBase64 // false
messsageDigestPasswordEncoderSHA256.iterations = conf.password.hash.iterations // 10000
int strength = conf.password.bcrypt.logrounds
[(ENCODING_ID_BCRYPT): new BCryptPasswordEncoder(strength),
(ENCODING_ID_LDAP): new LdapShaPasswordEncoder(),
(ENCODING_ID_MD4): new Md4PasswordEncoder(),
(ENCODING_ID_MD5): messageDigestPasswordEncoderMD5,
(ENCODING_ID_NOOP): NoOpPasswordEncoder.getInstance(),
(ENCODING_ID_PBKDF2): Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8(),
(ENCODING_ID_SCRYPT): SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8(),
(ENCODING_ID_ARGON2): Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8(),
(ENCODING_ID_SHA1): messsageDigestPasswordEncoderSHA1,
(ENCODING_IDSHA256): messsageDigestPasswordEncoderSHA256,
'sha256': new StandardPasswordEncoder()]
}
private boolean springSecurityPluginsAreActive() {
def conf = SpringSecurityUtils.securityConfig
if (!conf || !conf.active) {
return false
}
SpringSecurityUtils.loadSecondaryConfig 'DefaultRestSecurityConfig'
conf = SpringSecurityUtils.securityConfig
if (!conf.rest.active) {
return false
}
return true
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy