I've been trying out 'Method Level' security on this application I've been working on. The idea is to secure a method which gets called from the presentation layer using DWR. Now, I've tried adding the following annotations on my method:
@PreAuthorize("isAuthenticated() and hasRole('ROLE_CUSTOMER')")
And corresponding entry in my security context:
<global-method-security pre-post-annotations="enabled" />
On similar lines, I've tried @Secured annotation:
@Secured({"ROLE_CUSTOMER" })
And corresponding entry in my security context:
<global-method-security secured-annotations="enabled" />
Ideally, I would expect that if a user is not authenticated, they should be redirected to a 'Sign in' page and the 'ROLES' should not be checked. In this case, even for an unauthenticated user, this method call results in 'AccessDeniedException'. I need it to redirect the user to a login page in such a scenario.
To take it forward, I even tried handling the accessdenied exception by creating a custom AccessDenied Handler. Unfortunately, the handler never got called but the exception was thrown.
Here's the configuration:
<access-denied-handler ref="customAccessDeniedHandler"/>
This has a corresponding handler bean defined in the same file.
Still no luck. The accessdeniedhandler never gets called.
Just to summarize the requirement, I need to secure a method. If this method gets called, and the user is unauthenticated the user should get redirected to 'Sign In' page (which as of now is throwing Accessdenied execption).
Appreciate your help folks..
EDIT 1: Here is a snippet from the security context:
<http>
<intercept-url pattern="/*sign-in.do*" requires-channel="$secure.channel}" />
.....
.....
.....
<intercept-url pattern="/j_acegi_security_check.do" requires-channel="${secure.channel}" />
<intercept-url pattern="/*.do" requires-channel="http" />
<intercept-url pattern="/*.do\?*" requires-channel="http" />
<form-login login-page="/sign-in.do" authentication-failure-url="/sign-in.do?login_failed=1"
authentication-success-handler-ref="authenticationSuccessHandler" login-processing-url="/j_acegi_security_check.do"/>
<logout logout-url="/sign-out.do" logout-success-url="/index.do" />
<session-management session-authentication-strategy-ref="sessionAuthenticationStrategy" />
<access-denied-handler ref="customAccessDeniedHandler"/>
</http>
<beans:bean id="customAccessDeniedHandler" class="com.mypackage.interceptor.AccessDeniedHandlerApp"/>
Okay, I have not had much success finding why I got an AccessDeniedException. Regardless, I've worked my way out of it till the time I find the reason. Here's a couple of approaches I took:
1) As mentioned by Zagyi, I was able to propagate the AccessDenied Exception over to the client side. I could create an exception handler to and redirect the user to the sign in page.
However, I took a different approach (which may not be the most optimal but seems to work as of now. Here's what I've done:
1) Created a DWRAjaxFilter and mapped to only the Remote Objects I am interested in. This would bean only the DWR calls to these remote methods get intercepted by the filter. This is because I do not want all DWR exposed methods to as for a sign in.
<create creator="spring" javascript="downloadLinksAjaxService">
<param name="beanName" value="downloadLinksAjaxService" />
<include method="methodOne" />
<include method="methodTwo" />
<filter class="com.xyz.abc.interceptor.DwrAjaxFilter"></filter>
</create>
2) Here is the actual filter implementation:
public class DwrSessionFilter implements AjaxFilter {
public Object doFilter(final Object obj, final Method method,
final Object[] params, final AjaxFilterChain chain)
throws Exception {
SecurityContext context = SecurityContextHolder.getContext();
Authentication auth = context.getAuthentication();
if (!auth.isAuthenticated()
|| auth.getAuthorities().contains(
new GrantedAuthorityImpl("ROLE_ANONYMOUS"))) {
throw new LoginRequiredException("Login Required");
} else {
return chain.doFilter(obj, method, params);
}
}
}
3) Here is the client side handler:
function errorHandler(message, exception){
if(exception && exception.javaClassName == "org.directwebremoting.extend.LoginRequiredException") {
document.location.reload();
}
}
4) I also added and exception mapper for DWR so that I could convert JAVA exceptions to JS exceptions:
<convert match="java.lang.Exception" converter="exception">
<param name='include' value='message'/>
</convert>
5) This seems to work well as of now. I'm still testing this to see if this fails somewhere.
Would appreciate any more inputs.