2
votes

I have a project in Grails 2.2.3 using Groovy 2.0. I have it set up with Spring Security to use CAS for authentication and LDAP for user roles. When I run the app, everything works as it should: accessing /appcontext/ is allowed by anyone and anything under /appcontext/admin/ is secured by CAS and an admin role from LDAP. I am trying to use the newest versions of Grails and Groovy now. I installed GGTS 3.4.0.RELEASE and am using Grails 2.3.0 and Groovy 2.1. I created a new project, made a simple domain class and controller and added in the security settings.

Here is my output when running the app with GGTS 3.3.0.RELEASE, using Grails 2.2.3 and Groovy 2.0: (note the location of the "Server Running" message)

| Loading Grails 2.2.3
| Configuring classpath.
| Environment set to development.....
| Packaging Grails application.....
| Running Grails application

Configuring Spring Security Core ...
... finished configuring Spring Security Core

Configuring Spring Security CAS ...
... finished configuring Spring Security CAS

Configuring Spring Security LDAP ...
... finished configuring Spring Security LDAP

| Server running. Browse to http://localhost:8080/appcontext

Here is my output when running the app with GGTS 3.4.0.RELEASE, using Grails 2.3.0 and Groovy 2.1 (note the location of the "Server Running" message):

| Loading Grails 2.3.0
| Configuring classpath.
| Environment set to development.....
| Packaging Grails application.....
| Compiling 1 source files.....
| Running Grails application
| Server running. Browse to http://localhost:8080/appcontext
Configuring Spring Security Core ...
... finished configuring Spring Security Core
Configuring Spring Security LDAP ...
... finished configuring Spring Security LDAP
Error initializing the application: No bean named 'casAuthenticationProvider' is defined
    org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'casAuthenticationProvider' is defined
        at SpringSecurityCoreGrailsPlugin$_createBeanList_closure22.doCall(SpringSecurityCoreGrailsPlugin.groovy:686)
        at SpringSecurityCoreGrailsPlugin.createBeanList(SpringSecurityCoreGrailsPlugin.groovy:686)
        at SpringSecurityCoreGrailsPlugin$_closure4.doCall(SpringSecurityCoreGrailsPlugin.groovy:615)
        at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
        at java.util.concurrent.FutureTask.run(FutureTask.java:138)
        at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
        at java.lang.Thread.run(Thread.java:662)
| Error 2013-10-15 11:33:02,925 [localhost-startStop-1] ERROR context.GrailsContextLoader  - Error initializing the application: No bean named 'casAuthenticationProvider' is defined
Message: No bean named 'casAuthenticationProvider' is defined
   Line | Method
->> 686 | doCall         in SpringSecurityCoreGrailsPlugin$_createBeanList_closure22
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   615 | doCall         in SpringSecurityCoreGrailsPlugin$_closure4
|   303 | innerRun . . . in java.util.concurrent.FutureTask$Sync
|   138 | run            in java.util.concurrent.FutureTask
|   886 | runTask . . .  in java.util.concurrent.ThreadPoolExecutor$Worker
|   908 | run            in     ''
^   662 | run . . . . .  in java.lang.Thread
schema export unsuccessful
org.h2.jdbc.JdbcSQLException: Database is already closed (to disable automatic closing     at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-170]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:329)
    at org.h2.message.DbException.get(DbException.java:169)
    at org.h2.message.DbException.get(DbException.java:146)
    at org.h2.message.DbException.get(DbException.java:135)
    at org.h2.jdbc.JdbcConnection.checkClosed(JdbcConnection.java:1391)
    at org.h2.jdbc.JdbcConnection.checkClosed(JdbcConnection.java:1366)
    at org.h2.jdbc.JdbcConnection.getAutoCommit(JdbcConnection.java:424)
    at java.lang.Thread.run(Thread.java:662)
| Error 2013-10-15 11:33:03,071 [Thread-9] ERROR hbm2ddl.SchemaExport  - schema export unsuccessful
Message: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-170]
    Line | Method
