GWT + Spring Security login issue after logout - spring

I'm using Spring Security to secure access to my GWT's first page application "/HumanResources.html". It checks if the credentials of the user are correct (by comparing them with ldap content) and look if the user exists in database (custom authorized table).
First time user login, there is no trouble, then the user logout and a ".jsp" page is displayed.
But when he wants to access again to "HumanResources.html" page, the authentication is apparently ignored (login form isn't displayed) and the page is displayed. Only the interface is visible, data is retrieved by secured RPC services.
This problem appears on external Tomcat Server (tested on Firefox and Chrome), but not on GWT Dev mode. CTRL + F5 seems to work, and I looked for other cache issues but it didn't help.
Can anyone help me ?
A part of my security-applicationContext.xml :
<http use-expressions="true" auto-config="false">
<intercept-url pattern="/HumanResources.html" access="isAuthenticated()" />
<form-login
login-page='/login.jsp'
authentication-failure-url = "/login.jsp?login_error=1"
authentication-success-handler-ref="HRAuthenticationHandler" />
<logout
logout-url="/logout"
logout-success-url="/logout.jsp"
delete-cookies="JSESSIONID"/>
</http>
<beans:bean id="HRAuthenticationHandler" class="lu.sfeir.candidate.server.auth.HRAuthenticationHandler">
<beans:property name="useReferer" value="true" />
</beans:bean>
<ldap-server url="${ldap.serverUrl}" manager-dn="${ldap.adminLogin}" manager-password="${ldap.adminPassword}" />
<authentication-manager>
<ldap-authentication-provider
group-search-base="${ldap.groups}"
user-search-base="${ldap.users}"
user-search-filter="${ldap.userId}">
</ldap-authentication-provider>
</authentication-manager>
and my custom AuthenticationHandler implementation :
public class HRAuthenticationHandler extends SavedRequestAwareAuthenticationSuccessHandler {
#Autowired
private AuthorizedUsersDao usersDao;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException,
ServletException {
// Check if the user exist in the DB
if(usersDao.findUser(((UserDetails)authentication.getPrincipal()).getUsername())) {
// Redirect to home page
super.onAuthenticationSuccess(request, response, authentication);
} else {
// Redirect to error page
response.sendRedirect("/spring_security_login?login_error");
}
}
}

Edit:
Sorry for missleading You.
I dig a bit and found that security doesnt work cause user browser is caching xxx.html page, js,css etc
You can:
1) force refresh page by setting html headders :
Thats the simplest solution, and since You are using secure RPC services that should be enaught for You.
Or disable caching at all.
Mind it will increase Your network traffic.
2)
our application is checking if security error ocured in RPC:
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.InvocationException;
public abstract class CustomAsyncCallback<T> implements AsyncCallback<T> {
#Override
public void onFailure(Throwable caught) {
if (caught instanceof InvocationException) {
InvocationException ie = (InvocationException) caught;
if(ie.getMessage().contains("j_spring_security_check")) {
Window.alert("User session expired, please login again");
Window.open(GWT.getHostPageBaseURL() + "login.jsp", "_self", null);
return;
}
}
}
}
You must call it somewhere ie while displaying You current logged user:
userSecurityService.getUser(new CustomAsyncCallback<String>() {
#Override
public void onSuccess(String result) {
usrLoginLbl.setText(result);
}
});
or You may catch this exception to one of yours onFailure method

Related

Spring: Check in code if url has security set to none

