I have several Grails applications running in production and interfacing to a CAS server (CAS 3.3.5) via spring-security and the "Grails CAS plugin". These applications are supported by various versions of Grails from version 1.3.7 to 2.2.4
I am moving one of them to 2.4.4 and I have never ending problems with the authentication. At the end of the exercise I have the notorious: "This webpage has a redirect loop" (in Chrome). At the server side I have the usual (!) error:
ERROR [org.jasig.cas.web.ServiceValidateController] - <TicketException
generating ticket for: [callbackUrl:
https://casclient.mydomain.com:9043/testCas/secure/receptor]>
org.jasig.cas.ticket.InvalidTicketException
at org.jasig.cas.CentralAuthenticationServiceImpl.delegateTicketGrantingTicket(CentralAuthenticationServiceImpl.java:268)
Here are all the steps I made to reproduce the problem:
grails create-app testCas
grails create-controller showSecure
add the action showSecurePage in the controllergrails generate-views
- create the page showSecurePage under view/showSecure
Add this to UrlMappings
class UrlMappings { static mappings = { "/showSecure" { controller = "showSecure" action = [ GET: "showSecurePage" ] } "/"(view:"/index") "500"(view:'/error') } }
At this point if I start the application I can access the page "showSecurePage" without problems. Let's move on and install the "CAS plugin".
Add in the "plugins" snippet of BuildConfig.groovy the strings:
compile ":spring-security-core:2.0-RC4" compile ":spring-security-cas:2.0-RC1"
and in the "dependencies" snippet:
mavenRepo 'http://repo.spring.io/milestone'
Execute
grails clean
andgrails compile
.Run:
grails s2-quickstart testCas User Role
The Config.groovy
is modified and has to be "tuned".
Since we don't use annotations I replace the entries added by s2-quickstart (the staticRules) with:
grails.plugin.springsecurity.rejectIfNoRule = true grails.plugin.springsecurity.securityConfigType = "InterceptUrlMap" grails.plugin.springsecurity.interceptUrlMap = [ '/': ['permitAll'], '/index': ['permitAll'], '/index.gsp': ['permitAll'], '/**/js/**': ['permitAll'], '/**/css/**': ['permitAll'], '/**/images/**': ['permitAll'], '/**/favicon.ico': ['permitAll'], '/login/**': ['permitAll'], '/logout/**': ['permitAll'], '/secure/receptor': ['permitAll'], '/showSecure/**': ['isFullyAuthenticated()'], '/finance/**': ['ROLE_FINANCE', 'isFullyAuthenticated()'] ]
Note: I added "permitAll" to "/secure/receptor" as described in 19710841
Finally we add the CAS configuration. As described in the documentation all the listed parameters have to be defined:
grails.serverURL = "http://casclient.mydomain.com:9080/testCas" grails.serverSecureURL = "https://casclient.mydomain.com:9043/testCas" grails.plugin.springsecurity.useCAS = true grails.plugin.springsecurity.cas.active = true grails.plugin.springsecurity.cas.serverUrlPrefix = 'https://casserver.mydomain.com:10443/sso' grails.plugin.springsecurity.cas.serverUrlEncoding = 'UTF-8' grails.plugin.springsecurity.cas.loginUri = '/login' grails.plugin.springsecurity.cas.sendRenew = false grails.plugin.springsecurity.cas.serviceUrl = "${grails.serverURL}/secure/security_check" grails.plugin.springsecurity.cas.key ='authentication_provider' grails.plugin.springsecurity.cas.artifactParameter = 'ticket' grails.plugin.springsecurity.cas.serviceParameter = 'service' grails.plugin.springsecurity.cas.filterProcessesUrl = '/secure/security_check' grails.plugin.springsecurity.cas.proxyCallbackUrl = "${grails.serverSecureURL}/secure/receptor" grails.plugin.springsecurity.cas.proxyReceptorUrl = '/secure/receptor' grails.plugin.springsecurity.cas.useSingleSignOut = true grails.plugin.springsecurity.logoutURL = "${grails.plugin.springsecurity.cas.serverUrlPrefix}/logout" grails.plugin.springsecurity.logout.afterLogoutUrl = "${grails.plugin.springsecurity.cas.serverUrlPrefix}/logout?url=${grails.serverURL}"
Note: if in grails.plugin.springsecurity.cas.proxyCallbackUrl
I define the link as "http" and not as "https" I will have a loop with, at the CAS server side, a "bad credentials" message. Adding the secure link this error disappears.
Now accessing the secure page I see the usual CAS login. If I login correctly I get the "redirect loop" error while in the CAS log I see:
-------------------------2015-03-31 13:33:33,736 ERROR
[org.jasig.cas.web.ServiceValidateController] - <TicketException
generating ticket for: [callbackUrl:
https://casclient.mydomain.com:9043/testCas/secure/receptor]>
org.jasig.cas.ticket.InvalidTicketException
at org.jasig.cas.CentralAuthenticationServiceImpl.delegateTicketGrantingTicket(CentralAuthenticationServiceImpl.java:268)
at org.jasig.cas.web.ServiceValidateController.handleRequestInternal(ServiceValidateController.java:126)
EDIT: Some more information. Among the verbose output when you enable all sorts of debug, I realized that there were continuos references to ROLE_ANONYMOUS. The suspect is (was) that the problem is not authentication but rather authorization. Indeed this was the problem. If I modify the Config.groovy:
'/showSecure/**': ['ROLE_USER', 'isFullyAuthenticated()'],
and I implement the service that have (now) defined in resources.groovy:
// Place your Spring DSL code here
beans = {
userDetailsService(EsoUserDetailsService)
}
The problem disappears. I have another one though. If in EsoUserDetailsService I try to retrieve the roles from the database in this way:
User.withTransaction { status ->
User user = User.findByUsername(username)
if (!user) throw new UsernameNotFoundException('User not found', username)
def authorities = user.authorities.collect { new GrantedAuthorityImpl(it.authority)
}
return new EsoUserDetails(user.username, user.password, user.enabled,
!user.accountExpired, !user.passwordExpired, !user.accountLocked, authorities,
user.id, user.getUserRealName())
}
I have the error:
Method on class [eso.phase3.rm.User] was used outside of a Grails application. If running in the context of a test using the mocking API or bootstrap Grails correctly.