Integrating spring security with Ajax calls - ajax

So I have a web app that utilises jsf and primefaces for it's front end presentation.
We are using Spring security for the login mechanism and have defined the concurrency as such
<session-management session-fixation-protection="newSession">
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true"
expired-url="multipleLogins.xhtml" />
</session-management>
The problem seems to be when a user has two logins from different browsers there are certain buttonas that perform ajax actions that do not trigger the redirect. It seems to only be buttons that submit forms or redirect to pages themselves that will recognise the multiple logins actions.
For example, this button
<p:commandButton id="retrieve" value="#{msgs.BUTTON_RETRIEVE}"
action="#{removeContactForm.retrieve}" update="#form"/>
Which retrieves things from a web service and displays them on a page will not trigger the redirect if there are multiple logins.
<p:commandButton id="remove" value="#{msgs.BUTTON_REMOVE}"
action="/pages/confirm/confirmRemove.xhtml" ajax="false" process="#this"
immediate="true" rendered="#{!empty removeContactManager and
removeContactManager.contactRolesSuccessful}" />
This button will however (as it redirects to another page)
Anyone know a way of making the webapp register those ajax calls as events, without sticking everything ajax based into a new page?

I have used the JSFRedirectStrategy written by Ben Simpson for redirecting to a session expired url when the session expires using the session management filter. Source can be found here.
I think the same can be applied here but we need to remove the namespace configuration and add some beans like this:
<http>
<custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
<custom-filter position="FORM_LOGIN_FILTER" ref="myAuthFilter" />
<session-management session-authentication-strategy-ref="sas"/>
</http>
<beans:bean id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:constructor-arg name="expiredUrl" value="/multipleLogins.xhtml" />
<!-- this permits redirection to session timeout page from javascript/ajax or http -->
<beans:property name="redirectStrategy" ref="jsfRedirectStrategy" />
</beans:bean>
<beans:bean id="myAuthFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>
<beans:bean id="sas" class=
"org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="maximumSessions" value="1" />
<beans:property name="alwaysCreateSession" value="true" />
<beans:property name="exceptionIfMaximumExceeded" value="true" />
</beans:bean>
<beans:bean id="jsfRedirectStrategy" class="com.examples.JsfRedirectStrategy"/>
<beans:bean id="sessionRegistry"
class="org.springframework.security.core.session.SessionRegistryImpl" />
Now, you can check if the request was an ajax request and then send a redirect like this in the JSFRedirectStrategy class:
Here is the code copied from the ICEfaces tutorial.
/**
* This class represents an extension to the way DefaultRedirectStrategy works.
* This class takes into account if the incoming request causing action by Spring Security
* requires a "partail-response" xml redirect instead of a response.sendRedirect().
*
* #author Ben Simpson ben.simpson#icesoft.com
*/
public class JsfRedirectStrategy implements RedirectStrategy {
protected final Log logger = LogFactory.getLog(getClass());
private boolean contextRelative;
/**
* Redirects the response to the supplied URL.
* <p>
* If <tt>contextRelative</tt> is set, the redirect value will be the value after the request context path. Note
* that this will result in the loss of protocol information (HTTP or HTTPS), so will cause problems if a
* redirect is being performed to change to HTTPS, for example.
*/
public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
String redirectUrl = calculateRedirectUrl(request.getContextPath(), url);
redirectUrl = response.encodeRedirectURL(redirectUrl);
if (logger.isDebugEnabled()) {
logger.debug("Redirecting to '" + redirectUrl + "'");
}
//we should redirect using ajax response if the case warrants
boolean ajaxRedirect = request.getHeader("faces-request") != null
&& request.getHeader("faces-request").toLowerCase().indexOf("ajax") > -1;
if(ajaxRedirect) {
//javax.faces.context.FacesContext ctxt = javax.faces.context.FacesContext.getCurrentInstance();
//ctxt.getExternalContext().redirect(redirectUrl);
String ajaxRedirectXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<partial-response><redirect url=\""+redirectUrl+"\"></redirect></partial-response>";
response.setContentType("text/xml");
response.getWriter().write(ajaxRedirectXml);
} else {
response.sendRedirect(redirectUrl);
}
}
private String calculateRedirectUrl(String contextPath, String url) {
if (!UrlUtils.isAbsoluteUrl(url)) {
if (contextRelative) {
return url;
} else {
return contextPath + url;
}
}
// Full URL, including http(s)://
if (!contextRelative) {
return url;
}
// Calculate the relative URL from the fully qualified URL, minus the scheme and base context.
url = url.substring(url.indexOf("://") + 3); // strip off scheme
url = url.substring(url.indexOf(contextPath) + contextPath.length());
if (url.length() > 1 && url.charAt(0) == '/') {
url = url.substring(1);
}
return url;
}
/**
* If <tt>true</tt>, causes any redirection URLs to be calculated minus the protocol
* and context path (defaults to <tt>false</tt>).
*/
public void setContextRelative(boolean useRelativeContext) {
this.contextRelative = useRelativeContext;
}
}

