First I would recommend you to go through my previous blog post I have written for Spring Security hello world example.
Today I am going to explain a simple example of why to use entry point in spring security and how to use role based login in Spring Security 4.
First we will see a spring security flow diagram of today's implementation
In the above diagram, when user log-in by providing credentials then spring filter will evaluate user creds with the help of user details service provider.If user is authenticated successfully then spring filter will invoke success handler else failure handler.If success then success handler will redirect to page based on his roles.If authentication failure then failure handler will redirect to access denied page.
If none of the user is logged in, then for every request Spring Security handles the authentication process with the concept of an Entry Point.For any request to the application entry point will redirect to the login page.
let us see implementation of the above diagram
here Spring Security user credentials are placed in XML configuration. However it is not safe to keep user details in XML file,it is always better to use database to secure user creds.I recommend my previous article for spring security using database.
Spring Security handles this automatic triggering of the authentication process with the concept of an Entry Point – this is a required part of the configuration, and can be injected via the entry-point-ref attribute of the security:http element.
Spring Security handles success handler & failure handler based on authentication result.Success handler and failure handler can be configured in security:form-login element.We use SimpleUrlAuthentication FailureHandler which is provided by spring, which will auto redirect to the access denied page.
If we didn’t set this handler then spring framework push the 403 Forbidden in response. We can even set unauthrozied(401) in response header as authException.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
When authentication is successful then this handler will invoked and redirect to the page as configured in the determineTargetUrl api based on role.
Today I am going to explain a simple example of why to use entry point in spring security and how to use role based login in Spring Security 4.
First we will see a spring security flow diagram of today's implementation
In the above diagram, when user log-in by providing credentials then spring filter will evaluate user creds with the help of user details service provider.If user is authenticated successfully then spring filter will invoke success handler else failure handler.If success then success handler will redirect to page based on his roles.If authentication failure then failure handler will redirect to access denied page.
If none of the user is logged in, then for every request Spring Security handles the authentication process with the concept of an Entry Point.For any request to the application entry point will redirect to the login page.
let us see implementation of the above diagram
Technologies used
java 1.7
tomcat 8
spring 4.1.6.RELEASE
spring-security 4.0.1.RELEASE
Maven 3
Maven dependencies
<properties> <spring-version>4.1.6.RELEASE</spring-version> <spring-orm-version>4.1.1.RELEASE</spring-orm-version> <spring-security-version>4.0.1.RELEASE</spring-security-version> <hibernate.version>4.3.11.Final</hibernate.version> <mysql.driver.version>5.1.30</mysql.driver.version> </properties> <dependencies> <!-- spring dependencies --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring-orm-version}</version> </dependency> <!-- spring security dependencies --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>${spring-security-version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${spring-security-version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring-security-version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> </dependencies> <build> <finalName>spring-security-entry-point-and-role-based-url</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build>
Security Configuration XML
Lets look at the configuration xml. I assume that you have clear idea about spring security configuration so I’m not going to explain each and every thing on this project. If you have doubt about the spring configurations please follow my previous posts.
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <context:component-scan base-package="com.spring.security"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix"> <value>/WEB-INF/pages/</value> </property> <property name="suffix"> <value>.jsp</value> </property> </bean> <!-- Application entry point which will redirect to login if user is not authenticated --> <bean id="appAuthenticationEntryPoint" class="com.spring.security.entrypoint.AppAuthenticationEntryPoint"> <constructor-arg name="loginFormUrl" value="/services/login"/> </bean> <!-- if user authentication is successful then AppSuccessHandler will redirect to page based on role--> <bean id="successHandler" class="com.spring.security.handler.AppSuccessHandler"/> <!-- if user authentication is unsuccessful then failureHandler will redirect to access denied page--> <bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <constructor-arg name="defaultFailureUrl" value="/services/accessdenied"/> </bean> <!-- With out login below url can be accessed and they are public,hence security is none --> <security:http pattern="/services/login" security="none"/> <security:http pattern="/services/accessdenied" security="none"/> <!-- Observe entry-point-ref is configured and all the request /** should be authenticated, if any request is not authenticated then entry point will be invoked which intern redirect to login page --> <security:http auto-config="true" use-expressions="true" entry-point-ref="appAuthenticationEntryPoint"> <!-- Interceptor urls --> <security:intercept-url pattern="/" access="isAuthenticated()"/> <security:intercept-url pattern="/**" access="isAuthenticated()"/> <security:intercept-url pattern="/user**" access="hasRole('USER')" /> <security:intercept-url pattern="/admin**" access="hasRole('ADMIN')" /> <security:form-login login-page="/login" login-processing-url="/j_spring_security_check" authentication-success-handler-ref="successHandler" authentication-failure-handler-ref="failureHandler" username-parameter="username" password-parameter="password" /> <!-- disabling csrf protection --> <security:csrf disabled="true"/> </security:http> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider> <security:user-service> <security:user name="testuser" password="testuser" authorities="ROLE_USER" /> <security:user name="admin" password="admin" authorities="ROLE_ADMIN" /> </security:user-service> </security:authentication-provider> </security:authentication-manager> </beans>
here Spring Security user credentials are placed in XML configuration. However it is not safe to keep user details in XML file,it is always better to use database to secure user creds.I recommend my previous article for spring security using database.
Spring Security handles this automatic triggering of the authentication process with the concept of an Entry Point – this is a required part of the configuration, and can be injected via the entry-point-ref attribute of the security:http element.
Spring Security handles success handler & failure handler based on authentication result.Success handler and failure handler can be configured in security:form-login element.We use SimpleUrlAuthentication FailureHandler which is provided by spring, which will auto redirect to the access denied page.
EntryPoint :
We need to create a class that will extend Spring’s AuthenticationEntryPoint class and override its method commence to redirect when authentication header is not present in the request.By default This method will reject every unauthenticated request and send error code 401.
AppAuthenticationEntryPoint.java
public class AppAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint { private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); public AppAuthenticationEntryPoint(final String loginFormUrl) { super(loginFormUrl); } /** * Performs the redirect (or forward) to the login form URL. */ public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { // redirect to login page. Use https if forceHttps true String redirectUrl = buildRedirectUrlToLoginPage(request, response, authException); redirectStrategy.sendRedirect(request, response, redirectUrl); } }
If we didn’t set this handler then spring framework push the 403 Forbidden in response. We can even set unauthrozied(401) in response header as authException.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
Authentication Success Handler :
AppSuccessHandler.java
public class AppSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); @Override protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { String targetUrl = determineTargetUrl(authentication); if (response.isCommitted()) { System.out.println("Can't redirect"); return; } redirectStrategy.sendRedirect(request, response, targetUrl); } /* * This method extracts the roles of currently logged-in user and returns * appropriate URL according to his/her role. */ protected String determineTargetUrl(Authentication authentication) { String url = ""; Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); List<String> roles = new ArrayList<String>(); for (GrantedAuthority a : authorities) { roles.add(a.getAuthority()); } if (isAdmin(roles)) { url = "/services/adminpage"; } else if (isUser(roles)) { url = "/services/userpage"; } else { url = "/services/accessDenied"; } return url; } private boolean isUser(List<String> roles) { if (roles.contains("ROLE_USER")) { return true; } return false; } private boolean isAdmin(List<String> roles) { if (roles.contains("ROLE_ADMIN")) { return true; } return false; } public void setRedirectStrategy(RedirectStrategy redirectStrategy) { this.redirectStrategy = redirectStrategy; } protected RedirectStrategy getRedirectStrategy() { return redirectStrategy; } }
When authentication is successful then this handler will invoked and redirect to the page as configured in the determineTargetUrl api based on role.
AppController :
This controller will serve your request for login,logout,userpages and redirects to dynamic jsp page.
public class AppController { @RequestMapping(value = {"/userpage"}, method = RequestMethod.GET) public ModelAndView userPage() { if (isAdminPage()) return adminPage(); ModelAndView model = new ModelAndView(); model.addObject("title", "Spring Security Hello World"); model.addObject("user", getUser()); model.setViewName("user"); return model; } @RequestMapping(value = "/adminpage", method = RequestMethod.GET) public ModelAndView adminPage() { ModelAndView model = new ModelAndView(); model.addObject("title", "Spring Security Hello World"); model.addObject("user", getUser()); model.setViewName("admin"); return model; } @RequestMapping(value = "/login", method = RequestMethod.GET) public ModelAndView login(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException{ ModelAndView model = new ModelAndView(); model.addObject("title", "Login Page"); model.setViewName("login"); return model; } @RequestMapping(value = "/logout", method = RequestMethod.GET) public ModelAndView logout(HttpServletRequest request, HttpServletResponse response) throws ServletException,IOException{ Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null) { new SecurityContextLogoutHandler().logout(request, response, auth); } return login(request,response); } @RequestMapping(value = {"/accessdenied"}, method = RequestMethod.GET) public ModelAndView accessDeniedPage() { ModelAndView model = new ModelAndView(); model.addObject("message", "Either username or password is incorrect."); model.setViewName("accessdenied"); return model; } private String getUser() { String userName = null; Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { userName = ((UserDetails) principal).getUsername(); } else { userName = principal.toString(); } return userName; } private boolean isAdminPage() { Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { Collection<? extends GrantedAuthority> authorities = ((UserDetails) principal).getAuthorities(); if (authorities.size() == 1) { final Iterator<? extends GrantedAuthority> iterator = authorities.iterator(); GrantedAuthority grantedAuthority = iterator.next(); if (grantedAuthority.getAuthority().equals("ADMIN")) { return true; } } } return false; } }
Views :
Login.jsp :
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>${title}</title> <style> //removing style,you can see complete css in the attached source. </style> </head> <body> <br/><br/><br/><br/><br/><br/><br/> <div class="login-block"> <form name='loginForm' action="<c:url value='../j_spring_security_check' />" method='POST'> <h1>Login</h1> <input type="text" id="username" name="username" placeholder="Username" /> <input type="password" id="password" name="password" placeholder="Password" /> <button>Submit</button> </form> </div> </body> </html>
AccessDenied.jsp:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Access Denied Page</title> </head> <body> <h2> Access Denied </h2> <br/>${message}<br/> Click here for<a href="<c:url value="/login" />"> Login</a> </body> </html>
Admin.jsp :
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>HelloWorld page</title> </head> <body> ${title}<br/><br/> Dear ${user}, you are successfully logged into this application as admin. <br/> <a href="<c:url value="/services/logout" />">Logout</a> </body> </html>
User.jsp :
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>HelloWorld page</title> </head> <body> ${title}<br/><br/> Dear ${user}, you are successfully logged into this application. <br/> <a href="<c:url value="/services/logout" />">Logout</a> </body> </html>
Screens :
After deploying above application(spring-security-entry-point-and-role-based-url.war) in web server(tomcat) and you can find below screens