|
|
Siteminder and Custom Authorization Scenario
I've got a scenario I've been beating my head on the last few weeks:
We're using Siteminder for authentication and a custom in-house app for authorization. We're using Acegi to glue these two together and so far it's worked nicely.
But we have one Use Case that we're having a hard time implementing.
When our site is deployed to a server running Siteminder, we can safely assume that the user has already authenticated by the time they get to the application/Acegi filters. Cool. All good.
When our site is not deployed to a server running Siteminder (ie Development), SiteminderAuthenticationProcessingFilter nicely jumps in the way and puts up a login page allowing us to quot;mockquot; authenticate. Still cool. Still all good.
In both cases, once we've authenticated, we call out to our custom in-house authorization app to get the user's profile via our implementation of UserDetailsService.
This all works well, but here's the scenario that fails:
What if we can't get the user's profile from the service? Or they have been granted access to the site, but no profile has been created yet? Basically, they've authenticated (either via Siteminder, or quot;mocking itquot;), but they're not authorized to use the site yet. Seems reasonable. The problem is throwing an Exception in UserDetailsService operates in one of two ways, neither of which is desirable:
Throw either a UsernameNotFoundException, or a DataAccessException (or any child), and Acegi treats them both like an AuthenticationException. This kicks me out to my quot;Mockquot; login page, even if I'm running under Siteminder.
Throw any other exception, like an AccessDeniedException and it gets bubbled all the way back to the servlet and everything comes crashing down.
How do I get Acegi to understand that I'm already authenticated, but not authorized? Ideally I'd like to send the user to a friendly error page that says quot;Sorry, but you have no profile for this site yet. Come back laterquot;.
Thanks in advance for your comments.
DK
Here's my Acegi config:
Code:
lt;?xml version=quot;1.0quot; encoding=quot;UTF-8quot;?gt;
lt;beans
xmlns=quot;schema/beansquot;
xmlns:xsi=quot;2001/XMLSchema-instancequot;
xmlns:util=quot;schema/utilquot;
xsi:schemaLocation=quot;
schema/beans schema/beans/spring-beans-2.0.xsd
schema/util schema/util/spring-util-2.0.xsdquot;gt;
lt;!-- ================================= Acegi ================================= --gt;
lt;bean id=quot;filterChainProxyquot; class=quot;org.acegisecurity.util.FilterChainProxyquot;gt;
lt;property name=quot;filterInvocationDefinitionSourcequot;gt;
lt;valuegt;
CONVERT_ucl_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=fromSessionContextIntegrationFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptorlt;/valuegt;
lt;/propertygt;
lt;/beangt;
lt;bean id=quot;fromSessionContextIntegrationFilterquot; class=quot;org.acegisecurity.context.fromSessionContextIntegrationFilterquot;/gt;
lt;bean id=quot;authenticationProcessingFilterquot; class=quot;org.acegisecurity.ui.webapp.SiteminderAuthenticationProcessingFilterquot;gt;
lt;property name=quot;authenticationManagerquot; ref=quot;authenticationManagerquot; /gt;
lt;property name=quot;authenticationFailureuclquot; value=quot;/Login.htmquot; /gt;
lt;property name=quot;defaultTargetuclquot; value=quot;/main.htmquot; /gt;
lt;property name=quot;alwaysUseDefaultTargetuclquot; value=quot;truequot; /gt;
lt;property name=quot;filterProcessesuclquot; value=quot;/j_acegi_security_checkquot; /gt;
lt;property name=quot;siteminderUsernameHeaderKeyquot; value=quot;SM_USERquot; /gt;
lt;property name=quot;siteminderPasswordHeaderKeyquot; value=quot;SM_USERquot; /gt;
lt;property name=quot;formUsernameParameterKeyquot; value=quot;j_usernamequot; /gt;
lt;/beangt;
lt;bean id=quot;securityContextHolderAwareRequestFilterquot; class=quot;org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilterquot;/gt;
lt;bean id=quot;anonymousProcessingFilterquot; class=quot;org.acegisecurity.providers.anonymous.AnonymousProcessingFilterquot;gt;
lt;property name=quot;keyquot;gt;lt;valuegt;k1j23h4kjh1l23vbnmvlt;/valuegt;lt;/propertygt;
lt;property name=quot;userAttributequot;gt;lt;valuegt;anonymousUser,ROLE_ANONYMOUSlt;/valuegt;lt;/propertygt;
lt;/beangt;
lt;bean id=quot;exceptionTranslationFilterquot; class=quot;org.acegisecurity.ui.ExceptionTranslationFilterquot;gt;
lt;property name=quot;authenticationEntryPointquot;gt;
lt;bean id=quot;authenticationProcessingFilterEntryPointquot; class=quot;org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPointquot;gt;
lt;property name=quot;loginFormuclquot;gt;lt;valuegt;/Login.htmlt;/valuegt;lt;/propertygt;
lt;property name=quot;forcefromsquot;gt;lt;valuegt;falselt;/valuegt;lt;/propertygt;
lt;/beangt;
lt;/propertygt;
lt;/beangt;
lt;bean id=quot;authenticationManagerquot; class=quot;org.acegisecurity.providers.ProviderManagerquot;gt;
lt;property name=quot;providersquot;gt;
lt;listgt;
lt;ref local=quot;siteminderAuthenticationProviderquot;/gt;
lt;ref local=quot;anonymousAuthenticationProviderquot;/gt;
lt;/listgt;
lt;/propertygt;
lt;/beangt;
lt;bean id=quot;anonymousAuthenticationProviderquot; class=quot;org.acegisecurity.providers.anonymous.AnonymousAuthenticationProviderquot;gt;
lt;property name=quot;keyquot;gt;lt;valuegt;k1j23h4kjh1l23vbnmvlt;/valuegt;lt;/propertygt;
lt;/beangt;
lt;bean id=quot;siteminderAuthenticationProviderquot; class=quot;org.acegisecurity.providers.siteminder.SiteminderAuthenticationProviderquot; gt;
lt;property name=quot;hideUserNotFoundExceptionsquot; value=quot;falsequot; /gt;
lt;property name=quot;userDetailsServicequot; ref=quot;userDetailsServicequot;/gt;
lt;property name=quot;userCachequot;gt;
lt;bean class=quot;org.acegisecurity.providers.dao.cache.EhCacheBasedUserCachequot;gt;
lt;property name=quot;cachequot;gt;
lt;bean class=quot;org..cache.ehcache.EhCacheFactoryBeanquot;gt;
lt;property name=quot;cacheManagerquot;gt;
lt;bean class=quot;org..cache.ehcache.EhCacheManagerFactoryBeanquot;/gt;
lt;/propertygt;
lt;property name=quot;cacheNamequot; value=quot;userCachequot;/gt;
lt;/beangt;
lt;/propertygt;
lt;/beangt;
lt;/propertygt;
lt;/beangt;
lt;bean id=quot;userDetailsServicequot; class=quot;our.custom.UserDetailsServiceImplquot; /gt;
lt;bean id=quot;fromRequestAccessDecisionManagerquot; class=quot;org.acegisecurity.vote.AffirmativeBasedquot;gt;
lt;property name=quot;allowIfAllAbstainDecisionsquot;gt;lt;valuegt;falselt;/valuegt;lt;/propertygt;
lt;property name=quot;decisionVotersquot;gt;
lt;listgt;lt;bean class=quot;org.acegisecurity.vote.RoleVoterquot;/gt;
lt;/listgt;
lt;/propertygt;
lt;/beangt;
lt;bean id=quot;filterInvocationInterceptorquot; class=quot;org.acegisecurity.intercept.web.FilterSecurityInterceptorquot;gt; lt;property name=quot;authenticationManagerquot;gt;lt;ref bean=quot;authenticationManagerquot;/gt;lt;/propertygt; lt;property name=quot;accessDecisionManagerquot;gt;lt;ref local=quot;fromRequestAccessDecisionManagerquot;/gt;lt;/propertygt; lt;property name=quot;objectDefinitionSourcequot;gt; lt;valuegt; CONVERT_ucl_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /login.htm*=ROLE_ANONYMOUS /j_acegi_security_check=ROLE_ANONYMOUS,ROLE_User,ROLE_Admin
/css/*.css=ROLE_ANONYMOUS,ROLE_User,ROLE_Admin
/**=ROLE_User,ROLE_Admin lt;/valuegt; lt;/propertygt; lt;/beangt;
lt;!-- This bean is optional; it isn't used by any other bean as it only listens and logs --gt;
lt;bean id=quot;loggerListenerquot; class=quot;org.acegisecurity.event.authentication.LoggerListenerquot;/gt;
lt;/beansgt;I do not have a solution in mind but some thoughts (or a thought ). I am just starting to work on exactly the same scenario (siteminder for authentication, custom in-house service for authorization details).
The first thought that comes to mind as a solution is to craft a custom siteminderAuthenticationProvider that handles the missing authorization information in a way that makes sense for your application.
Brad
I figured it out.
I had to create an exceptionMapping in my authenticationProcessingFilter that redirected to my custom error page. This wasn't entirely intuitive from the documentation until I stumbled on it in the api doc for AbstractProcessingFilter.
The reason why I kinda don't like it, is because it's still only limited to AuthenticationExceptions. I guess I'm being picky about the semantic difference between when I'm authenticated and when I'm authorized, so I'll drop it, but here's the solution regardless:
In loadUserByUsername in UserDetailsService you only have two options for throwing exceptions: UsernameNotFoundException or DataAccessException. I had been trying to toss various forms of DataAccessException with no success until I started looking at the the Acegi source for SiteminderAuthenticationProvider, specifically the code in retrieveUser. I noticed that any DataAccessException thrown gets caught and re-thrown as an AuthenticationServiceException (a subclass of AuthenticationException). So I added an exceptionMapping for AuthenticationServiceException and pointed it to my error page. I made sure I added the neccessary authority for ROLE_ANONYMOUS to see the page and after that it was all set.
Here are the two changes I made:Code:
lt;bean id=quot;authenticationProcessingFilterquot; class=quot;org.acegisecurity.ui.webapp.SiteminderAuthenticationProcessingFilterquot;gt;
lt;property name=quot;authenticationManagerquot; ref=quot;authenticationManagerquot; /gt;
lt;property name=quot;authenticationFailureuclquot; value=quot;/Login.htmquot; /gt;
lt;property name=quot;defaultTargetuclquot; value=quot;/main.htmquot; /gt;
lt;property name=quot;alwaysUseDefaultTargetuclquot; value=quot;truequot; /gt;
lt;property name=quot;filterProcessesuclquot; value=quot;/j_acegi_security_checkquot; /gt;
lt;property name=quot;siteminderUsernameHeaderKeyquot; value=quot;SM_USERquot; /gt;
lt;property name=quot;siteminderPasswordHeaderKeyquot; value=quot;SM_USERquot; /gt;
lt;property name=quot;formUsernameParameterKeyquot; value=quot;j_usernamequot; /gt;
lt;property name=quot;exceptionMappingsquot;gt;
lt;propsgt;
lt;prop key=quot;org.acegisecurity.AuthenticationServiceExceptionquot;gt;/securityError.jsplt;/propgt;
lt;/propsgt;
lt;/propertygt;
lt;/beangt;
lt;bean id=quot;filterInvocationInterceptorquot; class=quot;org.acegisecurity.intercept.web.FilterSecurityInterceptorquot;gt; lt;property name=quot;authenticationManagerquot;gt;lt;ref bean=quot;authenticationManagerquot;/gt;lt;/propertygt; lt;property name=quot;accessDecisionManagerquot;gt;lt;ref local=quot;fromRequestAccessDecisionManagerquot;/gt;lt;/propertygt; lt;property name=quot;objectDefinitionSourcequot;gt; lt;valuegt; CONVERT_ucl_TO_LOWERCASE_BEFORE_COMPARISON PATTERN_TYPE_APACHE_ANT /login.htm*=ROLE_ANONYMOUS /securityerror.jsp=ROLE_ANONYMOUS /j_acegi_security_check=ROLE_ANONYMOUS,ROLE_User,ROLE_Admin
/css/*.css=ROLE_ANONYMOUS,ROLE_User,ROLE_Admin
/**=ROLE_User,ROLE_Admin lt;/valuegt; lt;/propertygt; lt;/beangt;Thank you for posting the solution to your problem. This will be very helpful.
Brad
Hi Guys,sorry to post this here but I can't find any other issue close to what I'm experiencing at the minute.
I have an app configured the same way as DK's (copied he config as a quick start). The problem is if I have my deafultTargetucl set to say index.jsp therefore I'm hitting (via siteminder)
lt;can't post ucl yetgt;/myapp/index.jsp[/ucl]
the siteminder auth processing filter gets fired, reads SM_USER etc and the user gets loaded and page displays, great!!!
BUT if I request
lt;can't post ucl yetgt;/myapp/otherindex.jsp[/ucl]
which may be a perfectly valid page for the user, the siteminder auth processing filter is not fired and th development env login page is displayed.
Looking through the SiteminderAuthenticationProcessingFilter shows the following
// if true is returned then authentication will be attempted. boolean bAttemptAuthentication = (uri.endsWith(request .getContextPath() + getFilterProcessesucl())) || ((getDefaultTargetucl() != null) amp;amp; uri.endsWith(getDefaultTargetucl()) amp;amp; !bAuthenticated);
Which suggests siteminder auth will only be fired for the dev form j_security_check or when the configured default target ucl is invoked???
Am I missing something really obvious here? (wouldn't be the first time)
Cheers,
Dave. |
|