Related

How to Over ride BindAuthenticator handleBindException for Spring LDAP Authentication setup in Spring Boot

For Spring security setup in Spring Boot. The LDAP Authentication provider is configured by default to use BindAuthenticator class.
This Class contains method
/**
* Allows subclasses to inspect the exception thrown by an attempt to bind with a
* particular DN. The default implementation just reports the failure to the debug
* logger.
*/
protected void handleBindException(String userDn, String username, Throwable cause) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to bind as " + userDn + ": " + cause);
}
}
This Method is to handle the authentication related Exceptions like invalid credentials.
I want to over-ride this method so i can handle this issue and return proper error message on the basis of error codes returned by LDAP. like invalid password or the account is locked.
Current LDAP implementation always returns "Bad Credentials" that does not give the right picture that why my credentials are invalid. i want to cover the cases
where the account is Locked
password is expired so i can redirect to change password
account locked due to number of invalid password retries
Please help
The issue i fixed by defining the LDAP context instead of using the Spring Boot LDAPAuthenticationProviderConfigurer.
Then created the FilterBasedLdapUserSearch and Over-written the BindAuthentication with my ConnectBindAuthenticator.
i created a separate LDAPConfiguration class for spring boot configuration and registered all these custom objects as Beans.
From the above Objects i created LDAPAuthenticationProvider by passing my Custom Objects to constructor
The Config is as below
#Bean
public DefaultSpringSecurityContextSource contextSource() {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(env.getProperty("ldap.url"));
contextSource.setBase(env.getProperty("ldap.base"));
contextSource.setUserDn(env.getProperty("ldap.managerDn"));
contextSource.setPassword(env.getProperty("ldap.managerPassword"));
return contextSource;
}
#Bean
public ConnectBindAuthenticator bindAuthenticator() {
ConnectBindAuthenticator connectBindAuthenticator = new ConnectBindAuthenticator(contextSource());
connectBindAuthenticator.setUserSearch(ldapUserSearch());
connectBindAuthenticator.setUserDnPatterns(new String[]{env.getProperty("ldap.managerDn")});
return connectBindAuthenticator;
}
#Bean
public LdapUserSearch ldapUserSearch() {
return new FilterBasedLdapUserSearch("", env.getProperty("ldap.userSearchFilter"), contextSource());
}
You have to change your spring security configuration to add your extension of BindAuthenticator:
CustomBindAuthenticator.java
public class CustomBindAuthenticator extends BindAuthenticator {
public CustomBindAuthenticator(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
#Override
protected void handleBindException(String userDn, String username, Throwable cause) {
// TODO: Include here the logic of your custom BindAuthenticator
if (somethingHappens()) {
throw new MyCustomException("Custom error message");
}
super.handleBindException(userDn, username, cause);
}
}
spring-security.xml
<beans:bean id="contextSource"
class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<beans:constructor-arg value="LDAP_URL" />
<beans:property name="userDn" value="USER_DN" />
<beans:property name="password" value="PASSWORD" />
</beans:bean>
<beans:bean id="userSearch"
class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<beans:constructor-arg index="0" value="USER_SEARCH_BASE" />
<beans:constructor-arg index="1" value="USER_SEARCH_FILTER" />
<beans:constructor-arg index="2" ref="contextSource" />
</beans:bean>
<beans:bean id="ldapAuthProvider"
class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<beans:constructor-arg>
<beans:bean class="com.your.project.CustomBindAuthenticator">
<beans:constructor-arg ref="contextSource" />
<beans:property name="userSearch" ref="userSearch" />
</beans:bean>
</beans:constructor-arg>
</beans:bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="ldapAuthProvider" />
</security:authentication-manager>
Hope it's helpful.

check if user subscription for trial period is expire or not using spring MVC

I am using spring MVC and want to check if user's trial period has expired.
I am getting user detail using spring security using the following method
public User getUserDetail() {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
Object principal = auth.getPrincipal();
if(principal instanceof User){
User user = (User) principal;
return user;
}
return null;
}
User object contains the date when he logged in first.
I am checking the user subscription using following code
UserBean userLoggedIn = (UserBean) userService.getUserDetail();
Date dt = userLoggedIn.getUserCreationDate();
DateTime userCreated = new DateTime(dt).plusDays(TRIAL_PERIOD);
DateTime currentDateTime = new DateTime();
if(currentDateTime.compareTo(userCreated) > 0 && userLoggedIn.getPackageType() == 0){
return new ModelAndView("pricing","user",userLoggedIn);
}
Now my problem is I don't want to write the above code repeatedly in each controller. So is there any common place where I can check the user trial period expire or not and redirect him to pricing page.
I have CustomUserDetail class where I am accessing user details from database and put it in spring security session. So I think this should be the best place to check if users trial period is expire or not but I don't know how I can redirect user from this class to pricing page.
My CustomUserDetail class is
#Service
#Transactional(readOnly = true)
public class CustomUserDetailsService implements UserDetailsService {
static final Logger logger = Logger.getLogger(CustomUserDetailsService.class);
#Resource(name="userService")
private UserService userService;
/* (non-Javadoc)
* #see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
#Override
public UserDetails loadUserByUsername(String email)
throws UsernameNotFoundException, DataAccessException {
try {
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
UserBean domainUser = userService.getUserByName(email);
domainUser.isEnabled();
domainUser.isAccountNonExpired();
domainUser.isCredentialsNonExpired();
domainUser.isAccountNonLocked();
//Collection<? extends GrantedAuthority> roles = getAuthorities((long) domainUser.getRoleId());
return domainUser;
} catch (Exception e) {
logger.error("Invalid Login.",e);
throw new RuntimeException(e);
}
}
---updated---
My spring-security.xml is
<form-login login-page="/login.htm"
authentication-failure-url="/loginfailed.htm"
authentication-failure-handler-ref="exceptionMapper"
default-target-url="/index.htm"
always-use-default-target="true"/>
<access-denied-handler error-page="/logout.htm"/>
<logout invalidate-session="true"
logout-url="/logout.htm"
success-handler-ref="userController"/>
<remember-me user-service-ref="customUserDetailsService" key="89dqj219dn910lsAc12" use-secure-cookie="true" token-validity-seconds="466560000"/>
<session-management session-authentication-strategy-ref="sas"/>
</http>
<authentication-manager>
<authentication-provider user-service-ref="customUserDetailsService">
<password-encoder ref="customEnocdePassword" >
<salt-source user-property="email"/>
</password-encoder>
</authentication-provider>
</authentication-manager>
<beans:bean id="customEnocdePassword" class="com.mycom.myproj.utility.CustomEnocdePassword" />
<beans:bean id="exceptionMapper" class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler" >
<beans:property name="exceptionMappings">
<beans:map>
<beans:entry key="your.package.TrialPeriodExpiredException" value="/pricing"/>
</beans:map>
</beans:property>
</beans:bean>
<beans:bean id="sas"
class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="maximumSessions" value="3" />
---update----
Now what I did is
<beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="customUserDetailsService"/>
<beans:property name="passwordEncoder" ref="customEnocdePassword"/>
<beans:property name="preAuthenticationChecks" ref="expirationChecker"/>
</beans:bean>
<authentication-manager>
<authentication-provider user-service-ref="authenticationProvider">
<password-encoder ref="customEnocdePassword" >
<salt-source user-property="email"/>
</password-encoder>
</authentication-provider>
</authentication-manager>
<!-- <authentication-manager>
<authentication-provider user-service-ref="customUserDetailsService">
<password-encoder ref="customEnocdePassword" >
<salt-source user-property="email"/>
</password-encoder>
</authentication-provider>
</authentication-manager> -->
<beans:bean id="expirationChecker" class="com.mycom.myproj.utility.UserTrialPeriodExpirationChecker" />
<beans:bean id="customEnocdePassword" class="com.mycom.myproj.utility.CustomEnocdePassword" />
now I am getting below error
"Cannot convert value of type [org.springframework.security.authentication.dao.DaoAuthenticationProvider]
to required type [org.springframework.security.core.userdetails.UserDetailsService]
for property 'userDetailsService': no matching editors or conversion strategy found"
You could set a custom UserDetailsChecker on the DaoAuthenticationProvider that verifies the expiration date before authenticating the user.
The <authentication-provider> element in your config generates a DaoAuthenticationProvider, but there is no attribute on that element that would allow you to set its preAuthenticationChecks property. In order to work around this limitation of the namespace configuration, you will have to fall back to defining that provider as a normal bean:
<bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="customUserDetailsService"/>
<property name="passwordEncoder" ref="customEnocdePassword"/>
<property name="preAuthenticationChecks" ref="expirationChecker"/>
</bean>
and refer to it by the id in the <authentication-manager> config:
<security:authentication-manager>
<security:authentication-provider ref="authenticationProvider"/>
</security:authentication-manager>
The above referenced expirationChecker bean must implement UserDetailsChecker which is a call-back interface receiving the UserDetails object, where you could throw a specific exception if the user's trial period has expired:
public class UserTrialPeriodExpirationChecker implements UserDetailsChecker {
#Override
public void check(UserDetails user) {
if( /* whatever way you check expiration */ ) {
throw new TrialPeriodExpiredException();
}
if (!user.isAccountNonLocked()) {
throw new LockedException("User account is locked");
}
if (!user.isEnabled()) {
throw new DisabledException("User is disabled");
}
if (!user.isAccountNonExpired()) {
throw new AccountExpiredException("User account has expired");
}
}
}
Note that the last three checks are not related to the expiration checking, but you have to have them here, as the default implementation (which is AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks) is now overridden by this class. Since the default implementation is a private inner class, you cannot simply extend it, but need to copy the code from there to prevent locked/disabled/etc. users from logging in.
Once you have all that in place, configure an ExceptionMappingAuthenticationFailureHandler that maps your TrialPeriodExpiredException to the URL of the pricing page, where the user should land.
<form-login authentication-failure-handler-ref="exceptionMapper" ... />
...
<bean id="exceptionMapper" class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler" >
<property name="exceptionMappings">
<map>
<entry key="your.package.TrialPeriodExpiredException" value="/pricing"/>
</map>
</property>
</bean>