It is possible to check in Spring Interceptor preHandle() method if requested URL is secured by Spring Security or not (has set security="none") ?
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(isSecured(request) && !paymentRegistered())
response.sendRedirect("/payment")
return super.preHandle(request, response, handler);
}
private boolean isSecured(HttpServletRequest request){
//how to check if url has security=none
}
My problem is that after successful login I want to check if user has payed for service. If not I want to redirect to payment page. My idea is to write custom request scope filter or interceptor and check if user has registered payment in database. Problem is that I do not want to filter non secured URLs such as resources, login page, error pages etc. Also payment page (which is secured) should be available.
Maybe better idea is to write custom security filter and add custom flag to Principal object such as servicePayed alongside with other security flags: enabed, accountNonExipired etc.
I would do it writing a custom AuthenticationSuccessHandler, mainly based in the simple implementation SimpleUrlAuthenticationSuccessHandler.
In your implementation, you should overwrite onAuthenticationSuccess method, and there check if you should redirect the user to the payment page or not.
/**
* Calls the parent class {#code handle()} method to forward or redirect to the target
* URL, and then calls {#code clearAuthenticationAttributes()} to remove any leftover
* session data.
*/
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
if(mustCompletePayment(authentication)){
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
}
Then just write a kind of mustCompletePayment using the authentication object, from which you must be able to check if the user must complete payment or not, or if you already made a custom UserDetailsService to check it during authentication, just check that indicator in your authentication object
EDIT:
If what you really want to do is to avoid any action for the logged user while he does not complete the payment, I would manage with granted authorities.
As I see, the key here is to translate the fact that the user has yet not paid into the authorization layer in a way you could take advantage of it.
You already have implemented the logic to discover if a user has completed payment information or not, so you could write your own UserDetailsService, so in the
UserDetails loadUserByUsername(String username)throws UsernameNotFoundException
you could check that and in case the user has not complete the payment, just erase any returning granthedAuthority from the UserDetails and let only one stating that the user must complete the payment, let's say ROLE_USER_HAS_NOT_PAID.
Then, in security http config (this is xml version but maybe you are using java config), make such a kind of mappings:
<security:http ...>
...
<security:intercept-url pattern="/secured/payment/**" access="ROLE_USER,ROLE_USER_HAS_NOT_PAID" />
<security:intercept-url pattern="/secured/**" access="ROLE_USER_HAS_PAID" />
...
</security:http>
With this config, payment page would be accessible for any user, wherever the user has paid or not, while other pages are available only for users who had already paid. Only, be carefull as you must renew the user's granthed authorities once the user has paid to made him available every page.
This way, the AuthenticationSuccessHandler should not eval other than the user granthed authorities to decide where to redirect the user. I have made this several times by building a AuthenticationSuccessHandler based on a ordered map where I configured a landing page for each of the granthed authorities which need their own landing page.
Now any logged user action is forbidden if he has cont complete payment, so a HTTP 403 would be raised while trying to access any other secured resource. But you want don't want just to block the user from doing anything else, you want to redirect it to the payment page. Here you need an AccessDeniedHandler, where you could do more or less the same check:
public class CustomAuthenticationAccessDeniedHandler extends
AccessDeniedHandlerImpl implements AccessDeniedHandler {
private String errorPage = "/error/403";
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void handle(HttpServletRequest arg0, HttpServletResponse arg1,
AccessDeniedException arg2) throws IOException, ServletException {
SecurityContext context = SecurityContextHolder.getContext();
if(context.getAuthentication() != null && context.getAuthentication().isAuthenticated()){
if(context.getAuthentication().getAuthorities().contains("ROLE_USER_HAS_NOT_PAID")){
this.redirectStrategy.sendRedirect(arg0, arg1, "/secured/payment/pay");
return;
}
}
this.redirectStrategy.sendRedirect(arg0, arg1, this.getErrorPage());
}
public RedirectStrategy getRedirectStrategy() {
return redirectStrategy;
}
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
}
#Override
public void setErrorPage(String errorPage) {
this.errorPage = errorPage;
}
public String getErrorPage() {
return errorPage;
}
}
This way you would redirect users which still must pay to your payment page and in any other case to a default 403 page
Don't know if there's a way to get such information from Spring Security. But maybe if you do not have a lot of urls which are not secured than you can do something like this:
private boolean isSecured(HttpServletRequest request) {
String requestUri = request.getRequestURI();
return !(request.getMethod().equalsIgnoreCase("GET")
&& (requestUri.contains("error/")
|| requestUri.startsWith("resources/"))
);
}
Or move those non-secured resources to some common start path and use the idea described in the code above.
Maybe you will find a way to do that, but IMHO you should not, because it is likely to require to dive in Spring Security internals.
If you want to only use Spring Security the way it was designed for, you could implement a custom AccessDecisionVoter. For example, if could only vote for one single security attributes starting with PAYMENT. You put that security attribute in spring security configuration:
<security:intercept-url pattern="/..." access="PAYMENT,ROLE_ADMIN"/>
to restrict access to user having payed or having the ADMIN role
To declare a custom voter, you must replace the default access decision manager. First you declare it:
<bean id="accessDecisionManager"
class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg>
<list>
<bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
<bean class="org.springframework.security.access.vote.RoleVoter"/>
<bean class="your.package.PaymentVoter"/>
</list>
</constructor-arg>
</bean>
Then you insert it in your <http> element:
<http access-decision-manager-ref="accessDecisionManager"/>

