Tuesday, July 5, 2016

Spring Security 4 Logout Example (Back Button issue)

Spring Security 4 Logout Example


Generally, In your views, you should be providing a simple logout link to logout a user, something like shown below:
<%@ 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"%>
     http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    Admin page


    Dear ${user}, Welcome to Admin Page.
     href="">Logout



Nothing fancy about it. Now , We just need to map this /logout link in our controller. Create a new method like shown below:
@RequestMapping(value="/logout", method = RequestMethod.GET)
public String logoutPage (HttpServletRequest request, HttpServletResponse response) {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    if (auth != null){   
        new SecurityContextLogoutHandler().logout(request, response, auth);
    }
    return "redirect:/login?logout";//You can redirect wherever you want, but generally it's a good practice to show login screen again.
}
Here firstly we identified if user was authenticated before usingSecurityContextHolder.getContext().getAuthentication(). If he/she was, then we calledSecurityContextLogoutHandler().logout(request, response, auth) to logout user properly.
This logout call performs following:
  • Invalidates HTTP Session ,then unbinds any objects bound to it.
  • Removes the Authentication from the SecurityContext to prevent issues with concurrent requests.
  • Explicitly clears the context value from the current thread.
That’s it. You don’t need anything else anywhere in your application to handle logout. Notice that you don’t even need to do anything special in your spring configuration(xml or annotation based), shown below just for information:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

     
    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("bill").password("abc123").roles("USER");
        auth.inMemoryAuthentication().withUser("admin").password("root123").roles("ADMIN");
        auth.inMemoryAuthentication().withUser("dba").password("root123").roles("ADMIN","DBA");
    }
     
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       
      http.authorizeRequests()
        .antMatchers("/", "/home").permitAll()
        .antMatchers("/admin/**").access("hasRole('ADMIN')")
        .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
        .and().formLogin().loginPage("/login")
        .usernameParameter("ssoId").passwordParameter("password")
        .and().exceptionHandling().accessDeniedPage("/Access_Denied");
    }
}
There is no special logout handling mentioned in above configuration.
Above security configuration in XML configuration format would be:
      
     auto-config="true" >
         pattern="/" access="hasRole('USER')" />
         pattern="/home" access="hasRole('USER')" />
         pattern="/admin**" access="hasRole('ADMIN')" />
         pattern="/dba**" access="hasRole('ADMIN') and hasRole('DBA')" />
          login-page="/login"
                     username-parameter="ssoId"
                     password-parameter="password"
                     authentication-failure-url="/Access_Denied" />
    

  
     >
        
            
                 name="bill"  password="abc123"  authorities="ROLE_USER" />
                 name="admin" password="root123" authorities="ROLE_ADMIN" />
                 name="dba"   password="root123" authorities="ROLE_ADMIN,ROLE_DBA" />
            

        

    

     

Rest of application code is same as mentioned in every post in this series

Following technologies being used:
  • Spring 4.1.6.RELEASE
  • Spring Security 4.0.1.RELEASE
  • Maven 3
  • JDK 1.7
  • Tomcat 7
  • STS
Let’s begin.
1.     Create  a Spring MVC project using STS
2.     Delete the web.xml as we will be doing java Configuration
3.     Delete the spring folder from WEB-INF
4.     Update pom.xml
     

·         First thing to notice here is the maven-war-plugin declaration. As we are using full annotation configuration, we don’t even use web.xml, so we will need to configure this plugin in order to avoid maven failure to build war package. We are using latest versions(at time of writing) of Spring and Spring Security.
·         Along with that, we have also included JSP/Servlet/Jstl dependencies which we will be needing as we are going to use servlet api’s and jstl view in our code. In general, containers might already contains these libraries, so we can set the scope as ‘provided’ for them in pom.xml.
 Let’s now add the content mentioned in above structure explaining each in detail.
  Create com.sbk.logout.configuratio.SecurityConfiguration  class
177.         package com.sbk.logout.configuration;
178.          
179.         import org.springframework.beans.factory.annotation.Autowired;
180.         import org.springframework.context.annotation.Configuration;
181.         import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
182.         import org.springframework.security.config.annotation.web.builders.HttpSecurity;
183.         import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
184.         import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
185.          
186.         @Configuration
187.         @EnableWebSecurity
188.         public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
189.          
190.             
191.              @Autowired
192.              public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
193.              auth.inMemoryAuthentication().withUser("smita").password("password").roles("USER");
194.              auth.inMemoryAuthentication().withUser("admin").password("password").roles("ADMIN");
195.              auth.inMemoryAuthentication().withUser("dba").password("password").roles("ADMIN","DBA");
196.              }
197.             
198.              @Override
199.              protected void configure(HttpSecurity http) throws Exception {
200.                
201.                http.authorizeRequests()
202.                   .antMatchers("/", "/home").permitAll()
203.                   .antMatchers("/admin/**").access("hasRole('ADMIN')")
204.                   .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
205.                   .and().formLogin().loginPage("/login")
206.                   .usernameParameter("ssoId").passwordParameter("password")
207.                 .and().exceptionHandling().accessDeniedPage("/Access_Denied");
208.              }
209.         }
210.     