Spring Security Error Pages using Forward

I want to display a custom error page on authentication denial (I am using a pre-authenticated secnario) and also access denial from Spring Security 3.0.X
Understand we can use the following to perform this:
<beans:bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/error.jsp"/>
</beans:bean>
<beans:bean id="accessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<beans:property name="errorPage" value="/error.jsp"/>
</beans:bean>
but doing this results in a redirect and not a forward to the error page. Anyway to perform forward to an error page (so that we can set some attributes in the request)
Thanks
In my security-context.xml, for authentication failure I do like this (pay attention to the authentication-failure-url attribute):
<security:form-login login-page="/auth/login"
authentication-failure-url="/auth/login?error=true"
default-target-url="/mvc/home"
always-use-default-target="true" />
And for access denied I use this:
<security:access-denied-handler error-page="/auth/access-denied"/>
Both tags inside <security:http use-expressions="true">. For me works like a charm, I don't know why you are trying to configure it in the way you are doing when Spring provides such nice tags easy to use.
I don't know if it answers your question, I hope it helps.
EDIT:
Using the configuration provided above, means that you are using the default authentication failure handler (SimpleUrlAuthenticationFailureHandler) at the background. You can change the default behavior (which as default performs a redirect when a failed authentication is produced) by changing the attribute forwardToDestination value. This is what SimpleUrlAuthenticationFailureHandler does:
/**
* Performs the redirect or forward to the {#code defaultFailureUrl} if set, otherwise returns a 401 error code.
* <p>
* If redirecting or forwarding, {#code saveException} will be called to cache the exception for use in
* the target view.
*/
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (defaultFailureUrl == null) {
logger.debug("No failure URL set, sending 401 Unauthorized error");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed: " + exception.getMessage());
} else {
saveException(request, exception);
if (forwardToDestination) {
logger.debug("Forwarding to " + defaultFailureUrl);
request.getRequestDispatcher(defaultFailureUrl).forward(request, response);
} else {
logger.debug("Redirecting to " + defaultFailureUrl);
redirectStrategy.sendRedirect(request, response, defaultFailureUrl);
}
}
}
So I guess that I you declare your SimpleUrlAuthenticationFailureHandler in your security-context.xml and set the mentioned property value using the setUseForward(boolean forwardToDestination) method it should work. Could be something like:
<bean id="simpleUrlAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<property name="useForward" value="true">
</bean>
And then:
<security:form-login login-page="/auth/login"
authentication-failure-handler-ref="simpleUrlAuthenticationFailureHandler"
default-target-url="/mvc/home"
always-use-default-target="true" />
Good luck.

