JSF2 with SpringSecurity: handle AccessDeniedException of #Secured-Annotation - spring

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)

Related

Secure rest api using spring security - custom status codes

I am using spring security (4.0.0.M2) to secure my web application and also my rest api. everything works great. but I have one problem. I want to return http result 403 (forbidden) instean of 401, when user could not be authenticated
I defined different http definitions, for each authentication scenario, one for web and one for api.
<http pattern="/rest/v?/**" auto-config="false" use-expressions="true"
disable-url-rewriting="true" authentication-manager-ref="tokenAuthenticationManager"
create-session="stateless" realm="API security check"
entry-point-ref="http403EntryPoint">
<intercept-url pattern="/rest/v?/*" access="isAuthenticated()" />
<anonymous enabled="false" />
<http-basic />
</http>
<authentication-manager id="tokenAuthenticationManager" erase-credentials="true">
<authentication-provider user-service-ref="tokenUserDetailsService" />
</authentication-manager>
public class TokenUserDetailsService implements UserDetailsService {
#Override
public org.springframework.security.core.userdetails.UserDetails loadUserByUsername(String checkUser)
throws UsernameNotFoundException {
// lookup user
if (user == null) {
// here I whant to return 403 instead of 401
throw new UsernameNotFoundException("User not found");
}
}
}
Could somebody help to return http status code 403 in this case?
Thank you for help.
Best regards
sonny
The BasicAuthenticationFilter which is created by the <http-basic> element also has a reference to an entry point. It invokes this when authentication fails and this is where the 401 code comes from (which is normal with Basic authentication).
If you want to change it you can use the entry-point-ref namespace attribute. So
<http-basic entry-point-ref="http403EntryPoint" />
should give the result you want.
Currently I found out a working solution. When I use a custom filter (position=pre_auth), then I could change the response code using doFilter method.
But I am not sure, if this is really the best solution.

Spring custom AuthenticationFailureHandler

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());
%>

Spring Security custom LogoutSuccessHandler gets strange Authentication object

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.

Spring Security hasRole('ROLE_ADMIN') in config and #PreAuthorize("permitAll") not working?

I'm trying to lock down my entire app except a particular URL/method.
Here's my applicationContext-security.xml:
<global-method-security pre-post-annotations="enabled"/>
<http use-expressions="true">
<http-basic/>
<intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')" />
<logout logout-success-url="/products" />
</http>
Here's the class with its annotation:
#RooWebScaffold(path = "products", formBackingObject = Product.class)
#RequestMapping("/products")
#Controller
public class ProductController {
#RequestMapping(value="/json", headers = "Accept=application/json")
#ResponseBody
#PreAuthorize("permitAll")
public String listJson() {
return Product.toJsonArray(Product.findAllProducts());
}
}
However, it's not working as expected.
If I swap the conditions around and have permitAll in the config and the hasRole() in the annotation it works as expected - but I'm trying to achieve the reverse.
Any advice would be greatly appreciated!
If you are using XML Configuration don't forget to add the following attribute:
<s:global-method-security pre-post-annotations="enabled"/>
If you are using Java Configuration don't forget to add the following annotation:
#EnableGlobalMethodSecurity(prePostEnabled = true)
It is almost similar to question spring security 3 - Setting up a customized login, if you restrict all access (pattern /**) to role_admin then how the permitAll on /product would work? Solution would be to provide IS_AUTHENTICATED_ANONYMOUSLY access on /product.
From #PreAuthorize and intercept-url priority
:
<intercept-url> ... takes precedence over (#PreAuthorize) annotations. [, since] <intercept-url> works at URL level and annotations at method level.
So the solution to your problem (apart from #PreAuthorize annotations) must be addressed in your security config.
You have to declare /prodcuts/json intercept url with permitAll (or anonymous..) before /** intercept url pattern like:
...
<intercept-url pattern="/products/json" access="permitAll" />
<intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')" />
...
From https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#nsa-intercept-url:
When matching the specified patterns against an incoming request, the matching is done in the order in which the elements are declared. So the most specific patterns should come first and the most general should come last.

Method #Secured supposed to throw error when no user authenticated

The code for my service follows...
#Controller
#GwtRpcEndPoint
public class ServerServiceImpl implements ServerService {
#org.springframework.security.annotation.Secured("ROLE_ADMIN")
public String runGwtSprMvcHibJpaDemo(String s) {
System.out.println("SecurityContextHolder.getContext()="+SecurityContextHolder.getContext());
System.out.println("SecurityContextHolder.getContext().getAuthentication()="+SecurityContextHolder.getContext().getAuthentication());
}
}
my applicationContext.xml
<security:global-method-security secured-annotations="enabled" jsr250-annotations="disabled" />
but when i call the serviceImpl through gwt-rpc, aren't runGwtSprMvcHibJpaDemo supposed to print out security error since user not yet authenticated? Rather, the method runGwtSprMvcHibJpaDemo is executed with output
SecurityContextHolder.getContext()=org.springframework.security.context.SecurityContextImpl#ffffffff: Null authentication SecurityContextHolder.getContext().getAuthentication()=null
Add
<security:http auto-config="true">
<security:intercept-url pattern="/**" access="ROLE_ADMIN" />
</security:http>
to your xml config and see if that fixes it.
Define bean in your spring context like:
bean id="userDetailsService"
class="packagename.MyUserService">.
Please note that bean name should be extactly same. Spring use this bean internally to start this service.
MyUserService is a implementation of UserDetailsService.

Resources