From 31c525f3f4320971e490053283a0767dc4af2ec5 Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Fri, 27 Sep 2024 08:45:03 -0700 Subject: [PATCH 1/7] Fix locale for authfail to be consistant with g:message FormatTagLib.resolveLocale --- .../plugin/springsecurity/LoginController.groovy | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugin/grails-app/controllers/grails/plugin/springsecurity/LoginController.groovy b/plugin/grails-app/controllers/grails/plugin/springsecurity/LoginController.groovy index 1be4bdc64..535a728d1 100644 --- a/plugin/grails-app/controllers/grails/plugin/springsecurity/LoginController.groovy +++ b/plugin/grails-app/controllers/grails/plugin/springsecurity/LoginController.groovy @@ -15,6 +15,7 @@ package grails.plugin.springsecurity import grails.converters.JSON +import org.grails.web.servlet.mvc.GrailsWebRequest import org.springframework.context.MessageSource import org.springframework.security.access.annotation.Secured import org.springframework.security.authentication.AccountExpiredException @@ -104,18 +105,19 @@ class LoginController { String msg = '' def exception = session[WebAttributes.AUTHENTICATION_EXCEPTION] if (exception) { + def locale = GrailsWebRequest.lookup().getLocale() ?: Locale.getDefault() if (exception instanceof AccountExpiredException) { - msg = messageSource.getMessage('springSecurity.errors.login.expired', null, "Account Expired", request.locale) + msg = messageSource.getMessage('springSecurity.errors.login.expired', null, "Account Expired", locale) } else if (exception instanceof CredentialsExpiredException) { - msg = messageSource.getMessage('springSecurity.errors.login.passwordExpired', null, "Password Expired", request.locale) + msg = messageSource.getMessage('springSecurity.errors.login.passwordExpired', null, "Password Expired", locale) } else if (exception instanceof DisabledException) { - msg = messageSource.getMessage('springSecurity.errors.login.disabled', null, "Account Disabled", request.locale) + msg = messageSource.getMessage('springSecurity.errors.login.disabled', null, "Account Disabled", locale) } else if (exception instanceof LockedException) { - msg = messageSource.getMessage('springSecurity.errors.login.locked', null, "Account Locked", request.locale) + msg = messageSource.getMessage('springSecurity.errors.login.locked', null, "Account Locked", locale) } else if (exception instanceof SessionAuthenticationException) { - msg = messageSource.getMessage('springSecurity.errors.login.max.sessions.exceeded', null, "Sorry, you have exceeded your maximum number of open sessions.", request.locale) + msg = messageSource.getMessage('springSecurity.errors.login.max.sessions.exceeded', null, "Sorry, you have exceeded your maximum number of open sessions.", locale) } else { - msg = messageSource.getMessage('springSecurity.errors.login.fail', null, "Authentication Failure", request.locale) + msg = messageSource.getMessage('springSecurity.errors.login.fail', null, "Authentication Failure", locale) } } From 6ac0d9986be1594fc6a47cfa9e1e46b37adaa01a Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Thu, 10 Oct 2024 23:54:22 -0700 Subject: [PATCH 2/7] Filter order is completely different in Spring Security 5 --- gradle/libs.versions.toml | 1 + plugin/build.gradle | 1 + ...ion.groovy => SecurityFilterPosition.java} | 106 ++++++++++++------ .../springsecurity/SpringSecurityUtils.groovy | 2 +- .../SpringSecurityUtilsSpec.groovy | 6 + .../config/http/SecurityFiltersMapper.groovy | 5 + 6 files changed, 86 insertions(+), 35 deletions(-) rename plugin/src/main/groovy/grails/plugin/springsecurity/{SecurityFilterPosition.groovy => SecurityFilterPosition.java} (57%) create mode 100644 plugin/src/test/groovy/org/springframework/security/config/http/SecurityFiltersMapper.groovy diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0dbdae2e1..97670006c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -77,6 +77,7 @@ spring-context-core = { module = 'org.springframework:spring-context', version.r spring-context-support = { module = 'org.springframework:spring-context-support', version.ref = 'spring' } spring-expression = { module = 'org.springframework:spring-expression', version.ref = 'spring' } spring-security-core = { module = 'org.springframework.security:spring-security-core', version.ref = 'spring-security' } +spring-security-config = { module = 'org.springframework.security:spring-security-config', version.ref = 'spring-security' } spring-security-crypto = { module = 'org.springframework.security:spring-security-crypto', version.ref = 'spring-security' } spring-security-web = { module = 'org.springframework.security:spring-security-web', version.ref = 'spring-security' } spring-test = { module = 'org.springframework:spring-test', version.ref = 'spring' } diff --git a/plugin/build.gradle b/plugin/build.gradle index f03a62481..ceaecf0fa 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -44,6 +44,7 @@ dependencies { testImplementation libs.bundles.grails.testing.support testImplementation libs.spock.core testImplementation libs.spring.test + testImplementation libs.spring.security.config testRuntimeOnly libs.slf4j.nop // Prevents warnings about missing slf4j implementation during tests } diff --git a/plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.groovy b/plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.java similarity index 57% rename from plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.groovy rename to plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.java index 543770d25..06f1a1879 100644 --- a/plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.groovy +++ b/plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package grails.plugin.springsecurity +package grails.plugin.springsecurity; /** * Stores the default order numbers of all Spring Security filters for use in configuration. @@ -23,63 +23,101 @@ * @author Burt Beckwith */ enum SecurityFilterPosition { - /** First */ + FIRST(Integer.MIN_VALUE), - /** HTTP/HTTPS channel filter */ + + DISABLE_ENCODE_URL_FILTER, + + FORCE_EAGER_SESSION_FILTER, + CHANNEL_FILTER, - /** Concurrent Sessions */ - CONCURRENT_SESSION_FILTER, - /** Populates the SecurityContextHolder */ + SECURITY_CONTEXT_FILTER, - /** Logout */ + + CONCURRENT_SESSION_FILTER, + + WEB_ASYNC_MANAGER_FILTER, + + HEADERS_FILTER, + + CORS_FILTER, + + SAML2_LOGOUT_REQUEST_FILTER, + + SAML2_LOGOUT_RESPONSE_FILTER, + + CSRF_FILTER, + + SAML2_LOGOUT_FILTER, + LOGOUT_FILTER, - /** x509 certs */ + + OAUTH2_AUTHORIZATION_REQUEST_FILTER, + + SAML2_AUTHENTICATION_REQUEST_FILTER, + X509_FILTER, - /** Pre-auth */ + PRE_AUTH_FILTER, - /** CAS */ + CAS_FILTER, - /** UsernamePasswordAuthenticationFilter */ + + OAUTH2_LOGIN_FILTER, + + SAML2_AUTHENTICATION_FILTER, + FORM_LOGIN_FILTER, - /** OpenID */ + OPENID_FILTER, - /** Not used, generates a dynamic login form */ + LOGIN_PAGE_FILTER, - /** Digest auth */ + + LOGOUT_PAGE_FILTER, + DIGEST_AUTH_FILTER, - /** Basic Auth */ + + BEARER_TOKEN_AUTH_FILTER, + BASIC_AUTH_FILTER, - /** saved request filter */ + REQUEST_CACHE_FILTER, - /** SecurityContextHolderAwareRequestFilter */ + SERVLET_API_SUPPORT_FILTER, - /** Remember-me cookie */ + + JAAS_API_SUPPORT_FILTER, + REMEMBER_ME_FILTER, - /** Anonymous auth */ + ANONYMOUS_FILTER, - /** SessionManagementFilter */ + + OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER, + + WELL_KNOWN_CHANGE_PASSWORD_REDIRECT_FILTER, + SESSION_MANAGEMENT_FILTER, - /** Spring FormContentFilter allows www-url-form-encoded content-types to provide params in PUT requests */ - FORM_CONTENT_FILTER, - /** ExceptionTranslationFilter */ + EXCEPTION_TRANSLATION_FILTER, - /** FilterSecurityInterceptor */ + FILTER_SECURITY_INTERCEPTOR, - /** Switch user */ + SWITCH_USER_FILTER, - /** Last */ - LAST(Integer.MAX_VALUE) - private static final int INTERVAL = 100 + LAST(Integer.MAX_VALUE); + + private static final int INTERVAL = 100; - /** The position in the chain. */ - final int order + private final int order; - private SecurityFilterPosition() { - order = ordinal() * INTERVAL + SecurityFilterPosition() { + this.order = ordinal() * INTERVAL; } - private SecurityFilterPosition(int filterOrder) { - order = filterOrder + SecurityFilterPosition(int order) { + this.order = order; } + + public int getOrder() { + return this.order; + } + } diff --git a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy index 2a79fc9db..9f1424898 100644 --- a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy +++ b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy @@ -762,7 +762,7 @@ final class SpringSecurityUtils { orderedNames[SecurityFilterPosition.SWITCH_USER_FILTER.order] = 'switchUserProcessingFilter' } - orderedNames[SecurityFilterPosition.FORM_CONTENT_FILTER.order] = 'formContentFilter' + orderedNames[SecurityFilterPosition.SWITCH_USER_FILTER.order+1] = 'formContentFilter' // add in filters contributed by secondary plugins orderedNames << SpringSecurityUtils.orderedFilters diff --git a/plugin/src/test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsSpec.groovy b/plugin/src/test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsSpec.groovy index e4f9f8ca7..92d10d631 100644 --- a/plugin/src/test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsSpec.groovy +++ b/plugin/src/test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsSpec.groovy @@ -17,6 +17,7 @@ package grails.plugin.springsecurity import grails.plugin.springsecurity.web.GrailsSecurityFilterChain import grails.plugin.springsecurity.web.SecurityRequestHolder import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl +import org.springframework.security.config.http.SecurityFiltersMapper import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.web.FilterChainProxy @@ -340,6 +341,11 @@ class SpringSecurityUtilsSpec extends AbstractUnitSpec { !SpringSecurityUtils.ifAnyGranted('ROLE_4') } + void 'SecurityFilterPosition order should match SecurityFilters'() { + expect: + SecurityFilterPosition.SWITCH_USER_FILTER.order == SecurityFiltersMapper.SWITCH_USER_FILTER.order + } + void 'private constructor'() { expect: SecurityTestUtils.testPrivateConstructor SpringSecurityUtils diff --git a/plugin/src/test/groovy/org/springframework/security/config/http/SecurityFiltersMapper.groovy b/plugin/src/test/groovy/org/springframework/security/config/http/SecurityFiltersMapper.groovy new file mode 100644 index 000000000..0c939e751 --- /dev/null +++ b/plugin/src/test/groovy/org/springframework/security/config/http/SecurityFiltersMapper.groovy @@ -0,0 +1,5 @@ +package org.springframework.security.config.http + +class SecurityFiltersMapper { + static final SWITCH_USER_FILTER = SecurityFilters.SWITCH_USER_FILTER +} \ No newline at end of file From 99e2d2ed8a9a8de05b96cbb3763146a64850ed82 Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Fri, 11 Oct 2024 00:16:23 -0700 Subject: [PATCH 3/7] Fix integration test --- .../SpringSecurityUtilsIntegrationSpec.groovy | 20 +++++++++---------- .../springsecurity/SpringSecurityUtils.groovy | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/integration-test-app/src/integration-test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsIntegrationSpec.groovy b/examples/integration-test-app/src/integration-test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsIntegrationSpec.groovy index 81fa0d968..03d550dcb 100644 --- a/examples/integration-test-app/src/integration-test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsIntegrationSpec.groovy +++ b/examples/integration-test-app/src/integration-test/groovy/grails/plugin/springsecurity/SpringSecurityUtilsIntegrationSpec.groovy @@ -87,15 +87,15 @@ class SpringSecurityUtilsIntegrationSpec extends AbstractIntegrationSpec { expect: 10 == map.size() map[Integer.MIN_VALUE + 10] instanceof SecurityRequestHolderFilter - map[300] instanceof SecurityContextPersistenceFilter - map[400] instanceof MutableLogoutFilter - map[800] instanceof GrailsUsernamePasswordAuthenticationFilter - map[1400] instanceof SecurityContextHolderAwareRequestFilter - map[1500] instanceof GrailsRememberMeAuthenticationFilter - map[1600] instanceof GrailsAnonymousAuthenticationFilter - map[1800] instanceof FormContentFilter - map[1900] instanceof ExceptionTranslationFilter - map[2000] instanceof FilterSecurityInterceptor + map[SecurityFilterPosition.SECURITY_CONTEXT_FILTER.order] instanceof SecurityContextPersistenceFilter + map[SecurityFilterPosition.LOGOUT_FILTER.order] instanceof MutableLogoutFilter + map[SecurityFilterPosition.FORM_LOGIN_FILTER.order] instanceof GrailsUsernamePasswordAuthenticationFilter + map[SecurityFilterPosition.SERVLET_API_SUPPORT_FILTER.order] instanceof SecurityContextHolderAwareRequestFilter + map[SecurityFilterPosition.REMEMBER_ME_FILTER.order] instanceof GrailsRememberMeAuthenticationFilter + map[SecurityFilterPosition.ANONYMOUS_FILTER.order] instanceof GrailsAnonymousAuthenticationFilter + map[SecurityFilterPosition.EXCEPTION_TRANSLATION_FILTER.order-10] instanceof FormContentFilter + map[SecurityFilterPosition.EXCEPTION_TRANSLATION_FILTER.order] instanceof ExceptionTranslationFilter + map[SecurityFilterPosition.FILTER_SECURITY_INTERCEPTOR.order] instanceof FilterSecurityInterceptor when: SpringSecurityUtils.clientRegisterFilter 'foo', SecurityFilterPosition.LOGOUT_FILTER @@ -123,7 +123,7 @@ class SpringSecurityUtilsIntegrationSpec extends AbstractIntegrationSpec { then: 11 == map.size() - map[410] instanceof DummyFilter + map[SecurityFilterPosition.LOGOUT_FILTER.order + 10] instanceof DummyFilter when: def filters = securityFilterChains[0].filters diff --git a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy index 9f1424898..a8d7e521b 100644 --- a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy +++ b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy @@ -762,7 +762,7 @@ final class SpringSecurityUtils { orderedNames[SecurityFilterPosition.SWITCH_USER_FILTER.order] = 'switchUserProcessingFilter' } - orderedNames[SecurityFilterPosition.SWITCH_USER_FILTER.order+1] = 'formContentFilter' + orderedNames[SecurityFilterPosition.EXCEPTION_TRANSLATION_FILTER.order-10] = 'formContentFilter' // add in filters contributed by secondary plugins orderedNames << SpringSecurityUtils.orderedFilters From cc4e5d2f393175ee59c71a83afa298334f58ac35 Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Fri, 11 Oct 2024 00:30:51 -0700 Subject: [PATCH 4/7] add sdkmanrc --- .sdkmanrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .sdkmanrc diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 000000000..e2a1d58cf --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1 @@ +java=11.0.24-librca From d5748a3f32ed27170d7ce659d84c19c2f4aff493 Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Fri, 11 Oct 2024 01:28:34 -0700 Subject: [PATCH 5/7] Java 17 --- .sdkmanrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.sdkmanrc b/.sdkmanrc index e2a1d58cf..215786fc3 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1 +1 @@ -java=11.0.24-librca +java=17.0.12-librca From 5ae99b3a14e5dbaf714df0014612612b200cb132 Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Fri, 11 Oct 2024 01:29:09 -0700 Subject: [PATCH 6/7] Spring Security 6 --- .../grails/plugin/springsecurity/SecurityFilterPosition.java | 4 +--- .../grails/plugin/springsecurity/SpringSecurityUtils.groovy | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.java b/plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.java index 06f1a1879..9676edd65 100644 --- a/plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.java +++ b/plugin/src/main/groovy/grails/plugin/springsecurity/SecurityFilterPosition.java @@ -22,7 +22,7 @@ * * @author Burt Beckwith */ -enum SecurityFilterPosition { +enum SecurityFilterPosition { FIRST(Integer.MIN_VALUE), @@ -68,8 +68,6 @@ enum SecurityFilterPosition { FORM_LOGIN_FILTER, - OPENID_FILTER, - LOGIN_PAGE_FILTER, LOGOUT_PAGE_FILTER, diff --git a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy index 7e4a1cbdd..e9eb851fe 100644 --- a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy +++ b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy @@ -787,7 +787,7 @@ final class SpringSecurityUtils { FilterRegistrationBean sitemesh3Filter = (FilterRegistrationBean) applicationContext.getBean('sitemesh3Secured') if (sitemesh3Filter) { allConfiguredFilters['sitemesh3Secured'] = sitemesh3Filter.filter - SpringSecurityUtils.configuredOrderedFilters[SecurityFilterPosition.FORM_CONTENT_FILTER.previous().order] = sitemesh3Filter.filter + SpringSecurityUtils.configuredOrderedFilters[SecurityFilterPosition.EXCEPTION_TRANSLATION_FILTER.order - 10] = sitemesh3Filter.filter } log.trace 'Ordered filters: {}', SpringSecurityUtils.configuredOrderedFilters From b0af3761b4a8816c8e964f015bf84b039805baed Mon Sep 17 00:00:00 2001 From: Scott Murphy Heiberg Date: Fri, 11 Oct 2024 02:11:12 -0700 Subject: [PATCH 7/7] Ensure sitemesh 3 is in correct place in filter chain --- .../SpringSecurityCoreGrailsPlugin.groovy | 8 ++++++++ .../springsecurity/SpringSecurityUtils.groovy | 16 ++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityCoreGrailsPlugin.groovy b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityCoreGrailsPlugin.groovy index fe6698786..eeb35c6c3 100644 --- a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityCoreGrailsPlugin.groovy +++ b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityCoreGrailsPlugin.groovy @@ -673,6 +673,14 @@ to default to 'Annotation'; setting value to 'Annotation' // build filters here to give dependent plugins a chance to register some SortedMap filterNames = ReflectionUtils.findFilterChainNames(conf) def securityFilterChains = applicationContext.securityFilterChains + + // if sitemesh 3 is installed, an additional sitemesh 3 filter will need to be registered + // as part of the security filter chain so that pages are decorated using the security context + def sitemesh3Filter = applicationContext.getBean('sitemesh3Secured') + if (sitemesh3Filter) { + filterNames[SecurityFilterPosition.EXCEPTION_TRANSLATION_FILTER.order - 10] = 'sitemesh3Secured' + } + SpringSecurityUtils.buildFilterChains filterNames, conf.filterChain.chainMap ?: [], securityFilterChains, applicationContext log.trace 'Filter chain: {}', securityFilterChains diff --git a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy index e9eb851fe..4edd9f627 100644 --- a/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy +++ b/plugin/src/main/groovy/grails/plugin/springsecurity/SpringSecurityUtils.groovy @@ -778,16 +778,12 @@ final class SpringSecurityUtils { def allConfiguredFilters = [:] filterNames.each { Integer order, String name -> - Filter filter = applicationContext.getBean(name, Filter) - allConfiguredFilters[name] = filter - SpringSecurityUtils.configuredOrderedFilters[order] = filter - } - // if sitemesh 3 is installed, an additional sitemesh 3 filter will need to be registered - // as part of the security filter chain so that pages are decorated using the security context - FilterRegistrationBean sitemesh3Filter = (FilterRegistrationBean) applicationContext.getBean('sitemesh3Secured') - if (sitemesh3Filter) { - allConfiguredFilters['sitemesh3Secured'] = sitemesh3Filter.filter - SpringSecurityUtils.configuredOrderedFilters[SecurityFilterPosition.EXCEPTION_TRANSLATION_FILTER.order - 10] = sitemesh3Filter.filter + def filter = applicationContext.getBean(name) + if (filter instanceof FilterRegistrationBean) { + filter = ((FilterRegistrationBean) filter).filter + } + allConfiguredFilters[name] = (Filter) filter + SpringSecurityUtils.configuredOrderedFilters[order] = (Filter) filter } log.trace 'Ordered filters: {}', SpringSecurityUtils.configuredOrderedFilters