Spring Security sesson timeout not recognized on ajax call

I want to redirect users to the login page when a session timeout occurs. This works out-of-the-box with spring security, but only on non-ajax calls.
On an ajax-call you have to react on the session timeout by yourself. Therefore I have created
my own filter(filter implemented like in this question) who checks if a session is timed out. The filter is registered via custom-filter tag in spring security config.
<http use-expressions="true">
<custom-filter ref="customTimeoutHandler" after="LAST"/>
</http>
The problem is, that the session timeout is not recognized by the filter. If I check for request.isRequestedSessionIdValid() it returns true even if the session is timed out. When I enter a new secured URL manually, the standard spring security filter recognizes the timeout correctly and does a redirect to the login page.
What could be wrong here? How recognizes spring security the session timeout?
UPDATE
It seems, that the session management filter of spring security replaces the timed-out session with a new anonymous one. Therefore everytime I check for session timeout it returns true, because the new anonymous session is, of course, not timed-out.
You can check the SecurityContext.
Grab the Authentication object and check the authorities looking for an ANONYMOUS one. Something like:
SecurityContext sc = SecurityContextHolder.getContext();
Authentication a = sc.getAuthentication();
if(!a.isAuthenticated() || a.getAuthorities().contains(new GrantedAuthorityImpl("ROLE_ANONYMOUS"))) {
//user not authenticated or ANONYMOUS
} else {
//user authenticated
}
This solution works like a charm for me.
The basic concept is to point to a servlet instead of the login page. The servlet then determines if the request was a ajax request and if that is true, it returns the redirect to the login page as xml fragment. The browser can interpret that fragment and redirects to the login page.
I am developing enterprise application including gwt/gwtp and spring security .
I add some issue with session time out , casue the SimpleRedirectInvalidSessionStrategy which used by default is executing response.sendRedirect() , the html page response I wanted to redirect is swallow by gwt com.google.gwt.user.client.rpc.InvocationException as the exception message . and no actully redirect is taking place .
for solving this
1 . I define my cosutom session-manamgemt-filter
for doing this you need in your spring-security.xml configuration file set
<session-management session-fixation-protection="none"/> by this spring secuirty will not take it default session managment filter .
define your session managment filter
enter code here
{
<custom-filter position="SESSION_MANAGEMENT_FILTER" ref="mySessionManagmentFilter"/>
<beans:bean id="mySessionManagmentFilter"
class="org.springframework.security.web.session.SessionManagementFilter">
<beans:constructor-arg index="0" ref="mySessionSecurityContextRepository"/>
<beans:constructor-arg index="1" ref="mySessionAutenticationStrategy"/>
<beans:property name="invalidSessionStrategy">
<beans:ref local="myInvalidSessionStrategy"/>
</beans:property>
</beans:bean>
<beans:bean id="mySessionSecurityContextRepository"
class='org.springframework.security.web.context.HttpSessionSecurityContextRepository'>
<beans:property name='allowSessionCreation' value='false'/>
</beans:bean>
<beans:bean id="mySessionAutenticationStrategy"
class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry"/>
<beans:property name="maximumSessions" value="1"/>
<beans:property name="exceptionIfMaximumExceeded" value="false"/>
<beans:property name="alwaysCreateSession" value="true"/>
</beans:bean>
<beans:bean id="myInvalidSessionStrategy"
class="com.my.project.MyInvalidSessionStrategy">
<beans:constructor-arg value="/login.jsp?timeout=1"/>
</beans:bean>
}
here custom - MyInvalidSessionStrategy
{
public class MyInvalidSessionStrategy implements InvalidSessionStrategy {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final String destinationUrl;
public OperationalInvalidSessionStrategy(String invalidSessionUrl) {
this.destinationUrl = invalidSessionUrl;
}
#Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String exMsg =session timeout ! , need to redirect to login page
logger.warn(exMsg);
throw new TimeOutException(exMsg);
}
}
}
so when time out taking place the new implementation is throwing an exception ..
the exception can be truck on gwt callback onFailure method
check the type of the exception and on onFailure method redirect the user to login page .
with Window.Location.replace(GWT.getHostPageBaseURL() + "/login.jsp")