Getting logged in users with sessionRegistry not work when manually authenticate

I use spring security3 and spring mvc3 to build an web project. There is page called index.jsp, login user name and online user count will be displayed on
the top of this screen. There are 2 ways to login the system:
from login page, use default configuration post by 'j_spring_security_check'
ajax login with manually authentication
When I use login page to login into index page, both of count of online information and user name show correctly.
But when I use ajax login (manually authenticate), problem occurs: count of online user don't updated, it always displaying 0 while user name can show properly.
Part of the controller:
#Autowired
#Qualifier("authenticationManager")
AuthenticationManager authenticationManager;
#Autowired
SecurityContextRepository repository;
#RequestMapping(value="/ajaxLogin")
#ResponseBody
public String performLogin(
#RequestParam("j_username") String username,
#RequestParam("j_password") String password,
HttpServletRequest request, HttpServletResponse response) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
try {
Authentication auth = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
repository.saveContext(SecurityContextHolder.getContext(), request, response);
logger.info("Authentication successfully! ");
return "{\"status\": true}";
} catch (BadCredentialsException ex) {
return "{\"status\": false, \"error\": \"Bad Credentials\"}";
}
}
spring-security.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.3.xsd">
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/login" access="permitAll" />
<intercept-url pattern="/index" access="permitAll" />
<form-login login-page="/login" default-target-url="/index"
authentication-failure-url="/loginfailed" />
<logout logout-success-url="/logout" />
<session-management invalid-session-url="/index">
<concurrency-control max-sessions="1"
error-if-maximum-exceeded="false" />
</session-management>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="
select login_id,login_pwd, is_enabled
from t_user where login_id=?"
authorities-by-username-query="
select u.login_id, r.authority from t_user u, t_roles r
where u.u_id = r.u_id and u.login_id =? " />
</authentication-provider>
</authentication-manager>
Method I used to get online login user count:
public class BaseController {
protected Logger logger = Logger.getLogger(this.getClass());
#Autowired
SessionRegistry sessionRegistry;
#ModelAttribute("numUsers")
public int getNumberOfUsers() {
logger.info("in getNumberOfUsers() ...");
return sessionRegistry.getAllPrincipals().size();
}
}
Code used to show login user name:
<div>
<security:authorize ifAllGranted="ROLE_USER">
<p>Welcome <security:authentication property="principal.username" />!
Logout</p>
</security:authorize>
</div>
code used to show count of logged in users:
<div style="color:#3CC457">
${numUsers} user(s) are logged in!
</div>
I guess that because when I manually authenticate, spring security not create new session for the user. I validate it by write customized SessionCounterListener.
public class SessionCounterListener implements HttpSessionListener {
private Logger logger = Logger.getLogger(this.getClass());
private static int totalActiveSessions;
public static int getTotalActiveSession(){
return totalActiveSessions;
}
#Override
public void sessionCreated(HttpSessionEvent event) {
totalActiveSessions++;
logger.info("sessionCreated - add one session into counter" + event.getSession().getId());
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
totalActiveSessions--;
logger.info("sessionDestroyed - deduct one session from counter" + event.getSession().getId());
}
}
Below is key content of log file for the action sequence: normal login -> normal logout -> ajax login -> ajax logout.
sessionDestroyed - deduct one session 1spueddcmdao019udc43k3uumw
sessionCreated - add one session 14nro6bzyjy0x1jtvnqjx31v1
sessionDestroyed - deduct one session 14nro6bzyjy0x1jtvnqjx31v1
sessionCreated - add one session e6jqz5qy6412118iph66xvaa1
Actually, ajax login/logout not give any output.
So now, how can I get correct login user count? And why the different authenticate ways has different method to deal with session? Any help will be appreciated.
As you are manually adding Principal to SecurityContext, it will not add user to SessionRegistry. You need to add user session to SessionRegistry manually.
SecurityContextHolder.getContext().setAuthentication(auth);
sessionRegistry.registerNewSession(request.getSession().getId(), auth.getPrincipal());
Hope it helps!!
In your Spring spring-security.xml file, the URL for the AJAX authentication (/ajaxLogin) is not explicitly allowed. Thus the request should be blocked by Spring. I would suggest to add this:
<intercept-url pattern="/ajaxLogin" access="permitAll" />

