Hi,
I would write an article concerning the overridden of the token of Spring 3 Security. In this post, we will follow several points: creation of a simple Spring MVC Web application (controller, JSP pages,…), securization with Spring security via custom login form, override the authentication components (provider, token,…).
Creation of Spring MVC Web application
- Create a simple Dynamic Web Module in Eclipse named Spring3Security;
- Final structure of Eclipse project named Spring3Security;
- Add the following jars in the folder Spring3Security\war\WEB-INF\lib:
- activation-1.1.jar
- antlr-2.7.7.jar
- aopalliance-1.0.jar
- asm-3.3.1.jar
- cglib-2.2.jar
- commons-logging-1.1.1.jar
- javassist-3.8.1.GA.jar
- jstl-1.2.jar
- org.springframework.aop-3.0.5.RELEASE.jar
- org.springframework.asm-3.0.5.RELEASE.jar
- org.springframework.beans-3.0.5.RELEASE.jar
- org.springframework.context.support-3.0.5.RELEASE.jar
- org.springframework.context-3.0.5.RELEASE.jar
- org.springframework.core-3.0.5.RELEASE.jar
- org.springframework.expression-3.0.5.RELEASE.jar
- org.springframework.jdbc-3.0.5.RELEASE.jar
- org.springframework.jms-3.0.5.RELEASE.jar
- org.springframework.orm-3.0.5.RELEASE.jar
- org.springframework.oxm-3.0.5.RELEASE.jar
- org.springframework.web-3.0.5.RELEASE.jar
- servlet-api-2.5.jar
- spring-oxm-tiger-1.5.9.jar
- spring-security-acl-3.1.2.RELEASE.jar
- spring-security-config-3.1.2.RELEASE.jar
- spring-security-core-3.1.2.RELEASE.jar
- spring-security-core-tiger-2.0.7.RELEASE.jar
- spring-security-taglibs-3.1.2.RELEASE.jar
- spring-security-web-3.1.2.RELEASE.jar
- spring-support-2.0.8.jar
- spring-webmvc-3.0.5.RELEASE.jar
- Edit the web descriptor file web.xml, in order to add a section concerning the Spring context and Spring security configuration (http filter):
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>SPRING3SECURITY</display-name> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <error-page> <error-code>403</error-code> <location>/unauthorizedAccess.jsp</location> </error-page> <error-page> <error-code>404</error-code> <location>/unauthorizedAccess.jsp</location> </error-page> <!-- ##################################### --> <!-- SPRING SECURITY --> <!-- ##################################### --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> </listener> <!-- Login failed error --> <!-- MUST be first context param --> <context-param> <param-name>webAppRootKey</param-name> <param-value>spring3security.root</param-value> </context-param> <!-- MUST be second context param --> <context-param> <param-name>log4jConfigLocation</param-name> <!-- <param-value>../../etc/spring3security-log4j.properties</param-value> --> <param-value>classpath:log4j.properties</param-value> </context-param> <!-- MUST be first listener --> <listener> <listener-class>org.springframework.web.util.WebAppRootListener</listener-class> </listener> <!-- MUST be second listener --> <listener> <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- ##################################### --> <!-- SPRING MVC & SPRING CONTEXT--> <!-- ##################################### --> <context-param> <param-name>webAppRootKey</param-name> <param-value>Spring3Security.root</param-value> </context-param> <listener> <listener-class>org.springframework.web.util.WebAppRootListener</listener-class> </listener> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <servlet> <servlet-name>Spring3Security</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Spring3Security</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
Spring MVC configuration
- First, we need to configure the Spring MVC controllers. For this, edit the file Spring3Security-servlet.xml in order to define the “web beans” here such as the Controllers and the URL mapping configurations. The Web beans defined in this file may reference “application beans” in the “root” web application context:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- ####################### SPRING MVC ################################################### --> <!-- Define the "web beans" here such as the Controllers and the URL mapping configurations Web beans here may reference "application beans" in the "root" web application context --> <!-- ################### SPRING MVC VIEW RESOLVER ################### --> <!-- IF the Controller returns a logical view name="rewardList" THEN the ViewResolver will return the file "/WEB-INF/jsp/rewardList.jsp" END IF --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"></property> <property name="prefix" value="/WEB-INF/jsp/"></property> <property name="suffix" value=".jsp"></property> <property name="order"><value>2</value></property> </bean> <!-- ################### SPRING MVC CONTROLLER XML ################### --> <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <value> /loginSecure.do=loginSecureController /myOwnController.do=myOwnController </value> </property> </bean> <bean name="loginSecureController" class="com.ho.spring3.security.test1.controller.LoginSecureController"> <property name="methodNameResolver" ><ref bean="paramResolver"/></property> </bean> <bean name="myOwnController" class="com.ho.spring3.security.test1.controller.MyOwnController" > <property name="methodNameResolver" ><ref bean="paramResolver"/></property> </bean> <bean id="paramResolver" class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver"> <property name="paramName"><value>action</value></property> </bean> <!-- ################### SPRING JSON RESOLVER ################### --> <bean id="jsonResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver"> <property name="order"><value>1</value></property> </bean> </beans>
- Then, we need a Spring MVC controller MyOwnController to handle the web requests for the pages navigation (handlePage0, handlePage1, handlePage2 methods):
package com.ho.spring3.security.test1.controller; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.multiaction.MultiActionController; public class MyOwnController extends MultiActionController { // ------------------------------------------------------------------ LOG4J protected final Log logger = LogFactory.getLog(this.getClass()); // --------------------------------------------------------- PUBLIC METHODS public ModelAndView handlePage0(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { logActionName("handlePage0"); return new ModelAndView("page0"); } public ModelAndView handlePage1(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { logActionName("handlePage1"); return new ModelAndView("page1"); } public ModelAndView handlePage2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { logActionName("handlePage2"); return new ModelAndView("page2"); } // -------------------------------------------------------- PRIVATE METHODS private void logActionName(String actionName) { if (logger.isDebugEnabled()) { logger.debug(this.getClass().getSimpleName() + actionName + "()"); } // end-if } }
- So, we need also a Spring MVC controller LoginSecureController to handle the security requests after that the authentication is successful (handleLogin method) or for the logout request (handleLogout method),
package com.ho.spring3.security.test1.controller; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.multiaction.MultiActionController; import com.ho.spring3.security.test1.util.MyRoleUtil; public class LoginSecureController extends MultiActionController { // -------------------------------------------------------------- PUBLIC FUNCTIONS public LoginSecureController() { } public ModelAndView handleLogin(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (logger.isInfoEnabled()) { logger.info("LoginSecureController : user " + (new MyRoleUtil()).getLoggedUserName() + " logged in."); MyRoleUtil roleUtil = new MyRoleUtil(); logger.info("User roles = " + roleUtil.getLoggedUserRolesNames()); } // end-if return new ModelAndView("redirect:/myOwnController.do?action=handlePage0"); } public ModelAndView handleLogout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Invaliadtion manuelle de la session utilisateur if(null!=request.getSession()){ request.getSession().invalidate(); } if (logger.isInfoEnabled()) { logger.info("LoginSecureController : user " + (new MyRoleUtil()).getLoggedUserName() + " logged out."); } // end-if return new ModelAndView("redirect:/index.jsp"); } }
Spring Security components (provider, token)
We will securize the application with Spring security via a custom login form.
- Add the security access configuration file name applicationContext.xml in the folder Spring3Security\war\WEB-INF:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:beans="http://www.springframework.org/schema/beans" 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.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd "> <!-- ################### SPRING SECURITY ################### --> <global-method-security jsr250-annotations="enabled" secured-annotations="enabled" /> <http pattern="/*.jsp" security="none" /> <http pattern="/*.jsp.*" security="none" /> <http pattern="/jsp/**" security="none" /> <http pattern="/css/**" security="none" /> <http pattern="/images/**" security="none" /> <http pattern="/img/**" security="none" /> <http pattern="/js/**" security="none" /> <http auto-config="true"> <!-- Limitation of simultaneous connections --> <session-management session-fixation-protection="none"> <!-- <concurrency-control error-if-maximum-exceeded="true" max-sessions="1"/> --> <concurrency-control max-sessions="1"/> </session-management> <!-- Access rule: every user should have the role ROLE_BASIC to view any URLs. The syntax, / ** is called "Ant syntax." --> <!-- The role ROLE_BASIC will be affected after authentication --> <intercept-url pattern="/*.do.*" access="ROLE_BASIC" /> <intercept-url pattern="/.*" access="ROLE_BASIC" /> <intercept-url pattern="/**" access="ROLE_BASIC" /> <!-- Authentication form in Spring Security --> <form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" default-target-url="/loginSecure.do?action=handleLogin" /> <!-- Disconnect: By default, the disconnection URL is j_spring_security_logout. --> <logout invalidate-session="true" logout-success-url="/index.jsp" logout-url="/loginSecure.do?action=handleLogout" /> </http> <!-- Components AuthenticationProvider --> <authentication-manager> <!-- Spring Security password hashing see http://md5.gromweb.com/ --> <!-- <authentication-provider> --> <!-- <password-encoder hash="md5" /> --> <!-- <user-service id="userDetailsService"> --> <!-- <user name="admin" password="21232f297a57a5a743894a0e4a801fc3" authorities="ROLE_BASIC,ROLE_FULLACCESS_XML" /> --> <!-- <user name="user1" password="24c9e15e52afc47c225b757e7bee1f9d" authorities="ROLE_BASIC,ROLE_FULLACCESS_XML" /> --> <!-- <user name="user2" password="7e58d63b60197ceb55a1c487989a3720" authorities="ROLE_BASIC,ROLE_FULLACCESS_XML" /> --> <!-- </user-service> --> <!-- </authentication-provider> --> <authentication-provider ref="myAuthenticationProvider" /> </authentication-manager> <beans:bean id="myAuthenticationProvider" class="com.ho.spring3.security.test1.util.MyAuthenticationProvider"> </beans:bean> </beans:beans>
Some explanations:
- We have used a customized HTTP authentication form via the form-login tag: a cutomized form page named login.jsp to which all users will be redirected;
- If the authentication fails, the user will redirect to the URL /login.jsp?error=true; but if the authentication is successful, the user will redirect to the URL /loginSecure.do?action=handleLogin:
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" default-target-url="/loginSecure.do?action=handleLogin" />
- The syntax used in the acess rules /** is named/called “Ant syntax”;
- All JSP pages, css, images, javascript resources will not be securized:
<http pattern="/*.jsp" security="none" /> <http pattern="/*.jsp.*" security="none" /> <http pattern="/jsp/**" security="none" /> <http pattern="/css/**" security="none" /> <http pattern="/images/**" security="none" /> <http pattern="/img/**" security="none" /> <http pattern="/js/**" security="none" />
- We have defined 1 access rule: every user should have the role ROLE_BASIC to view the URLs *.do. The role ROLE_BASIC will be affected after authentication:
<intercept-url pattern="/*.do.*" access="ROLE_BASIC" /> <intercept-url pattern="/.*" access="ROLE_BASIC" /> <intercept-url pattern="/**" access="ROLE_BASIC" />
- We have set a limitation of simultaneous connections.
<session-management session-fixation-protection="none"> <concurrency-control max-sessions="1"/> </session-management>
.. it requires the presence of the listener “org.springframework.security.web.session.HttpSessionEventPublisher” in web.xml. The max-sessions attribute specifies the maximum number of simultaneous connections to the same identifier;
- We have specified a logout URL /loginSecure.do?action=handleLogout which will invalide the user’s session;
<logout invalidate-session="true" logout-success-url="/index.jsp" logout-url="/loginSecure.do?action=handleLogout" />
- Finally, we have defined a custom AuthenticationProvider named myAuthenticationProvider. The components AuthenticationProvider of Spring Security, perform the authentication, that is to say it manages the verification of the identity of the user:
<authentication-manager> <authentication-provider ref="myAuthenticationProvider" /> </authentication-manager> <beans:bean id="myAuthenticationProvider" class="com.ho.spring3.security.test1.util.MyAuthenticationProvider"></beans:bean>
- The index page index.jsp will redirect the user to a securized URL /loginSecure.do?action=handleLogin for which the login form will be displayed by the Spring security:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%-- Redirected because we can't set the welcome page to a virtual URL. --%> <c:redirect url="/loginSecure.do?action=handleLogin"/>
- The login form login.jsp will be display an authentication form, and error message if the authentication has failed:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Login Page</title> </head> <body onload='document.f.j_username.focus();'> <h3>Login with Username and Password - JavaBlog.fr - Custom Page</h3> <c:if test="${param.error == true}"> <font color="red"> Authentication was not successful, try again.<br /> Cause: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/><br/> <%-- ${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}<br/> --%> <%-- ${sessionScope.SPRING_SECURITY_LAST_EXCEPTION.message}<br/> --%> </font> <br/> </c:if> <form name='f' action="/Spring3Security/j_spring_security_check" method='POST'> <table> <tr> <td>User:</td> <td><input type='text' name='j_username' value=''> </td> </tr> <tr> <td>Password:</td> <td><input type='password' name='j_password' /> </td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="submit" /> </td> </tr> <tr> <td colspan='2'><input name="reset" type="reset" /> </td> </tr> </table> </form> </body> </html>
- After a successful authentication, the main page page0.jsp will display the user’s informations:
– fullname, email stored in the custom token
– username, roles from the Spring security,<%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> <%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%> <%@ page import="com.ho.spring3.security.test1.util.MyRoleUtil" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head> <META http-equiv="Cache-Control" content="no-cache"> <META http-equiv="Pragma" content="no-cache"> <META http-equiv="Expires" content="0"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Page 0</title> </head> <body> <%-- <h2>Bienvenue <sec:authentication property="principal.username" />: --%> <h2>Bienvenue <%= new MyRoleUtil().getLoggedUserName()%>:</h2> * <%= new MyRoleUtil().getCurrentMyUser().getFullName()%><br/> * <%= new MyRoleUtil().getCurrentMyUser().getEmail()%><br/> <br/> <table> <tr><td><a href="/Spring3Security/loginSecure.do?action=handleLogout">Logout</a></td></tr> <tr> <td><a href="myOwnController.do?action=handlePage0">page0</a></td> <td> | </td> <td><a href="myOwnController.do?action=handlePage1">page1</a></td> <td> | </td> <td><a href="myOwnController.do?action=handlePage2">page2</a></td> </tr> </table> <br/> Yours roles: <sec:authentication property="authorities" var="authorities" /> <ul> <c:forEach items="${authorities}" var="authority"> <li>${authority}</li> </c:forEach> </ul> <sec:authorize ifAnyGranted ="ROLE_BASIC,ROLE_FULLACCESS_XML,ROLE_FULLACCESS_CUSTOM"> <br/>Code visible for users who have the roles ROLE_FULLACCESS_XML <u>or</u> ROLE_FULLACCESS_CUSTOM </sec:authorize> <sec:authorize ifAllGranted ="ROLE_FULLACCESS_XML,ROLE_FULLACCESS_CUSTOM"> <br/>Code visible for users who have the roles ROLE_FULLACCESS_XML <u>and</u> ROLE_FULLACCESS_CUSTOM </sec:authorize> <sec:authorize ifNotGranted ="ROLE_FULLACCESS_XML,ROLE_FULLACCESS_CUSTOM"> <br/>Code visible for users who don't have the role ROLE_FULLACCESS_XML, <u>and neither</u> ROLE_FULLACCESS_CUSTOM </sec:authorize> <sec:authorize ifAnyGranted ="ROLE_FULLACCESS_XML"> <br/>Code visible for the users who have the role ROLE_FULLACCESS_XML </sec:authorize> <sec:authorize ifAnyGranted ="ROLE_FULLACCESS_CUSTOM"> <br/>Code visible for the users who have the role ROLE_FULLACCESS_CUSTOM </sec:authorize> <br/> </div> </body> </html>
- Several pages page1.jsp and page2.jsp will contain the mock application pages like:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Page 1</title> </head> <body> <table> <tr><td><a href="/Spring3Security/loginSecure.do?action=handleLogout">Logout</a></td></tr> <tr> <td><a href="myOwnController.do?action=handlePage0">page0</a></td> <td> | </td> <td><a href="myOwnController.do?action=handlePage1">page1</a></td> <td> | </td> <td><a href="myOwnController.do?action=handlePage2">page2</a></td> </tr> </table> <hr/> <h3>PAGE 1 </h3> </body> </html>
- The main class is MyAuthenticationProvider which performs the authentication, i.e., it manages the checking of user’s identity via the authenticate method which gets the filled login and password to check with others systems (LDAP, WS, …). In our case, we check only if
the login == password???. Then, we add the technical role ROLE_BASIC for the AUTHENTICATION ONLY, and the applicative ROLE_FULLACCESS_CUSTOM role. And finally, we have set the specific user’s informations (fullname and email) in an simple POJO MyUser
used in the security and representing the User informations stored in the authentication mechanism:package com.ho.spring3.security.test1.util; import java.util.ArrayList; import java.util.List; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.GrantedAuthorityImpl; /** * The "AuthenticationProvider" components perform the authentication, i.e., they manage the checking of user's identity. * * @author Huseyin OZVEREN * */ public class MyAuthenticationProvider implements AuthenticationProvider { // ---------------------------------- PUBLIC METHODS @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException{ // Login //String login= authentication.getName(); String login = (String) authentication.getPrincipal(); // Password String password = (String) authentication.getCredentials(); // Additional details of current (not yet authenticated) user MyUser myUser = null; if(password == null || login == null || login.trim().length()==0 || password.trim().length()==0){ throw new AuthenticationServiceException("Your login/password are empty!!!"); } boolean isAuth= false; try{ // .... Call remote service like LDAP or WS // login == password??? isAuth = (login.equals(password)); List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>(); if(isAuth){ // Technical role : FOR AUTHENTICATION ONLY AUTHORITIES.add(new GrantedAuthorityImpl("ROLE_BASIC")); // .... Get Applicative Roles from remote services LDAP of WS AUTHORITIES.add(new GrantedAuthorityImpl("ROLE_FULLACCESS_CUSTOM")); // .... Get User Details from remote services LDAP of WS { // ---- FullName String fullName = "Huseyin OZVEREN"; // ---- Email String email = "contact@javablog.fr"; // myUser = new MyUser(fullName, email); } }else{ throw new AuthenticationServiceException("Your login attempt was not successful."); } //return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(), AUTHORITIES); return new MyAuthenticationToken(authentication.getName(), authentication.getCredentials(), AUTHORITIES, myUser); }catch(AuthenticationServiceException e){ throw e; }catch(Throwable e){ throw new AuthenticationServiceException("An error/exception occurs during the authentication. Please, try again.", e); //Exception ex = new DisabledException(msg); //Exception ex = new CredentialException(msg); //Exception ex = new CredentialExpiredException(msg); //Exception ex = new BadCredentialsException(msg); //Exception ex = new BadCredentialsException(msg); } } @Override public boolean supports(Class<? extends Object> authentication){ return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication) && authentication.equals(UsernamePasswordAuthenticationToken.class); } }
- Simple code for the MyUser class:
package com.ho.spring3.security.test1.util; import java.io.Serializable; /** * This class is used in the security and represents the User informations stored in the authentication mechanism. * * @author Huseyin OZVEREN * */ public class MyUser implements Serializable{ // ------------------------ PRIVATE ATTRIBUTES private String fullName = null; private String email = null; // ------------------------ CONSTRUCTOR public MyUser(String fullName, String email){ this.fullName = fullName; this.email = email; } // ------------------------ GET/SET TERS public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
- Last, we have override the Spring token UsernamePasswordAuthenticationToken by our token MyAuthenticationToken used in the “AuthenticationProvider” component MyAuthenticationProvider:
return new MyAuthenticationToken(authentication.getName(), authentication.getCredentials(), AUTHORITIES, myUser);
… so, here, the source of our new token MyAuthenticationToken which extends the standard Spring class “UsernamePasswordAuthenticationToken” to allow the storing of additional data/details attached to authenticated user (for example, fullName, email..):
package com.ho.spring3.security.test1.util; import java.util.Collection; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; /** * This component extends the standard Spring class "UsernamePasswordAuthenticationToken" to allow the storing of additional data/details * attached to authenticated user (for example, fullName, email..). * * @author Huseyin OZVEREN */ public class MyAuthenticationToken extends UsernamePasswordAuthenticationToken{ // ----------------------------------- PRIVATE ATTRIBUTES private MyUser myUser = null; // ----------------------------------- CONSTRUCTOR public MyAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities, MyUser myUser){ super(principal, credentials, authorities); this.myUser = myUser; } // ----------------------------------- GET/SET TERS public MyUser getMyUser() { return myUser; } public void setMyUser(MyUser myUser) { this.myUser = myUser; } }
- We have created a utility class MyRoleUtil using SPRING to retrieve the properties and roles of a authenticated user from the Spring Security Context:
package com.ho.spring3.security.test1.util; import java.util.ArrayList; import java.util.Collection; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; /** * <p> * Utility class using SPRING to retrieve the properties and roles of a authenticated user. * </p> * * @author Huseyin OZVEREN * */ public class MyRoleUtil { // --------------------------------------------------------------------------------------------------- FONCTIONS PUBLIQUES /** * return true if at least one role match */ public boolean loggedUserHasRole(String ... roles) { SecurityContext context = SecurityContextHolder.getContext(); //GrantedAuthority[] authorities = context.getAuthentication().getAuthorities(); Collection<GrantedAuthority> authorities = (Collection<GrantedAuthority>) context.getAuthentication().getAuthorities(); if (authorities != null && authorities.size()>0) { for (GrantedAuthority authority : authorities) { for (String role : roles) { if (authority.getAuthority().matches(role)) { return true; } } } } return false; } /** * Return the username/login of the user */ public String getLoggedUserName() { SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); String userName = null; if(authentication.getPrincipal() instanceof org.springframework.security.core.userdetails.User){ org.springframework.security.core.userdetails.User user = (org.springframework.security.core.userdetails.User) authentication.getPrincipal(); userName = user.getUsername(); }else{ userName = (String) authentication.getPrincipal(); authentication.getName(); authentication.getPrincipal(); authentication.getCredentials(); authentication.isAuthenticated(); authentication.getDetails(); } return userName; } /** * Return the list of user roles */ public Collection<String> getLoggedUserRolesNames() { SecurityContext context = SecurityContextHolder.getContext(); //GrantedAuthority[] authorities = context.getAuthentication().getAuthorities(); Collection<GrantedAuthority> authorities = (Collection<GrantedAuthority>) context.getAuthentication().getAuthorities(); Collection<String> roles = new ArrayList<String>(); if (authorities != null && authorities.size()>0) { for (GrantedAuthority authority : authorities) { roles.add(authority.getAuthority()); } } return roles; } /** * Return the informations/details relatives to the authenticated user * @return */ public MyUser getCurrentMyUser(){ SecurityContext context = SecurityContextHolder.getContext(); Authentication authentication = context.getAuthentication(); MyUser user = null; if(authentication instanceof MyAuthenticationToken){ user = ((MyAuthenticationToken)authentication).getMyUser(); } return user; } }
Demo and Tests
1. Access URL “http://localhost:8080/Spring3Security/index.jsp“, the index page will redirect to the securized page “/loginSecure.do?action=handleLogin”. So Spring will redirect your request to the custom login form:
URL : http://localhost:8080/Spring3Security/login.jsp
2. If username/password is wrong (login != password), authentication failed, display custom error messages:
URL : http://localhost:8080/Spring3Security/login.jsp?error=true
3. If username/password is correct (login == password), authentication success, display requested page. The welcome page contains several informations concerning the authenticated user like roles from Spring Security context, or user’s personnal informations (in our example, fullname, email) from the overridden token. These datas are accessible via our utility class MyRoleUtil.
URL : http://localhost:8080/Spring3Security/myOwnController.do?action=handlePage0
4. If you click on the page1 link:
URL : http://localhost:8080/Spring3Security/myOwnController.do?action=handlePage1
5. If you click on the page2 link:
URL : http://localhost:8080/Spring3Security/myOwnController.do?action=handlePage2
6. If you click on the logout link (http://localhost:8080/Spring3Security/loginSecure.do?action=handleLogout) which will be redirected to the login page:
URL : http://localhost:8080/Spring3Security/login.jsp
Advanced Demo and Tests: concurrency access
1. Open a first window and access URL “http://localhost:8080/Spring3Security/index.jsp“, the index page will redirect to the securized page “/loginSecure.do?action=handleLogin”. So Spring will redirect your request to the custom login form:
URL : http://localhost:8080/Spring3Security/login.jsp
2. IN WINDOW n°1: Fill in correct username/password (login == password == mylogin), authentication success, display requested page (welcome page):
URL : http://localhost:8080/Spring3Security/myOwnController.do?action=handlePage0
3. Open a second window with New Session and access URL “http://localhost:8080/Spring3Security/index.jsp“, the index page will redirect to the securized page “/loginSecure.do?action=handleLogin”. So Spring will redirect your request to the custom login form:
URL : http://localhost:8080/Spring3Security/login.jsp
4. IN WINDOW n°2: Fill in correct username/password (login == password == mylogin), authentication success, display requested page (welcome page):
URL : http://localhost:8080/Spring3Security/myOwnController.do?action=handlePage0
5. IN WINDOW n°2: If you click on the page1 link:
URL : http://localhost:8080/Spring3Security/myOwnController.do?action=handlePage1
6. IN WINDOW n°1: If you click on the page1 link, SPRING will block your action to an error page with the message “This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).”, because we have set a limitation of simultaneous connections with the same identifier to “1”.
URL : http://localhost:8080/Spring3Security/myOwnController.do?action=handlePage1
Source: Spring3Security
That’s all!!!
Best regards,
Huseyin OZVEREN
the best!!