ajax login with spring webMVC and spring security

I've been using Spring Security 3.0 for our website login mechanism using a dedicated login webpage. Now I need that login webpage to instead be a lightbox/popup window on every webpage in our site where upon logging in I get an AJAX result whether it was successful or not. What's the best way to go about this with Spring Security and Spring webmvc 3.0?
At the client-side you may simulate a normal form submission to your login url via ajax. For example, in jQuery:
$.ajax({
url: "${pageContext.request.contextPath}/j_spring_security_check",
type: "POST",
data: $("#loginFormName").serialize(),
beforeSend: function (xhr) {
xhr.setRequestHeader("X-Ajax-call", "true");
},
success: function(result) {
if (result == "ok") {
...
} else if (result == "error") {
...
}
}
});
At the server side, you may customize AuthenticationSuccessHandler and AuthenticationFailureHandler to return a value instead of redirect. Because you probably need a normal login page as well (for attempt to access a secured page via direct url), you should tell ajax calls from normal calls, for example, using header:
public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private AuthenticationSuccessHandler defaultHandler;
public AjaxAuthenticationSuccessHandler() {
}
public AjaxAuthenticationSuccessHandler(AuthenticationSuccessHandler defaultHandler) {
this.defaultHandler = defaultHandler;
}
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication auth)
throws IOException, ServletException {
if ("true".equals(request.getHeader("X-Ajax-call"))) {
response.getWriter().print("ok");
response.getWriter().flush();
} else {
defaultHandler.onAuthenticationSuccess(request, response, auth);
}
}
}
I did something similar (thanks axtavt):
public class AjaxAuthenticationSuccessHandler extends
SimpleUrlAuthenticationSuccessHandler {
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication auth)
throws IOException, ServletException {
if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
response.getWriter().print(
"{success:true, targetUrl : \'"
+ this.getTargetUrlParameter() + "\'}");
response.getWriter().flush();
} else {
super.onAuthenticationSuccess(request, response, auth);
}
}}
I chose to extend the simple success handler for the default behavior on non-Ajax requests. Here's the XML to make it work:
<http auto-config="false" use-expressions="true" entry-point-ref="authenticationProcessingFilterEntryPoint">
<custom-filter position="FORM_LOGIN_FILTER" ref="authenticationFilter" />
...
...
</http>
<beans:bean id="authenticationProcessingFilterEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/index.do" />
<beans:property name="forceHttps" value="false" />
</beans:bean>
<beans:bean id="authenticationFilter" class=
"org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager"/>
<beans:property name="filterProcessesUrl" value="/j_spring_security_check"/>
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
<beans:property name="authenticationFailureHandler" ref="failureHandler"/>
<beans:property name="authenticationSuccessHandler" ref="successHandler"/>
</beans:bean>
<beans:bean id="successHandler" class="foo.AjaxAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/login.html"/>
</beans:bean>
<beans:bean id="failureHandler" class="foo.AjaxAuthenticationFailureHandler" />

Resources