I already try the whole day, to get my custom authentication failure handler to work with Spring 3.1.3.
I think it is properly configured
<http use-expressions="true" disable-url-rewriting="true">
<intercept-url pattern="/rest/login" access="permitAll" />
<intercept-url pattern="/rest/**" access="isAuthenticated()" />
<intercept-url pattern="/index.html" access="permitAll" />
<intercept-url pattern="/js/**" access="permitAll" />
<intercept-url pattern="/**" access="denyAll" />
<form-login username-parameter="user" password-parameter="pass" login-page="/rest/login"
authentication-failure-handler-ref="authenticationFailureHandler" />
</http>
<beans:bean id="authenticationFailureHandler" class="LoginFailureHandler" />
My implementation is this
public class LoginFailureHandler implements AuthenticationFailureHandler {
private static final Logger log = LoggerFactory.getLogger(LoginFailureHandler.class);
public LoginFailureHandler() {
log.debug("I am");
}
#Autowired
private ObjectMapper customObjectMapper;
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
log.debug("invalid login");
User user = new User();
user.setUsername("invalid");
try (OutputStream out = response.getOutputStream()) {
customObjectMapper.writeValue(out, user);
}
}
}
In the console I see
2013-04-11 14:52:29,478 DEBUG LoginFailureHandler - I am
So it is loaded.
With wrong username or passwort, when a BadCredentialsException is thrown, I don't see invalid login.
The Method onAuthenticationFailure is never invoked.
Instead the service redirects the browser onto /rest/login again and again...
Edit
2013-04-11 15:47:26,411 DEBUG de.pentos.spring.LoginController - Incomming login chuck.norris, norris
2013-04-11 15:47:26,412 DEBUG o.s.s.a.ProviderManager - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2013-04-11 15:47:26,415 DEBUG o.s.s.a.d.DaoAuthenticationProvider - Authentication failed: password does not match stored value
2013-04-11 15:47:26,416 DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolving exception from handler [public de.pentos.spring.User de.pentos.spring.LoginController.login(de.pentos.spring.User)]: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2013-04-11 15:47:26,419 DEBUG o.s.w.s.m.a.ResponseStatusExceptionResolver - Resolving exception from handler [public de.pentos.spring.User de.pentos.spring.LoginController.login(de.pentos.spring.User)]: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2013-04-11 15:47:26,419 DEBUG o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolving exception from handler [public de.pentos.spring.User de.pentos.spring.LoginController.login(de.pentos.spring.User)]: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2013-04-11 15:47:26,426 DEBUG o.s.web.servlet.DispatcherServlet - Could not complete request
org.springframework.security.authentication.BadCredentialsException: Bad credentials
This happens in DEBUG Mode
Where is my mistake?
Judged from the logs you attached I think you've made a mistake in implementing the login process. I cannot be absolutely sure, but I guess you call ProviderManager.authenticate() in your LoginController. The method throws a BadCredentialsException that causes Spring MVC's exception handling mechanism to kick in, which of course has no knowledge about the AuthenticationFailureHandler configured for Spring Security.
From the login controller you should normally just serve a simple login form with action="j_spring_security_check" method="post". When the user submits that form, the configured security filter (namely UsernamePasswordAuthenticationFilter) intercepts that request and handles authentication. You don't have to implement that logic yourself in a controller method.
Reply to your comment:
You do use ProviderManager (it's the implementation of the autowired AuthenticationManager interface). The mistake you make is that you try to rewrite the logic already implemented and thoroughly tested in auth filters. This is bad in itself, but even that is done in a wrong way. You select just a few lines from a complex logic, and among other things you forget e.g. invoking the session strategy (to prevent session fixation attacks, and handling concurrent sessions). The original implementation invokes the AuthenticationFailureHandler
as well, which you also forgot in your method, this is the very reason of the problem your original question is about.
So you end up with an untested, brittle solution instead of nicely integrating with the framework to leverage its roboustness and full capacity. As I said, the config you posted in your answer is a definite improvement, because it uses the framework provided filter for authentication. Keep that config and remove LoginController.login(), it won't be called anyway by requests sent to /rest/login.
A more fundamental question is if it's really a good solution to use sessions and form-based login mechanism if you implement RESTful services. (On form-based login I mean that the client sends its credentials once in whatever format, and then gets authenticated by a stateful session on subsequent requests.) With REST services it's more prevalent to keep everything stateless, and re-authenticate each new request by information carried by http headers.
It's a problem with the order in the security-app-context.xml.
If I first define all my beans and then all the rest it works.
I tried a lot, so don't wonder, that it now looks a little different then in the question
<beans:bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/rest/login" />
</beans:bean>
<beans:bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="filterProcessesUrl" value="/rest/login" />
<beans:property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" />
<beans:property name="authenticationFailureHandler" ref="authenticationFailureHandler" />
</beans:bean>
<beans:bean id="authenticationSuccessHandler" class="de.pentos.spring.LoginSuccessHandler" />
<beans:bean id="authenticationFailureHandler" class="de.pentos.spring.LoginFailureHandler" />
<http use-expressions="true" disable-url-rewriting="true" entry-point-ref="authenticationProcessingFilterEntryPoint"
create-session="ifRequired">
<intercept-url pattern="/rest/login" access="permitAll" />
<intercept-url pattern="/rest/**" access="isAuthenticated()" />
<intercept-url pattern="/index.html" access="permitAll" />
<intercept-url pattern="/js/**" access="permitAll" />
<intercept-url pattern="/**" access="denyAll" />
<custom-filter position="FORM_LOGIN_FILTER" ref="authenticationFilter" />
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider>
<user-service>
<user name="chuck.norris" password="cnorris" authorities="ROLE_ADMIN" />
<user name="user" password="user" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
Does not look bad to me. Did you try to use the debug mode of your IDE ?
Did you see things like this in your logs :
Authentication request failed: ...
Updated SecurityContextHolder to contain null Authentication
Delegating to authentication failure handler ...
The AuthenticationFailureHandler will be called automatically, only if the authentication is done in one of the authentication filter : UsernamePasswordAuthenticationFilter normally in your case.
(Looking at your requirements), You don't need a custom AuthenticationFailureHandler as the with default SimpleUrlAuthenticationFailureHandler of Spring and properly implementing AuthenticationProvider should serve the purpose.
<form-login login-page="/login" login-processing-url="/do/login" authentication- failure-url ="/login?authfailed=true" authentication-success-handler-ref ="customAuthenticationSuccessHandler"/>
If you have handled the Exceptions well in Authentication Provider:
Sample Logic:
String loginUsername = (String) authentication.getPrincipal();
if (loginUsername == null)
throw new UsernameNotFoundException("User not found");
String loginPassword = (String) authentication.getCredentials();
User user = getUserByUsername(loginUsername);
UserPassword password = getPassword(user.getId());
if (!password.matches(loginPassword)) {
throw new BadCredentialsException("Invalid password.");
}
If we want the exceptions thrown to be reflected at the client interface, add the following scriplet on the JSP responding to authentication-failure-url="/login?authfailed=true"
<%
Exception error = (Exception) request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
if (error != null)
out.write(error.getMessage());
%>
Related
i am using Spring-Security 3.1.3 with Spring 3.2.2 and Majorra 2.1.25. I don't use managed beans, but use SpringBeanFacesELResolver. So basically, i use spring for everything.
I use the following
<http auto-config="true">
<form-login login-page="/components/public/login.jsf" authentication-failure-handler-ref="customAuthenticationFailureHandler" />
<intercept-url pattern="/components/admin/**" access="ROLE_ADMIN" />
<intercept-url pattern="/components/secured/**" access="ROLE_USER,ROLE_ADMIN" />
<intercept-url pattern="/**" />
<session-management>
<concurrency-control max-sessions="1" expired-url="/components/public/sessionExpired.jsf" />
</session-management>
<access-denied-handler ref="customAccessDeniedHandler" />
</http>
which works as indended, e.g. on accessing a secured page, the user is directed to the login and after the login he is brought to the requested page. If he tries to reach an admin-page, but only has ROLE_USER, he is directed to the access-denied page by my customAccessDeniedHandler
So far so good. The problem now is the following:
i use #Secured({ "ROLE_ADMIN" }) on a method. If a user with insufficient rights accesses this method, an AccessDeniedException is thrown, which is just what i want. BUT: My customAccessDeniedHandler is not invoked! Why is that?
Some more info: The method is invoked as part of an AJAX call and i would like to use my handler to set a FacesMessage as Feedback. How do i do this centrally? I am pretty sure i could wrap another method around this and use try-catch to catch the AccessDeniedException myself. But doing this for every method that has to be secured will just bloat up my code with a massive amount of unnecessary try-catch-methods. How can i handle the Exception centrally?
I found a solution now. I use Spring-AOP and "bind" an around aspect to all methods annotated with #Secured
<!-- aspect configuration -->
<aop:config>
<aop:aspect id="securedAspect" ref="securityFeedbackAspect">
<aop:around pointcut="#annotation(org.springframework.security.access.annotation.Secured)" method="handleSecuredAnnotations" />
</aop:aspect>
</aop:config>
The aspect looks like this
#Service
public class SecurityFeedbackAspect {
public Object handleSecuredAnnotations(final ProceedingJoinPoint pjp) throws Throwable {
try {
return pjp.proceed();
} catch (AccessDeniedException e) {
// log + set feedback for user here
}
}
}
Hope this helps anyone someday. One addition info: Somehow i couldn't get this to work with annotation-only configuration, because the #Secured-check would always be invoked first and my aspect would only run if no exception was thrown by the Spring-Security-Logic. I ended up using XML configuration, which seems to always go first, since i found no other way (even with #Order)
I have a SpringMVC web application that needs to authenticate to a RESTful web service using Spring Security by sending the username and password. When an user is logged, a cookie needs to be set to the user's browser and in the subsequent calls the user session is validated with another RESTful web service by using the cookie.
I've been looking everywhere, but I have not been able to find a good example on how to accomplish this, and all my attempts have been in vain.
Here is what I have in mind:
I can have two authentication-providers declared, the first checks the cookie, and if it fails for any reason it goes to the second one which checks with the username and password (will fail too if there is no username and password in that request).
Both services return the authorities of the user each time, and spring security is "stateless".
On the other hand, I have questioned myself if this approach is correct, since it's been so difficult to find an example or somebody else with the same problem. Is this approach wrong?
The reason why I want to do this instead of just JDBC authentication is because my whole web application is stateless and the database is always accessed through RESTful web services that wrap a "petitions queue", I'd like to respect this for user authentication and validation too.
What have I tried so far? I could paste the long long springSecurity-context.xml, but I'll just list them instead for now:
Use a custom authenticationFilter with a authenticationSuccessHandler. Obviously doesn't work because the user is already logged in this point.
Make an implementation of entry-point-ref filter.
Do a custom-filter in the position BASIC_AUTH_FILTER
Make a custom Authentication Provider (Struggled a lot with no luck!). I'm retrying this while I get some answers.
I was starting to use CAS when I decided to write a question instead. Maybe in the future I can consider having a CAS server in my webapp, however for the moment, this feels like a huge overkill.
Thanks in advance!
BTW, I'm using Spring Security 3.1.4 and Spring MVC 3.2.3
EDIT: I WAS ABLE TO DO IT THANKS TO #coder ANSWER
Here is some light on what I did, I'll try to document all this and post it here or in a blog post sometime soon:
<http use-expressions="true" create-session="stateless" entry-point-ref="loginUrlAuthenticationEntryPoint"
authentication-manager-ref="customAuthenticationManager">
<custom-filter ref="restAuthenticationFilter" position="FORM_LOGIN_FILTER" />
<custom-filter ref="restPreAuthFilter" position="PRE_AUTH_FILTER" />
<intercept-url pattern="/signin/**" access="permitAll" />
<intercept-url pattern="/img/**" access="permitAll" />
<intercept-url pattern="/css/**" access="permitAll" />
<intercept-url pattern="/js/**" access="permitAll" />
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
</http>
<authentication-manager id="authManager" alias="authManager">
<authentication-provider ref="preauthAuthProvider" />
</authentication-manager>
<beans:bean id="restPreAuthFilter" class="com.company.CustomPreAuthenticatedFilter">
<beans:property name="cookieName" value="SessionCookie" />
<beans:property name="checkForPrincipalChanges" value="true" />
<beans:property name="authenticationManager" ref="authManager" />
</beans:bean>
<beans:bean id="preauthAuthProvider"
class="com.company.CustomPreAuthProvider">
<beans:property name="preAuthenticatedUserDetailsService">
<beans:bean id="userDetailsServiceWrapper"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<beans:property name="userDetailsService" ref="userDetailsService" />
</beans:bean>
</beans:property>
</beans:bean>
<beans:bean id="userDetailsService" class="com.company.CustomUserDetailsService" />
<beans:bean id="loginUrlAuthenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:constructor-arg value="/signin" />
</beans:bean>
<beans:bean id="customAuthenticationManager"
class="com.company.CustomAuthenticationManager" />
<beans:bean id="restAuthenticationFilter"
class="com.company.CustomFormLoginFilter">
<beans:property name="filterProcessesUrl" value="/signin/authenticate" />
<beans:property name="authenticationManager" ref="customAuthenticationManager" />
<beans:property name="authenticationFailureHandler">
<beans:bean
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/login?login_error=t" />
</beans:bean>
</beans:property>
</beans:bean>
And the Custom Implementations are something like this:
// Here, the idea is to write authenticate method and return a new UsernamePasswordAuthenticationToken
public class CustomAuthenticationManager implements AuthenticationManager { ... }
// Write attemptAuthentication method and return UsernamePasswordAuthenticationToken
public class CustomFormLoginFilter extends UsernamePasswordAuthenticationFilter { ... }
// Write getPreAuthenticatedPrincipal and getPreAuthenticatedCredentials methods and return cookieName and cookieValue respectively
public class CustomPreAuthenticatedFilter extends AbstractPreAuthenticatedProcessingFilter { ... }
// Write authenticate method and return Authentication auth = new UsernamePasswordAuthenticationToken(name, token, grantedAuths); (or null if can't be pre-authenticated)
public class CustomPreAuthProvider extends PreAuthenticatedAuthenticationProvider{ ... }
// Write loadUserByUsername method and return a new UserDetails user = new User("hectorg87", "123456", Collections.singletonList(new GrantedAuthorityImpl("ROLE_USER")));
public class CustomUserDetailsService implements UserDetailsService { ... }
you can define a custom pre-auth filter by extending
AbstractPreAuthenticatedProcessingFilter.
In your implementation of
getPreAuthenticatedPrincipal() method you can check if cookie exists
and if it exists return cookie name is principal and cookie value in
credentials.
Use PreAuthenticatedAuthenticationProvider and provide your custom preAuthenticatedUserDetailsService to check if cookie is vali, if its valid also fetch granted authorities else throw AuthenticationException like BadCredentialsException
For authenticating user using username/password, add a form-login filter, basic-filter or a custom filter with custom authentication provider (or custom userdetailsService) to validate user/password
In case cookie exists, pre auth filter will set authenticated user in springContext and your username./password filter will not be called, if cookie is misisng/invalid, authentication entry point will trigger the authentication using username/password
Hope it helps
i have a method secured with spring security as follows:
#PreAuthorize("hasRole('add_user')")
public void addUser(User user) ;
and if a user with no enoguh permissions is trying to invoke it
, an accessDenied exception is thrown:
org.springframework.security.access.AccessDeniedException: Access is denied
this is what's expected, but the question is, why the defined access-denied-handler
in security.xml configuration file is not working:
<access-denied-handler error-page="accessDenied"/>
I mean by not working that when user with not enough permission when trying to press the button addUser which will invoke the service addUser (that's only accessible by user has this permission) an AccessDenied Exception is thrown and that's the desired behavior, but the user isn't redirected to the access denied exception as configured in xml.
shouldn't the user gets redirected automatically to access denied page when this exception is thrown, or i have to define such behavior explicitly in code ?
please advise.
I am using Spring Security 3.0.5 with JSF 2.1 and ICEFaces 2
UPDATE: applicationSecurity.xml:
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.4.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.1.xsd">
<!-- Enable #pre, #post spring security method level annotations -->
<global-method-security pre-post-annotations="enabled" />
<http use-expressions="true" auto-config="true" access-denied-page="/accessDenied">
<session-management session-fixation-protection="none"/>
<remember-me token-validity-seconds="1209600"/>
<intercept-url pattern="/accessDenied" access="permitAll"/>
<intercept-url pattern="/login" access="permitAll"/>
<intercept-url pattern="/j_spring_security_check" access="permitAll" />
<intercept-url pattern="/faces/javax.faces.resource/**" access="permitAll" />
<intercept-url pattern="/xmlhttp/**" access="permitAll" />
<intercept-url pattern="/resources/**" access="permitAll" />
<intercept-url pattern="/scripts/**" access="permitAll" />
<intercept-url pattern="/images/**" access="permitAll" />
<intercept-url pattern="/css/**" access="permitAll" />
<!-- All pages requires authentication (not anonymous user) -->
<intercept-url pattern="/**" access="isAuthenticated()" />
<intercept-url pattern="/faces/**" access="isAuthenticated()" />
<form-login default-target-url="/"
always-use-default-target="true"
login-processing-url="/j_spring_security_check"
login-page="/login"
authentication-failure-url="/login?login_error=1"
/>
<logout logout-url="/logout" logout-success-url="/login" />
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="userDetailsServiceImpl"/>
</authentication-manager>
</beans:beans>
UPDATE 2 : debugs before exception:
DEBUG [http-bio-8080-exec-1] (PrePostAnnotationSecurityMetadataSource.java:93) - #org.springframework.security.access.prepost.PreAuthorize(value=hasRole('add_user')) found on specific method: public void com.myapp.service.impl.UserServiceImpl.addUser(com.myapp.domain.User) throws java.lang.Exception,org.springframework.security.access.AccessDeniedException
DEBUG [http-bio-8080-exec-1] (DelegatingMethodSecurityMetadataSource.java:66) - Adding security method [CacheKey[com.myapp.service.impl.UserServiceImpl; public abstract void com.myapp.service.UserService.addUser(com.myapp.domain.User) throws java.lang.Exception,org.springframework.security.access.AccessDeniedException]] with attributes [[authorize: 'hasRole('add_user')', filter: 'null', filterTarget: 'null']]
DEBUG [http-bio-8080-exec-1] (AbstractSecurityInterceptor.java:191) - Secure object: ReflectiveMethodInvocation: public abstract void com.myapp.service.UserService.addUser(com.myapp.domain.User) throws java.lang.Exception,org.springframework.security.access.AccessDeniedException; target is of class [com.myapp.service.impl.UserServiceImpl]; Attributes: [[authorize: 'hasRole('add_user')', filter: 'null', filterTarget: 'null']]
DEBUG [http-bio-8080-exec-1] (AbstractSecurityInterceptor.java:292) - Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#c650d918: Principal: org.springframework.security.core.userdetails.User#db344023: Username: user#mycomp.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: access_viewUsers; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: E6BBAC0CD4499B1455227DC6035CC882; Granted Authorities: access_viewUsers
DEBUG [http-bio-8080-exec-1] (AffirmativeBased.java:53) - Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter#1d1e082e, returned: -1
DEBUG [http-bio-8080-exec-1] (AffirmativeBased.java:53) - Voter: org.springframework.security.access.vote.RoleVoter#1eab12f1, returned: 0
DEBUG [http-bio-8080-exec-1] (AffirmativeBased.java:53) - Voter: org.springframework.security.access.vote.AuthenticatedVoter#71689bf1, returned: 0
According to the spring Security documentation the user of the access-denied-page attribute of the element has been deprecated in Spring 3.0 and above.
We do the following in our app:
Create a custom access denied handler by extending the Spring Security framework's AccessDeniedHandlerImpl.
Call the setErrorPage method, passing in the name of the controller that will display your access denied page
In our case we lock the user's account in the custom handler - there's no good reason for any user to ever get an access denied exception unless they're doing something that they should't. We also log what they were trying to access, etc.
Call super.handle(_request, _response, _exception); at the end of the handler. Spring will forward control to the controller listed in #2 above.
public class AccessDeniedHandlerApp extends AccessDeniedHandlerImpl {
private static Logger logger = Logger.getLogger(AccessDeniedHandlerApp.class);
private static final String LOG_TEMPLATE = "AccessDeniedHandlerApp: User attempted to access a resource for which they do not have permission. User %s attempted to access %s";
#Override
public void handle(HttpServletRequest _request, HttpServletResponse _response, AccessDeniedException _exception) throws IOException, ServletException {
setErrorPage("/securityAccessDenied"); // this is a standard Spring MVC Controller
// any time a user tries to access a part of the application that they do not have rights to lock their account
<custom code to lock the account>
super.handle(_request, _response, _exception);
}
}
Here's my XML: AccessDeniedHandlerApp extends 'AccessDeniedHandlerImpl`
<http auto-config='true'>
<intercept-url pattern="/views/**" access="ROLE_USER" />
<form-login login-page="/Login.jsp" authentication-success-handler-ref="loginSuccessFilter"
authentication-failure-handler-ref="loginFailureFilter" />
<logout logout-success-url="/home" />
<access-denied-handler ref="customAccessDeniedHandler"/>
</http>
<beans:bean id="customAccessDeniedHandler" class="org.demo.security.AccessDeniedHandlerApp"/>
Here's my Access Denied Controller - I should have posted this earlier - sorry about that. In order to get the access denied page to come up I had to use a redirect:
#Controller
public class AccessDeniedController {
private static Logger logger = Logger.getLogger(AccessDeniedController.class);
#RequestMapping(value = "/securityAccessDenied")
public String processAccessDeniedException(){
logger.info("Access Denied Handler");
return "redirect:/securityAccessDeniedView";
}
#RequestMapping(value = "/securityAccessDeniedView")
public String displayAccessDeniedView(){
logger.info("Access Denied View");
return "/SecurityAccessDenied";
}
Please let me know if this doesn't resolve it and I'll keep digging - I just tested it again locally here and this should do the trick.
}
I have run into the same problem myself and posted another question relating to the same issue. After several hours of digging around, I finally found the solution for my issue. I know this question is 2+ years old, but thought I would update it with information in case it was of value to someone else.
In a nutshell, I noticed that the SimpleMappingExceptionResolver was handling the exception and resolving it with a default mapping. Consequently, there was no exception left to bubble up the stack to the ExceptionTranslationFilter which would redirect to the access-denied-handler.
Please see Spring Security ignoring access-denied-handler with Method Level Security for further information.
Spring Security redirect to the access denied page just when the user don't have authorization to access the resource. This is, when the user is authenticated but doesn't have the allowed roles.
But when the problem is not authorization, but authentication, Spring Security redirects to the login page (to let the user authenticate himself/herself), not to the access denied page.
As you have a rule checking for "isAuthenticated()" in the rules, you won't be redirected to the access denied page, but to the login page.
Hope it helps.
I am developing an application using the Spring Security (3.1) and I encoutered the following problem. When user logs out, I want to redirect to some custom URL depending if he logs out from a secure page or not. I wrote a custom LogoutHandler, that looks as follow:
#Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String refererUrl = request.getHeader("Referer");
if (requiredAuthentication(refererUrl, authentication)) {
response.sendRedirect(request.getContextPath());
} else {
response.sendRedirect(refererUrl);
}
}
private boolean requiredAuthentication(String url, Authentication authentication){
return !getPrivilegeEvaluator().isAllowed(url, authentication);
}
So, when the user is logging out from the non-secure page he is logged out and redirected to the same URL, and if he is logging ouf from secure page, he goes to index page.
The problem is, that Authentication object that comes to the method is always authenticated (even though, the method is called AFTER loggin out the user, acording to the specification).
My security context:
<http use-expressions="true" disable-url-rewriting="true" request-matcher-ref="requestMatcher" >
<intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" requires-channel="https" />
<intercept-url pattern="/dashboard/**" access="hasRole('ROLE_OWNER')" requires-channel="https" />
<intercept-url pattern="/**" access="permitAll"/>
<form-login login-page="/login"
authentication-success-handler-ref="successHandler"
authentication-failure-url="/login"
login-processing-url="/validate" />
<logout logout-url="/logout" invalidate-session="true" success-handler-ref="logoutSuccessHandler" />
<remember-me services-ref="rememberMeServices" key="KEY" use-secure-cookie="false" />
<session-management session-fixation-protection="migrateSession">
<concurrency-control max-sessions="1" />
</session-management>
</http>
Do you have any idea, why received Authentication is still valid, when gettig to the logoutSuccessHandler? I can't edit this object, because it's fields are final (except the isAuthenticated, but it's not checked by isAllowed() method..)
Looking at Spring Security source code, the LogoutFilter gets the Authentication object from the SecurityContextHolder, keeps it on a local variable, and removes it from the holder, via SecurityContextLogoutHandler. After all LogoutHandlers are called, it calls your LogoutSuccessHandler, and passes the Authentication object.
Even that it says it is valid, it is not anymore in the SecurityContextHolder, so for Spring, the user is logged out.
i have a method secured with spring security as follows:
#PreAuthorize("hasRole('add_user')")
public void addUser(User user) ;
and if a user with no enoguh permissions is trying to invoke it
, an accessDenied exception is thrown:
org.springframework.security.access.AccessDeniedException: Access is denied
this is what's expected, but the question is, why the defined access-denied-handler
in security.xml configuration file is not working:
<access-denied-handler error-page="accessDenied"/>
I mean by not working that when user with not enough permission when trying to press the button addUser which will invoke the service addUser (that's only accessible by user has this permission) an AccessDenied Exception is thrown and that's the desired behavior, but the user isn't redirected to the access denied exception as configured in xml.
shouldn't the user gets redirected automatically to access denied page when this exception is thrown, or i have to define such behavior explicitly in code ?
please advise.
I am using Spring Security 3.0.5 with JSF 2.1 and ICEFaces 2
UPDATE: applicationSecurity.xml:
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.4.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.1.xsd">
<!-- Enable #pre, #post spring security method level annotations -->
<global-method-security pre-post-annotations="enabled" />
<http use-expressions="true" auto-config="true" access-denied-page="/accessDenied">
<session-management session-fixation-protection="none"/>
<remember-me token-validity-seconds="1209600"/>
<intercept-url pattern="/accessDenied" access="permitAll"/>
<intercept-url pattern="/login" access="permitAll"/>
<intercept-url pattern="/j_spring_security_check" access="permitAll" />
<intercept-url pattern="/faces/javax.faces.resource/**" access="permitAll" />
<intercept-url pattern="/xmlhttp/**" access="permitAll" />
<intercept-url pattern="/resources/**" access="permitAll" />
<intercept-url pattern="/scripts/**" access="permitAll" />
<intercept-url pattern="/images/**" access="permitAll" />
<intercept-url pattern="/css/**" access="permitAll" />
<!-- All pages requires authentication (not anonymous user) -->
<intercept-url pattern="/**" access="isAuthenticated()" />
<intercept-url pattern="/faces/**" access="isAuthenticated()" />
<form-login default-target-url="/"
always-use-default-target="true"
login-processing-url="/j_spring_security_check"
login-page="/login"
authentication-failure-url="/login?login_error=1"
/>
<logout logout-url="/logout" logout-success-url="/login" />
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="userDetailsServiceImpl"/>
</authentication-manager>
</beans:beans>
UPDATE 2 : debugs before exception:
DEBUG [http-bio-8080-exec-1] (PrePostAnnotationSecurityMetadataSource.java:93) - #org.springframework.security.access.prepost.PreAuthorize(value=hasRole('add_user')) found on specific method: public void com.myapp.service.impl.UserServiceImpl.addUser(com.myapp.domain.User) throws java.lang.Exception,org.springframework.security.access.AccessDeniedException
DEBUG [http-bio-8080-exec-1] (DelegatingMethodSecurityMetadataSource.java:66) - Adding security method [CacheKey[com.myapp.service.impl.UserServiceImpl; public abstract void com.myapp.service.UserService.addUser(com.myapp.domain.User) throws java.lang.Exception,org.springframework.security.access.AccessDeniedException]] with attributes [[authorize: 'hasRole('add_user')', filter: 'null', filterTarget: 'null']]
DEBUG [http-bio-8080-exec-1] (AbstractSecurityInterceptor.java:191) - Secure object: ReflectiveMethodInvocation: public abstract void com.myapp.service.UserService.addUser(com.myapp.domain.User) throws java.lang.Exception,org.springframework.security.access.AccessDeniedException; target is of class [com.myapp.service.impl.UserServiceImpl]; Attributes: [[authorize: 'hasRole('add_user')', filter: 'null', filterTarget: 'null']]
DEBUG [http-bio-8080-exec-1] (AbstractSecurityInterceptor.java:292) - Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#c650d918: Principal: org.springframework.security.core.userdetails.User#db344023: Username: user#mycomp.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: access_viewUsers; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: E6BBAC0CD4499B1455227DC6035CC882; Granted Authorities: access_viewUsers
DEBUG [http-bio-8080-exec-1] (AffirmativeBased.java:53) - Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter#1d1e082e, returned: -1
DEBUG [http-bio-8080-exec-1] (AffirmativeBased.java:53) - Voter: org.springframework.security.access.vote.RoleVoter#1eab12f1, returned: 0
DEBUG [http-bio-8080-exec-1] (AffirmativeBased.java:53) - Voter: org.springframework.security.access.vote.AuthenticatedVoter#71689bf1, returned: 0
According to the spring Security documentation the user of the access-denied-page attribute of the element has been deprecated in Spring 3.0 and above.
We do the following in our app:
Create a custom access denied handler by extending the Spring Security framework's AccessDeniedHandlerImpl.
Call the setErrorPage method, passing in the name of the controller that will display your access denied page
In our case we lock the user's account in the custom handler - there's no good reason for any user to ever get an access denied exception unless they're doing something that they should't. We also log what they were trying to access, etc.
Call super.handle(_request, _response, _exception); at the end of the handler. Spring will forward control to the controller listed in #2 above.
public class AccessDeniedHandlerApp extends AccessDeniedHandlerImpl {
private static Logger logger = Logger.getLogger(AccessDeniedHandlerApp.class);
private static final String LOG_TEMPLATE = "AccessDeniedHandlerApp: User attempted to access a resource for which they do not have permission. User %s attempted to access %s";
#Override
public void handle(HttpServletRequest _request, HttpServletResponse _response, AccessDeniedException _exception) throws IOException, ServletException {
setErrorPage("/securityAccessDenied"); // this is a standard Spring MVC Controller
// any time a user tries to access a part of the application that they do not have rights to lock their account
<custom code to lock the account>
super.handle(_request, _response, _exception);
}
}
Here's my XML: AccessDeniedHandlerApp extends 'AccessDeniedHandlerImpl`
<http auto-config='true'>
<intercept-url pattern="/views/**" access="ROLE_USER" />
<form-login login-page="/Login.jsp" authentication-success-handler-ref="loginSuccessFilter"
authentication-failure-handler-ref="loginFailureFilter" />
<logout logout-success-url="/home" />
<access-denied-handler ref="customAccessDeniedHandler"/>
</http>
<beans:bean id="customAccessDeniedHandler" class="org.demo.security.AccessDeniedHandlerApp"/>
Here's my Access Denied Controller - I should have posted this earlier - sorry about that. In order to get the access denied page to come up I had to use a redirect:
#Controller
public class AccessDeniedController {
private static Logger logger = Logger.getLogger(AccessDeniedController.class);
#RequestMapping(value = "/securityAccessDenied")
public String processAccessDeniedException(){
logger.info("Access Denied Handler");
return "redirect:/securityAccessDeniedView";
}
#RequestMapping(value = "/securityAccessDeniedView")
public String displayAccessDeniedView(){
logger.info("Access Denied View");
return "/SecurityAccessDenied";
}
Please let me know if this doesn't resolve it and I'll keep digging - I just tested it again locally here and this should do the trick.
}
I have run into the same problem myself and posted another question relating to the same issue. After several hours of digging around, I finally found the solution for my issue. I know this question is 2+ years old, but thought I would update it with information in case it was of value to someone else.
In a nutshell, I noticed that the SimpleMappingExceptionResolver was handling the exception and resolving it with a default mapping. Consequently, there was no exception left to bubble up the stack to the ExceptionTranslationFilter which would redirect to the access-denied-handler.
Please see Spring Security ignoring access-denied-handler with Method Level Security for further information.
Spring Security redirect to the access denied page just when the user don't have authorization to access the resource. This is, when the user is authenticated but doesn't have the allowed roles.
But when the problem is not authorization, but authentication, Spring Security redirects to the login page (to let the user authenticate himself/herself), not to the access denied page.
As you have a rule checking for "isAuthenticated()" in the rules, you won't be redirected to the access denied page, but to the login page.
Hope it helps.