->>  329 | getJdbcSQLException in org.h2.message.DbException
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    169 | get                 in     ''
|    146 | get . . . . . . . . in     ''
|    135 | get                 in     ''
|   1391 | checkClosed . . . . in org.h2.jdbc.JdbcConnection
|   1366 | checkClosed         in     ''
|    424 | getAutoCommit . . . in     ''
^    662 | run                 in java.lang.Thread
| Error Forked Grails VM exited with error

Here is my basic security setup:

conf/spring/resources.groovy

import org.apache.commons.lang.StringEscapeUtils

// Place your Spring DSL code here
beans = {
    // load ldap roles from spring security
    def ldapUrl = StringEscapeUtils.escapeJava('${ldap.defaultUrl}')
    def ldapUser = StringEscapeUtils.escapeJava('${ldap.username}')
    def ldapPassword = StringEscapeUtils.escapeJava('${ldap.password}')
    def ldapBase = StringEscapeUtils.escapeJava('${ldap.base}')
    def ldapRoleSearchBase = StringEscapeUtils.escapeJava('${ldap.roleSearchBase}')

    initialDirContextFactory(org.springframework.security.ldap.DefaultSpringSecurityContextSource, ldapUrl){
        userDn = ldapUser
        password = ldapPassword
    }

    ldapUserSearch(org.springframework.security.ldap.search.FilterBasedLdapUserSearch,
        ldapBase, 'sAMAccountName={0}', initialDirContextFactory){ }

    ldapAuthoritiesPopulator(org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator,
        initialDirContextFactory, ldapRoleSearchBase){
            groupRoleAttribute = 'cn'
            groupSearchFilter = 'member={0}'
            searchSubtree = true
            rolePrefix = 'ROLE_'
            convertToUpperCase = true
            ignorePartialResultException = true
        }

    userDetailsService(org.springframework.security.ldap.userdetails.LdapUserDetailsService,ldapUserSearch,ldapAuthoritiesPopulator){ }

}

conf/Config.groovy

def appName = grails.util.Metadata.current.getApplicationName()

environments {
    development {
        grails.logging.jul.usebridge = true

        host.ip = "12.34.56.78"
        host.port = "8080"
        host.securePort = "8080"
        ldap.username = "ldapUsername"
        ldap.password = "ldapPassword"
        ldap.base = "DC=foo,DC=company,DC=com"
        ldap.roleSearchBase = "OU=bar,DC=foo,DC=company,DC=com"
        ldap.defaultUrl = "ldap://123.45.67.89:389"
        ldap.urls = "ldap://123.45.67.89:389 ldap://123.45.67.89:389"

        cas.url = "https://sso.company.com/cas/"
        cas.loginUrl = "https://sso.company.com/cas/login"
        cas.logoutUrl = "https://sso.company.com/cas/logout"

        grails.plugins.springsecurity.cas.serviceUrl = 'http://${host.ip}:${host.securePort}/' + appName +'/j_spring_cas_security_check'
        grails.plugins.springsecurity.cas.proxyCallbackUrl = 'http://${host.ip}:${host.securePort}/' + appName +'/secure/receptor'
    }
    production {
        grails.logging.jul.usebridge = false
        grails.plugins.springsecurity.cas.serviceUrl = 'https://${host.ip}:${host.securePort}/' + appName +'/j_spring_cas_security_check'
        grails.plugins.springsecurity.cas.proxyCallbackUrl = 'https://${host.ip}:${host.securePort}/' + appName +'/secure/receptor'
    }
}

//spring security core config
grails.plugins.springsecurity.providerNames = ['casAuthenticationProvider']
grails.plugins.springsecurity.rejectIfNoRule = true
grails.plugins.springsecurity.securityConfigType = "InterceptUrlMap"
grails.plugins.springsecurity.interceptUrlMap = [
    '/js/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
    '/css/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
    '/images/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
    '/admin/login/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
    '/admin/logout/**': ['IS_AUTHENTICATED_ANONYMOUSLY'],
    '/admin/**': ['hasAnyRole("ROLE_ADMIN")'],
    '/**': ['IS_AUTHENTICATED_ANONYMOUSLY']
]