Register the springSecurityFilter with war com.sbk.logout.configuration.SecurityWebApplicationInitializer

Below specified initializer class registers the springSecurityFilter [created in previous step] with application war.
 package com.sbk.logout.configuration;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

}

package com.websystique.springsecurity.configuration; 
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; 
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

}
This class is exactly same as in Previous demo0001SpringSecurity4HelloWorldAnnotation
Above setup in XML configuration format would be(In web.xml):
    springSecurityFilterChain
    org.springframework.web.filter.DelegatingFilterProxy

 
    springSecurityFilterChain
    /*

Add Controller com.sbk.logout.controller


package com.sbk.logout.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HelloWorldController {

    
     @RequestMapping(value = { "/", "/home" }, method = RequestMethod.GET)
     public String homePage(ModelMap model) {
           model.addAttribute("greeting", "Hi, Welcome to mysite");
           return "welcome";
     }

     @RequestMapping(value = "/admin", method = RequestMethod.GET)
     public String adminPage(ModelMap model) {
           model.addAttribute("user", getPrincipal());
           return "admin";
     }
    
     @RequestMapping(value = "/db", method = RequestMethod.GET)
     public String dbaPage(ModelMap model) {
           model.addAttribute("user", getPrincipal());
           return "dba";
     }

     @RequestMapping(value = "/Access_Denied", method = RequestMethod.GET)
     public String accessDeniedPage(ModelMap model) {
           model.addAttribute("user", getPrincipal());
           return "accessDenied";
     }

     @RequestMapping(value = "/login", method = RequestMethod.GET)
     public String loginPage() {
           return "login";
     }

     @RequestMapping(value="/logout", method = RequestMethod.GET)
     public String logoutPage (HttpServletRequest request, HttpServletResponse response) {
           Authentication auth = SecurityContextHolder.getContext().getAuthentication();
           if (auth != null){   
                new SecurityContextLogoutHandler().logout(request, response, auth);
           }
           return "redirect:/login?logout";//You can redirect wherever you want, but generally it's a good idea to show login screen again.
     }

     private String getPrincipal(){
           String userName = null;
           Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

           if (principal instanceof UserDetails) {
                userName = ((UserDetails)principal).getUsername();
           } else {
                userName = principal.toString();
           }
           return userName;
     }

}

Add SpringMVC Configuration Class

com.sbk.logout.configuration
package com.sbk.logout.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.sbk.logout")
public class HelloWorldConfiguration extends WebMvcConfigurerAdapter {
    
     @Bean(name="HelloWorld")
     public ViewResolver viewResolver() {
           InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
           viewResolver.setViewClass(JstlView.class);
           viewResolver.setPrefix("/WEB-INF/views/");
           viewResolver.setSuffix(".jsp");

           return viewResolver;
     }

     /*
     * Configure ResourceHandlers to serve static resources like CSS/ Javascript etc...
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("/static/");
    }
}

Add Initializer class(This class is exactly same as in previous post.)

com.sbk.logout.configuration

package com.sbk.logout.configuration;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

     @Override
     protected Class[] getRootConfigClasses() {
           return new Class[] { HelloWorldConfiguration.class };
     }

     @Override
     protected Class[] getServletConfigClasses() {
           return null;
     }

     @Override
     protected String[] getServletMappings() {
           return new String[] { "/" };
     }

}


Notice that above initializer class extends AbstractAnnotationConfigDispatcherServletInitializerwhich is the base class for all WebApplicationInitializer implementations. Implementations of WebApplicationInitializer configures ServletContext programatically, for Servlet 3.0 environments. It means we won’t be using web.xml and we will deploy the app on Servlet 3.0 container.

 Add Views


========================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"%>
     
      AccessDenied page


      Dear ${user}, You are not authorized to access this page
      ">Logout


========================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"%>
     
      Admin page

      Dear ${user}, Welcome to Admin Page.
      ">Logout


========================dba.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"%>
     
      DBA page

      Dear ${user}, Welcome to DBA Page.
      ">Logout


=============welcome.jsp========================
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
     
      Welcome page

      Greeting : ${greeting}
      This is a welcome page.


============= 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"%>
           
                       
Notice the CSRF (Cross Site Request Forgery)related line in above jsp:
type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

This is required to protect against CSRF attacks.

 

 

Build and Deploy the application

NOTE : Before deploying, check the java build path(1.7), Check  java compiler(1.7), java facets(jdk1.7,web module version 3.0), targeted runtime(tom cat7) and Add Maven dependency to build path


Run the application(http://localhost:8087/logout/)


·         Now try to access admin page on http://localhost:8087/logout/admin, you will be prompted for login.

·        Provide credentials of a ‘USER’ role.enter [smita,password]

·        Submit, you will see AccessDenied Page


Logout. http://localhost:8087/logout/login?logout

·        click on browser back button, you will remain at login screen.

No comments:

Post a Comment