How to pass request parameter to 'default-target-url'

I am setting 'cat=1' in the hidden field in login.jsp page and was expecting it to be available on the default-target-url. Entry in spring-security.xml is,
<form-login login-page="/login.html" default-target-url="/index.html"
authentication-failure-url="/loginfailed.html" />
and in the controller,
#RequestMapping(value="/index", method = RequestMethod.GET)
public String index(HttpServletRequest request) {
String cat = request.getParameter("cat");
if (cat != null && cat.equalsIgnoreCase("1")) {
return "add";
}
return "redirect:/index.jsp";
}
but cant get request parameter value (cat is null) so I believe it is because 'default-target-url' redirects the request (and does not forward it?). Is it the case?
If yes then is there any way I can pass parameter to the 'default-target-url'?
I have changed implementation approach a bit. Details give below,
spring-security.xml
<form-login login-page="/login.html" authentication-success-handler-ref="feedSuccessHandler"
authentication-failure-url="/loginfailed.html" />
<logout logout-success-url="/loggedout.html"/>
<beans:bean id="feedSuccessHandler"
class="main.java.com.sp.utilities.FeedSuccessHandler">
</beans:bean>
FeedSuccessHandler.java
public class FeedSuccessHandler implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String cat = request.getParameter("cat");
if (cat != null && cat.equalsIgnoreCase("1")) {
response.sendRedirect(request.getContextPath()+"/add.html");
}else{
SavedRequest savedRequest = new HttpSessionRequestCache().getRequest(request, response);
if(savedRequest != null) {
response.sendRedirect(savedRequest.getRedirectUrl());
}else{
response.sendRedirect(request.getContextPath()+"/");
}
}
}
}
Application is working as desired also in future if I want to customize redirection based on roles, I can use same class.
It does redirect by defult, but there are a couple configuration options you can use to change this behavior. Both of them is defined on the AbstractAuthenticationTargetUrlRequestHandler which is the parent class of the two existing authentication success handler implementations (by default SavedRequestAwareAuthenticationSuccessHandler is used by the namespace configuration).
Set its targetUrlParameter property, so that it will check if the HTTP request has a parameter with that name. If so, it will redirect to the URL given in that request parameter.
Or set a custom redirectStrategy. The default implementation calls response.sendRedirect(), but you can change that as you like in your custom implementation.
You will have some difficulty though, because neither of these configuration points are exposed through the namespace configuration, so you will need to go a level deeper, and write the bean definitions manually.
The redirect is controlled by the Redirect Strategy definined in the redirectStrategy property of SimpleUrlAuthenticationSuccessHandler.
The Default for redirectStrategy is an instance of DefaultRedirectStrategy.
What you need to do is to implement you own redirectStrategy (implements RedirectStrategy).
And then configure it:
...
<bean id="usernamePasswordAuthenticationFilter">
...
<property name="authenticationSuccessHandler">
<bean
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<property name="redirectStrategy">
<bean class="yourRedirectStrategy"/>
<property>
</bean>
</property>
</bean>

How to obtain a referrer URL with Spring MVC 3.1

