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.
Related
I have an application using Spring Security 4.2.18 with the security configured in xml. Right now I am introducing Spring Boot 2.5.4 in the project. Since upgrading, I have a problem with the configuration of security for some requests.
I have a block matching specific requests and one matching the rest of all requests.
<http pattern="/api/**" use-expressions="true" authentication-manager-ref="apiAuthenticationManager" >
<http-basic/>
<intercept-url pattern="/api/**" access="hasRole('ROLE_API')"/>
<csrf disabled="true"/>
</http>
<http pattern="/**" auto-config="true" use-expressions="true" create-session="always" disable-url-rewriting="true"
authentication-manager-ref="authenticationManager">
<form-login login-processing-url="/resources/j_spring_security_check" login-page="/login"
username-parameter="j_username"
password-parameter="j_password"
authentication-failure-url="/login?login_error=t" default-target-url="/redirectlogin"/>
<logout logout-url="/resources/j_spring_security_logout"/>
...
<intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>
...
</http>
<authentication-manager id="apiAuthenticationManager">
<authentication-provider user-service-ref="apiUserDetailsService">
</authentication-provider>
</authentication-manager>
The ApiUserDetailsService follows the specification:
#Service
#Transactional
public class ApiUserDetailsService implements UserDetailsService {
...
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
boolean foundAccount = apiConfig.getUsername().equals(username);
if (foundAccount) {
return new User(username, apiConfig.getPassword(), singletonList(new SimpleGrantedAuthority("ROLE_API")));
}
throw new UsernameNotFoundException("Could not finder user with name " + username);
}
}
If I make a request to something under /api and use incorrect Basic auth credentials, I previously got a 401 Unauthorized response. Since upgrading I get redirected to /login and end up in a redirect loop since the credentials are used there as well.
If I remove the <intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/> in the second <http> block, I get the expected behaviour.
How can I configure my application so that my request doesn't try to authorize using the rules in the second <http> block? I have tried using an EntryPoint but it isn't called before the user is erroneously authorized using the method of the generic <http> block.
Have you tried putting <intercept-url pattern="/login*" access="permitAll" /> right above your <intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/> like:
In the second http block:
...
<intercept-url pattern="/login*" access="permitAll" />
<intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>
...
The problem wasn't that the configurations were overlapping but rather that the failed authentication was redirected to /login which in turn was handled by the second <http> configuration.
I solved it by creating an implementation of AuthenticationEntryPoint that sets the 401 Unathorized status on the response:
#Component
public class BasicAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
}
and in turn referencing to this in my basic authentication configuration:
<http pattern="/api/**" use-expressions="true" authentication-manager-ref="apiAuthenticationManager" >
<http-basic entry-point-ref="basicAuthenticationEntryPoint"/>
<intercept-url pattern="/api/**" access="hasRole('ROLE_API')"/>
<csrf disabled="true"/>
</http>
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());
%>
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 am using Spring Security 3.0.4. I have a bunch of web service which are protected by Spring Security. When I access them as an unauthenticated user, Spring Security redirects to login page. Instead of that, I want to return HTTP 403 error. How can I achieve that?
Here is my security config:
<http auto-config="false" use-expressions="true" >
<intercept-url pattern="/authorization.jsp" access="permitAll"/>
<intercept-url pattern="/registration.jsp" access="permitAll"/>
<intercept-url pattern="/api/authorization/auth" access="permitAll"/>
<intercept-url pattern="/api/authorization/new" access="permitAll"/>
<intercept-url pattern="/api/accounts/new" access="permitAll"/>
<intercept-url pattern="/app/**" access="permitAll"/>
<intercept-url pattern="/extjs/**" access="permitAll"/>
<intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<form-login login-page="/authorization.jsp"
default-target-url="/index.jsp"
authentication-failure-url="/registration.jsp?login_error=1"
always-use-default-target="true"
/>
<logout logout-success-url="/authorization.jsp"
logout-url="/j_spring_security_logout"
invalidate-session="true"/>
</http>
For java configuration you need to do
http.exceptionHandling().authenticationEntryPoint(alwaysSendUnauthorized401AuthenticationEntryPoint);
Where alwaysSendUnauthorized401AuthenticationEntryPoint is innstance of class
public class AlwaysSendUnauthorized401AuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public final void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
LOGGER.debug("Pre-authenticated entry point called. Rejecting access");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
This disables default behavior of Spring (redirecting unauthenticated requests to login form).
Side note:
for such case HTTP code SC_UNAUTHORIZED(401) is better choice than SC_FORBIDDEN(403).
There's an article on the spring forums here that outlines how to get your app determining between the two methods. So far I'm using the following code to secure my data controllers:
<bean id="ep403" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
<sec:http pattern="/data/**" entry-point-ref="ep403" use-expressions="true">
<sec:intercept-url pattern="/**" access="isAuthenticated()"/>
</sec:http>
<bean id="epauth" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<constructor-arg value="/login.html"/>
</bean>
<sec:http pattern="/**" entry-point-ref="epauth" use-expressions="true">
<sec:intercept-url pattern="/**" access="isAuthenticated()"/>
</sec:http>
So the whole DelegatingAuthenticationEntryPoint solution in the article I linked is a bit more heavyweight, but I imagine it does the job just fine as well.
you need to
Create a RequestMatcher to determine which requests should get a 403 (AntPathRequestMatcher may suffice in your case).
Configure the HttpSessionRequestCache to check the matcher and not store those pages for post-login redirect.
Use a DelegatingAuthenticationEntryPoint to either 403 the request outright or redirect to login according to the matcher.
See the example here:
http://distigme.wordpress.com/2012/11/01/ajax-and-spring-security-form-based-login/
This solution worked for me (reference)
http
...
.oauth2Login().permitAll()
.and().exceptionHandling()
.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"))
...
It should return a 403 error unless you configure it to go to another url with this tag:
<sec:access-denied-handler error-page="/urlToGoIfForbidden" />