//cas config
grails.plugins.springsecurity.cas.loginUri = 'login'
grails.plugins.springsecurity.cas.serverUrlPrefix = '${cas.url}'
grails.plugins.springsecurity.cas.proxyReceptorUrl = '/secure/receptor'

conf/BuildConfig.groovy

compile ":spring-security-core:1.2.7.3"
compile ":spring-security-cas:1.0.5"
compile ":spring-security-ldap:1.0.6"

EDIT Using the advice of the accepted answer below, I was able to get Spring Security CAS to configure correctly, but my controllers were still unsecured. I figured that it had to do with that weird load order where the app server says it's running and THEN it loads Spring Security, LDAP, and CAS. A coworker suggested taking out my InterceptUrlMap and using @Secured annotations to see if it was the loading order (since InterceptUrlMap can't be updated after everything is up and running). I got rid of the rejectIfNoRule, securityConfigType, and interceptUrlMap settings and added a @Secured(['ROLE_ADMIN']) to the controller. The app now works as expected and that controller is secured.

So, there is still an issue with the order of events with Grails 2.3.0 and Spring Security, but this is a workaround.

Related question: https://stackguides.com/questions/19411102/grails-2-3-0-spring-security-ldap-and-cas-load-after-server-starts

2

2 Answers

3
votes

I see the same thing. Looks like the CAS plugin defaults from DefaultCasSecurityConfig.groovy are not merged properly under Grails 2.3.0. Possibly worth a JIRA. In the meantime, you may be able to move forward by adding the defaults to Config.groovy (overriding for your environment):

grails.plugins.springsecurity.cas.active = true
grails.plugins.springsecurity.cas.loginUri = null // must be set, e.g. '/login'
grails.plugins.springsecurity.cas.sendRenew = false
grails.plugins.springsecurity.cas.serviceUrl = null // must be set, e.g. 'http://localhost:8080/myapp/j_spring_cas_security_check'
grails.plugins.springsecurity.cas.serverUrlPrefix = null // must be set, e.g. 'http://localhost:9090/cas'
grails.plugins.springsecurity.cas.serverUrlEncoding = 'UTF-8'
grails.plugins.springsecurity.cas.key = 'grails-spring-security-cas'
grails.plugins.springsecurity.cas.artifactParameter = 'ticket'
grails.plugins.springsecurity.cas.serviceParameter = 'service'
grails.plugins.springsecurity.cas.filterProcessesUrl = '/j_spring_cas_security_check'
grails.plugins.springsecurity.cas.proxyCallbackUrl = null // should be set, e.g. 'http://localhost:8080/myapp/secure/receptor'
grails.plugins.springsecurity.cas.proxyReceptorUrl = null // should be set, e.g. '/secure/receptor'
grails.plugins.springsecurity.cas.useSingleSignout = true
0
votes

I know this thread is a bit old, but I just stumbled on it when hit with the same problem in 2.0-RC1.

What I found was the default configuration wasn't being loaded in SpringSecurityCasGrailsPlugin.groovy. This was an issue that happened when deploying .War files on Tomcat that was fixed long ago. The code added for that fix, if allowed to execute, fixed this new issue for me. I simply forced the if condition to always be true, as shown below (the code is from around line 105 in SpringSecurityCasGrailsPlugin.groovy):

if (true /*application.warDeployed*/) {
    // need to load secondary here since web.xml was already built, so
    // doWithWebDescriptor isn't called when deployed as war

    SpringSecurityUtils.loadSecondaryConfig 'DefaultCasSecurityConfig'
    conf = SpringSecurityUtils.securityConfig
}

(Only the if statement is change, the rest of the code is for illustration).

I don't know what side effects this may introduce (none that I can tell yet...). It seems like there is may be a difference in the run-app command making it behave like a .war deployment or something. Anyway, I certainly would be hesitant to deploy a production app this way, and would opt for creating all the configuration options by hand like in the accepted answer. But maybe this will give insight to someone who can figure out the real issue.