I'm working with a Grails 2 application and a Spring Security Saml Grails Plugin 2.0.0.
Purely for testing purposes I host the app on a local Tomcat and initially everything was working fine. However after some time, my loadUserBySAML method stopped being called, and so I was unable to login with SSO. There was a period of time where it was inconsistent - sometimes it would use the method, many times not. Now it seems like it will not work at all.
I am getting the SAML token back from the Idp properly, with the correct information and everything. However, it just gives me my default login failure message and doesn't show anything happening with the token nor any error messages.
I had 'solved' this issue before, and it was resolved by following the answer to this, so I added these beans to my resources.groovy file:
emptyStorageFactory(EmptyStorageFactory)
contextProvider(SAMLContextProviderImpl) {
storageFactory = ref('emptyStorageFactory')
}
Everything started working properly; the method in my custom User Service was being called and users were logged in. Leading me to believe the issue was due to the app being hosted on localhost and solved by using the EmptyStorageFactory. However, after a few weeks the issue came back, loadUserBySAML no longer being called. Even when reverting all changes back to the time it was working, it would no longer work. I would greatly appreciate any knowledge on how to fix this, especially as I am a novice to SAML and SSO. Everything else I've tried had little to no effect and only broke things further before reverting them.
Here is the entirety of my resources file:
customPropertyEditorRegistrar(util.CustomPropertyEditorRegistrar)
saltSource(util.UserSaltSource)
{userPropertyToUse = application.config.grails.plugin.springsecurity.dao.reflectionSaltSourceProperty}
sessionRegistry(SessionRegistryImpl)
sessionAuthenticationStrategy(ConcurrentSessionControlStrategy, sessionRegistry) {
maximumSessions = -1
}
concurrentSessionFilter(ConcurrentSessionFilter){
sessionRegistry = sessionRegistry
expiredUrl = '/login/concurrentSession'
}
def conf = SpringSecurityUtils.securityConfig
if (!conf || !conf.active) { return }
SpringSecurityUtils.loadSecondaryConfig 'SamlSecurityConfig'
conf = SpringSecurityUtils.securityConfig
if (!conf.saml.active) { return }
// Due to Spring DSL limitations, need to import these beans as XML definitions
def beansFile = "classpath:security/springSecuritySamlBeans.xml"
log.debug "Importing beans from ${beansFile}..."
delegate.importBeans beansFile
xmlns context:"http://www.springframework.org/schema/context"
context.'annotation-config'()
context.'component-scan'('base-package': "org.springframework.security.saml")
SpringSecurityUtils.registerProvider 'samlAuthenticationProvider'
SpringSecurityUtils.registerLogoutHandler 'successLogoutHandler'
SpringSecurityUtils.registerLogoutHandler 'logoutHandler'
SpringSecurityUtils.registerFilter 'samlEntryPoint', SecurityFilterPosition.SECURITY_CONTEXT_FILTER.order + 1
SpringSecurityUtils.registerFilter 'metadataFilter', SecurityFilterPosition.SECURITY_CONTEXT_FILTER.order + 2
SpringSecurityUtils.registerFilter 'samlProcessingFilter', SecurityFilterPosition.SECURITY_CONTEXT_FILTER.order + 3
SpringSecurityUtils.registerFilter 'samlLogoutFilter', SecurityFilterPosition.SECURITY_CONTEXT_FILTER.order + 4
SpringSecurityUtils.registerFilter 'samlLogoutProcessingFilter', SecurityFilterPosition.SECURITY_CONTEXT_FILTER.order + 5
successRedirectHandler(SavedRequestAwareAuthenticationSuccessHandler) {
alwaysUseDefaultTargetUrl = conf.saml.alwaysUseAfterLoginUrl ?: false
defaultTargetUrl = conf.saml.afterLoginUrl
}
successLogoutHandler(SimpleUrlLogoutSuccessHandler) {
defaultTargetUrl = conf.saml.afterLogoutUrl
}
samlLogger(SAMLDefaultLogger)
keyManager(JKSKeyManager,
conf.saml.keyManager.storeFile, conf.saml.keyManager.storePass, conf.saml.keyManager.passwords, conf.saml.keyManager.defaultKey)
def idpSelectionPath = conf.saml.entryPoint.idpSelectionPath
samlEntryPoint(SAMLEntryPoint) {
filterProcessesUrl = conf.saml.loginFormUrl // '/saml/login'
// contextProvider = ref('contextProvider')
if (idpSelectionPath) {
idpSelectionPath = idpSelectionPath // '/index.gsp'
}
defaultProfileOptions = ref('webProfileOptions')
}
webProfileOptions(WebSSOProfileOptions) {
includeScoping = false
}
metadataFilter(MetadataDisplayFilter) {
filterProcessesUrl = conf.saml.metadata.url // '/saml/metadata'
}
metadataGenerator(MetadataGenerator)
log.debug "Defining bean metadata providers... "
def providerBeanName = "extendedMetadataDelegate"
def idpResource
def idpFile = conf.saml.metadata.idp.file
if(idpFile){
idpResource = new ClassPathResource(idpFile)
defaultIdpMetadata(ExtendedMetadataDelegate) { extMetaDataDelegateBean ->
idpMetadataProvider(FilesystemMetadataProvider) { bean ->
bean.constructorArgs = [idpResource.getFile()]
parserPool = ref('parserPool')
}
extMetaDataDelegateBean.constructorArgs = [ref('idpMetadataProvider')]
}
}
def spFile = conf.saml.metadata.sp.file
if (spFile) {
def spResource = new ClassPathResource(spFile)
spMetadata(ExtendedMetadataDelegate) { spMetadataBean ->
spMetadataProvider(FilesystemMetadataProvider) { spMetadataProviderBean ->
spMetadataProviderBean.constructorArgs = [spResource.getFile()]
parserPool = ref('parserPool')
}
def spDefaults = conf.saml.metadata.sp.defaults
spMetadataDefaults(ExtendedMetadata) { extMetadata ->
local = spDefaults.local
alias = spDefaults.alias
signingKey = spDefaults.signingKey
encryptionKey = spDefaults.encryptionKey
tlsKey = spDefaults.tlsKey
requireArtifactResolveSigned = spDefaults.requireArtifactResolveSigned
requireLogoutRequestSigned = spDefaults.requireLogoutRequestSigned
requireLogoutResponseSigned = spDefaults.requireLogoutResponseSigned
idpDiscoveryEnabled = spDefaults.idpDiscoveryEnabled
}
spMetadataBean.constructorArgs = [ref('spMetadataProvider'), ref('spMetadataDefaults')]
}
}
metadata(CachingMetadataManager,[ref('spMetadata'), ref('defaultIdpMetadata')]){
hostedSPName = conf.saml.metadata.sp?."alias"
defaultIDP = conf.saml.metadata.defaultIdp
}
userDetailsService(SamlUserService) {
grailsApplication = ref('grailsApplication')
authorityClassName = conf.authority.className
authorityJoinClassName = conf.userLookup.authorityJoinClassName
authorityNameField = conf.authority.nameField
samlAutoCreateActive = conf.saml.autoCreate.active
samlAutoAssignAuthorities = conf.saml.autoCreate.assignAuthorities
samlAutoCreateKey = conf.saml.autoCreate.key
samlUserAttributeMappings = conf.saml.userAttributeMappings
samlUserGroupAttribute = conf.saml.userGroupAttribute
samlUserGroupToRoleMapping = conf.saml.userGroupToRoleMapping
userDomainClassName = conf.userLookup.userDomainClassName
}
samlAuthenticationProvider(GrailsSAMLAuthenticationProvider) {
userDetails = ref('userDetailsService')
hokConsumer = ref('webSSOprofileConsumer')
}
emptyStorageFactory(EmptyStorageFactory)
contextProvider(SAMLContextProviderImpl) {
storageFactory = ref('emptyStorageFactory')
}
samlProcessingFilter(SAMLProcessingFilter) {
authenticationManager = ref('authenticationManager')
authenticationSuccessHandler = ref('successRedirectHandler')
sessionAuthenticationStrategy = ref('sessionFixationProtectionStrategy')
authenticationFailureHandler = ref('authenticationFailureHandler')
}
authenticationFailureHandler(AjaxAwareAuthenticationFailureHandler) {
redirectStrategy = ref('redirectStrategy')
defaultFailureUrl = conf.failureHandler.defaultFailureUrl //'/login/authfail?login_error=1'
useForward = conf.failureHandler.useForward // false
ajaxAuthenticationFailureUrl = conf.failureHandler.ajaxAuthFailUrl // '/login/authfail?ajax=true'
exceptionMappings = conf.failureHandler.exceptionMappings // [:]
}
redirectStrategy(DefaultRedirectStrategy) {
contextRelative = conf.redirectStrategy.contextRelative // false
}
sessionFixationProtectionStrategy(SessionFixationProtectionStrategy)
logoutHandler(SecurityContextLogoutHandler) {
invalidateHttpSession = true
}
samlLogoutFilter(SAMLLogoutFilter,
ref('successLogoutHandler'), ref('logoutHandler'), ref('logoutHandler'))
samlLogoutProcessingFilter(SAMLLogoutProcessingFilter,
ref('successLogoutHandler'), ref('logoutHandler'))
webSSOprofileConsumer(WebSSOProfileConsumerImpl){
responseSkew = conf.saml.responseSkew
}
webSSOprofile(WebSSOProfileImpl)
ecpprofile(WebSSOProfileECPImpl)
logoutprofile(SingleLogoutProfileImpl)
postBinding(HTTPPostBinding, ref('parserPool'), ref('velocityEngine'))
redirectBinding(HTTPRedirectDeflateBinding, ref('parserPool'))
artifactBinding(HTTPArtifactBinding,
ref('parserPool'),
ref('velocityEngine'),
ref('artifactResolutionProfile')
)
artifactResolutionProfile(ArtifactResolutionProfileImpl, ref('httpClient')) {
processor = ref('soapProcessor')
}
httpClient(HttpClient)
soapProcessor(SAMLProcessorImpl, ref('soapBinding'))
soapBinding(HTTPSOAP11Binding, ref('parserPool'))
paosBinding(HTTPPAOS11Binding, ref('parserPool'))
bootStrap(SAMLBootstrap)
velocityEngine(VelocityFactory) { bean ->
bean.factoryMethod = "getEngine"
}
parserPool(BasicParserPool)
securityTagLib(SamlTagLib) {
springSecurityService = ref('springSecurityService')
webExpressionHandler = ref('webExpressionHandler')
webInvocationPrivilegeEvaluator = ref('webInvocationPrivilegeEvaluator')
}
springSecurityService(SamlSecurityService) {
config = conf
authenticationTrustResolver = ref('authenticationTrustResolver')
grailsApplication = ref('grailsApplication')
passwordEncoder = ref('passwordEncoder')
objectDefinitionSource = ref('objectDefinitionSource')
userDetailsService = ref('userDetailsService')
userCache = ref('userCache')
}
and a snippet of my SamlSecurityConfig:
security {
saml {
userAttributeMappings = [:]
userGroupToRoleMapping = [:]
active = true
afterLoginUrl = '/'
afterLogoutUrl = '/'
loginFormUrl = '/saml/login'
userGroupAttribute = "memberOf"
responseSkew = 60
idpSelectionPath = '/'
autoCreate {
active = true
key = 'username'
assignAuthorities = true
}
...