I am building a website that has 2 pages. A recipe list page, a recipe detail page, and a sign in page. A user can sign in to the website by clicking on a sign in button on the recipe list page or recipe detail page. When a user click the sign in button, the user will be brought to the sign in page. I would like to redirect the user back again to the recipe detail page if they click the sign in button from the recipe detail page, or to the recipe list page if they click the sign in button from the recipe list page.
I wrote this method in a Controller class. This method will be called whenever user sign in to the website. I stored the referer URL into the session. The intention of saving this URL into a session is to keep track the page where user click on the sign in button. And also to redirect user to that page in the authentication handler that I wrote.
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String openLoginPage(Model uiModel, HttpServletRequest request) {
String referrer = request.getHeader("Referer");
request.getSession().setAttribute("url_prior_login", referrer);
return RECIPE_LOGIN_PAGE;
}
I also created an authentication handler class called SuccessHandler.
public class SuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
/*
* (non-Javadoc)
*
* #see org.springframework.security.web.authentication.
* SavedRequestAwareAuthenticationSuccessHandler
* #onAuthenticationSuccess(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse,
* org.springframework.security.core.Authentication)
*/
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
String url = (String) request.getSession().getAttribute("url_prior_login");
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal != null && principal instanceof RecipeUser) {
request.getSession().setAttribute("RecipeUser", (RecipeUser) principal);
}
getRedirectStrategy().sendRedirect(request, response, url);
}
}
This class will redirect user to either the recipe list page or the recipe detail page when they sign in to the website. I register this class inside a security-context.xml file:
<http use-expressions="true" auto-config="false" entry-point-ref="authenticationEntryPoint">
<intercept-url pattern="/login" access="permitAll" />
<form-login login-page="/login" authentication-failure-url="/loginfail"
default-target-url="/login"
authentication-success-handler-ref="successHandler" />
<logout logout-success-url="/" />
</http>
<authentication-manager alias="authManager">
<authentication-provider user-service-ref='myUserDetailsService' />
</authentication-manager>
<beans:bean id="myUserDetailsService" class="com.safe.stack.service.security.UserService">
<beans:property name="dataSource" ref="dataSource" />
</beans:bean>
<beans:bean id="successHandler"
class="com.safe.stack.service.security.SuccessHandler" />
<beans:bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/login"/>
</beans:bean>
Is this a good way to do what I want to achieve ? Is there a better way to do this ? I could not find any example on how to this using Spring MVC.
Thank you
This is how I do it in Spring 3.1.4
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
// Supplies the default target Url that will be used if
// no saved request is found in the session
setDefaultTargetUrl("/member/dashboard");
super.onAuthenticationSuccess(request, response, authentication);
}
Extending SavedRequestAwareAuthenticationSuccessHandler is correct since the redirection is done automatically.
Just add new attribute "always-use-default-target" to "form-login" tag and set it to "false" (by default it is set to "true", even if don't specify it) like this:
<form-login
login-page="/login" authentication-failure-url="/loginfail"
default-target-url="/login"
authentication-success-handler-ref="successHandler"
always-use-default-target="false"
/>
You can read more about it in Spring Security documentation here, on end of section: 3.2.3.
If you add this, you won't need custom onAuthenticationSuccess anymore and you won't need to store anything in session on login page.

Spring Security: put additional attributes(properties) in the session on success Authentication

Just simple question: what is the best way to add attributes(properties) to the HttpSession on success authentication? The userID for example.
For now i'm using my own SimpleUrlAuthenticationSuccessHandler implementation in UsernamePasswordAuthenticationFilter and doing it like this:
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication auth)
throws IOException, ServletException {
PersonBean person = (PersonBean) auth.getPrincipal();
request.getSession().setAttribute("currentUserId", person .getId().toString());
super.onAuthenticationSuccess(request, response, auth);
But I dont think this is good approach as there is another ways to do authentication(RememberMe for example).
So what do I need to use here?
The answer was given on spring forum. Link.
Generally, need to implement an ApplicationListener which listens for succes events and put additional attributes in the session there.
But in my case its not required to store attributes in the session. I can retrieve userID like here:
var userId = ${pageContext.request.userPrincipal.principal.id}
Spring does all this for you, you'll have to create a table *persistent_logins*, here is a snippet from app context that might help. And the official doc's lay describe in detail what is required :
<security:http auto-config='true'>
<security:intercept-url pattern="/**" access="ROLE_USER" />
<security:form-login login-page="/Login"
authentication-success-handler-ref="authenticationSuccessHandler"
authentication-failure-url="/Login?login_error=1" />
<security:remember-me data-source-ref="dataSource"
user-service-ref="myUserService" />
</security:http>
and then you can access the principal object from your anywhere in your app, eg below shows the tag to output username in jsp :
<sec:authentication property="principal.username" />
and from your java code this can be done :
MyUser user = (MyUser) authentication.getPrincipal();

Resources