Spring Security Basics

Agenda

  • Basic Security Using Java Config
  • URL Authorization
  • Building a Login Form
  • Implementing Logout
  • Simple Registration Flow
  • Authenticating Real Users
  • Activate New User Via Email
  • Remember Me with Cookie and Persistence
  •  Display Current User
  • Url and On-Method Authorization with Expressions

How do we intercept a servlet ?

How does Spring MVC works ?

What is Spring Security?

Spring MVC

What is Spring Security ?

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

build.gradle

apply plugin: 'war'
apply plugin: 'com.bmuschko.tomcat'

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.bmuschko:gradle-tomcat-plugin:2.2.2'
    }
}

repositories {
    mavenCentral()
    jcenter()
}

dependencies {

    providedCompile 'javax.servlet:servlet-api:2.5', 'javax.servlet:jsp-api:2.0'

    compile 'org.hibernate:hibernate-entitymanager:4.1.7.Final'
    compile 'mysql:mysql-connector-java:5.1.13'
    compile 'org.springframework.data:spring-data-jpa:1.11.4.RELEASE'

    compile 'org.springframework:spring-aspects:4.2.5.RELEASE'
    compile 'org.springframework:spring-webmvc:4.2.5.RELEASE'
    compile 'org.springframework.security:spring-security-web:4.2.3.RELEASE'
    compile 'org.springframework.security:spring-security-config:4.2.3.RELEASE'
    compile 'org.hibernate:hibernate-validator:4.2.0.Final'
    def tomcatVersion = '7.0.57'
    tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
            "org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}",
            "org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}"
}

tomcatRun {
    contextPath = ""
    httpPort = 8080
}

Spring MVC using Java Config

package com.springmvc.config;


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

public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{};

    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{WebConfig.class, SpringSecurityConfig.class, PersistenceConfig.class };
    }

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

Spring java Configuration

package com.springmvc.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;

@Configuration
@EnableWebMvc
@ComponentScan("com.springmvc")
public class WebConfig extends WebMvcConfigurerAdapter{

    @Bean(name ="templateResolver")
    public ServletContextTemplateResolver getTemplateResolver(){
        ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
        templateResolver.setPrefix("/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode("XHTML");
        return templateResolver;
    }

    @Bean(name ="templateEngine")
    public SpringTemplateEngine getTemplateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(getTemplateResolver());
        return templateEngine;
    }

    @Bean(name="viewResolver")
    public ThymeleafViewResolver getViewResolver(){
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(getTemplateEngine());
        return viewResolver;
    }
}

Spring Security Java Configuration

package com.springmvc.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;


@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        managerBuilder
                .inMemoryAuthentication()
                .withUser("user").password("pass").roles("USER");
    }
}

Register the springSecurityFilterChain

package com.springmvc.config;

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

public class SecurityWebApplicationInitializer 
        extends AbstractSecurityWebApplicationInitializer {
}

Adding another user for In-memory authentication

package com.springmvc.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;


@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) 
        throws Exception {
        managerBuilder
                .inMemoryAuthentication()
                .withUser("user").password("pass").roles("USER")
                .and()
                .withUser("user2").password("pass2").roles("USER");
    }
}

Url Authorization

package com.springmvc.config;

import org.springframework.beans.factory.annotation.Autowired;
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;

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        // configuration code
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/home/**").hasAuthority("ROLE_ADMIN")
                //.antMatchers("/home/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .formLogin()
    }
}

A role is just an authority with a special ROLE_ prefix

.antMatchers("/home/index").hasAnyRole("ADMIN","USER")
.antMatchers("/home/index").hasAnyAuthority("ROLE_ADMIN","ROLE_USER")
.antMatchers("/home/index").not().hasAuthority("ROLE_USER")
.antMatchers("/home/index").anonymous()
.antMatchers("/home/index").permitAll()

Exercise 1

  • Introduce Spring Security Java Config file.
  • Introduce configure global method and introduce and create 2 users with in memory Authentication.
  • Override the configure method to place restrictions on authorising certain requests.
  • Use hasRole, hasAnyRole, hasAuthority, hasAnyAuthority, not, anonymous and permit all API's for request authorization.

Building  a login form

package com.springmvc.config;

import org.springframework.beans.factory.annotation.Autowired;
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;


@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        // Configurations
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()
                .authorizeRequests()
                    .anyRequest().authenticated()
                .and()
                .formLogin()
                    .loginPage("/login").permitAll()
                    .loginProcessingUrl("/loginUrl");
    }
}
<html>
<head></head>
<body>
<form method="post" action="/loginUrl">
    <table>
        <tr>
            <td>Username</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
             <td>Password</td>
             <td><input type="text" name="password"></td>
         </tr>

    </table>
    <input type="submit"/>
</form>
</body>
</html>

login.jsp

Controller changes for login

package com.springmvc.controller;


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {

    @RequestMapping(value = "/login")
    public String login(){
        return "login";
    }
}

Implementing Logout

package com.springmvc.config;

// imports

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        //Configuration Code
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()
                .authorizeRequests()
                    .anyRequest().authenticated()
                .and()
                .formLogin()
                    .loginPage("/login").permitAll()
                    .loginProcessingUrl("/loginUrl")
                .and()
                .logout()
                   .permitAll();
                   .logoutRequestMatcher(new AntPathRequestMatcher("/doLogout","GET"));
    }
}

Authentication Success Handler

package com.springmvc.config;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class AuthenticationSuccess extends SimpleUrlAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println("login Success");
        super.onAuthenticationSuccess(request,response,authentication);
    }
}

Logout Success Handler

package com.springmvc.config;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class LogoutSuccess extends SimpleUrlLogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println(">>>>>Successful Logout");
        super.onLogoutSuccess(request,response,authentication);

    }
}
@Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        httpSecurity
                .csrf()
                .disable()

                .authorizeRequests()
                .antMatchers( "/").hasAnyRole("USER","ADMIN")
                .antMatchers("/user/home").anonymous()
                .anyRequest()
                .authenticated()

                .and()
                .formLogin().
                successHandler(authenticationSuccess)
                .loginPage("/login").permitAll()
                .loginProcessingUrl("/loginUrl")

                .and()
                .logout()
                .permitAll()
                .logoutRequestMatcher(new AntPathRequestMatcher("/doLogout","GET")).
                logoutSuccessHandler(logoutSuccess);
    }

Spring Security Configuration for Handlers

Exercise 2

  • Prepare a custom login form
  • Implement logout
  • Implement Authentication success and Logout success events

Authentication using real users

In order to login via Via Registered users . We need to follow the steps below:

  1. Create a Registration for with backend
  2. Create a class which implements User DetailsService
  3. Register the class in Spring security Config

Registration form UI

<html>
<head></head>
<body>
<form method="POST" action="/registerUser">
    <table>
        <tr>
             <td>Username</td>
             <td><input type="text" name="username"/></td>
        </tr>
        <tr>
            <td>password</td>
            <td><input type="text" name="password"/></td>
        </tr>
    </table>
    <input type="submit"/>
</form>
</body>
</html>

Backend for Registration Form

package com.springmvc.controller;

import com.springmvc.entity.User;
import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {
    @Autowired
    UserRepository userRepository;

    @RequestMapping(value = "/")
    public String home(){
        return "home";
    }

    @RequestMapping(value = "/login")
    public String login(){
        return "login";
    }

    @RequestMapping(value = "/register")
    public String register(){
        return "register";
    }

    @RequestMapping("/registerUser")
    public String registerUser(User user){
        userRepository.save(user);
        return "login";
    }
}
package com.springmvc.service;

import com.springmvc.entity.User;
import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.List;

@Service
public class CustomUserDetailsService implements UserDetailsService {

   @Autowired
    UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user=  null;
        try {
            
            user = userRepository.findByUsername(username);
        }catch (Exception ex){
            ex.printStackTrace();
        }

        if(user == null){
            throw new UsernameNotFoundException("User does not exists");
        }
        List<SimpleGrantedAuthority> auths = new java.util.ArrayList<SimpleGrantedAuthority>();
        auths.add(new SimpleGrantedAuthority("ROLE_USER"));
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                true,
                true,
                true,
                true,
                auths
        );
    }
}

UserDetailsService

Registering UserDetailsService

package com.springmvc.config;

import org.springframework.beans.factory.annotation.Autowired;
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;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;


@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService detailsService;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        managerBuilder
                .userDetailsService(detailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()
                .authorizeRequests()
                .antMatchers("/register/**").permitAll()
                .antMatchers("/registerUser/**").permitAll()
                .anyRequest().authenticated()

                .and()
                .formLogin()
                    .loginPage("/login").permitAll()
                    .loginProcessingUrl("/loginUrl")
                .and()
                .logout().permitAll();

    }
}

Exercise 3

  • Create a registration flow to register new users.
  • Implement UserDetailsSevice for custom Authentication

Email Verification

Steps for email Verification:

  1. Introduce enabled field in User Entity
  2. Set enable to false while saving the user and send the generated token to user.
  3. Create a controller action to verify the token
package com.springmvc.entity;

import javax.persistence.*;

@Entity
public class VerificationToken {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private String token;

    @OneToOne
    private User user;

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

VerificationToken Class

Set enable to false while saving the user and send the generated token to user

@RequestMapping("/registerUser")
    public String registerUser(User user, HttpServletRequest httpServletRequest){
        String token= UUID.randomUUID().toString();
        user.setEnabled(false);
        userRepository.save(user);
        String authUrl="http://"+httpServletRequest.getServerName()
                + ":"
                +httpServletRequest.getServerPort()
                + httpServletRequest.getContextPath()
                +"/registrationConfirmation"
                +"?token="+token;
        
        VerificationToken verificationToken = new VerificationToken();
        verificationToken.setToken(token);
        verificationToken.setUser(user);
        verificationTokenRepository.save(verificationToken);

        return "login";
    }

Action to verify the token

    @RequestMapping("/registrationConfirmation")
    public String registrationConfirmation(String token){
        VerificationToken verificationToken = verificationTokenRepository.findByToken(token);
        User user =verificationToken.getUser();
        user.setEnabled(true);
        userRepository.save(user);
        return "redirect:/login";
    }

Exercise 4

Implement email authorization for new registered user.

Remember Me

Enable Remember me in Spring Security Configuration

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()
                .authorizeRequests()
                .antMatchers("/register/**").permitAll()
                .antMatchers("/registerUser/**").permitAll()
                .antMatchers("/registrationConfirmation/**").permitAll()
                .anyRequest().authenticated()

                .and()
                .rememberMe()

                .and()
                .formLogin()
                    .loginPage("/login").permitAll()
                    .loginProcessingUrl("/loginUrl")
                .and()
                .logout()
                .permitAll();

    }

Parameter to be passed from the front end

<html>
<head></head>
<body>
<form method="post" action="/loginUrl">
    <table>
        <tr>
            <td>Username</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
             <td>Password</td>
             <td><input type="text" name="password"></td>
         </tr>
         <tr>
             <td>Remember Me</td>
             <td><input type="checkbox" name="remember-me"/></td>
         </tr>
    </table>
    <input type="submit"/>
</form>
</body>
</html>

More Options with remember me

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()
                .authorizeRequests()
                .antMatchers("/register/**").permitAll()
                .antMatchers("/registerUser/**").permitAll()
                .antMatchers("/registrationConfirmation/**").permitAll()
                .anyRequest().authenticated()

                .and()
                .rememberMe()
                  //Set Expiration Time
                .tokenValiditySeconds(3600)
                  //Change Cookie Name  
                .rememberMeCookieName("my-cookie")
                  // Change Parameter for cookie
                .rememberMeParameter("remember")

                .and()
                .formLogin()
                    .loginPage("/login").permitAll()
                    .loginProcessingUrl("/loginUrl")
                .and()
                .logout().permitAll();
    }

Remember Me with Persistence

create table persistent_logins (
username varchar(64) not null, 
series varchar(64) primary key, 
token varchar(64) not null, 
last_used timestamp not null)

You need to have following table in your database

Make the following changes in Spring Security Configuration

@Autowired
DataSource dataSource;

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf()
                .disable()
                .authorizeRequests()
                .antMatchers("/register/**").permitAll()
                .antMatchers("/registerUser/**").permitAll()
                .antMatchers("/registrationConfirmation/**").permitAll()
                .anyRequest().authenticated()


                //Remember to correct the name of remember me checkbox before this
                .and()
                .rememberMe()
                .tokenRepository(persistentTokenRepository())
                

                .and()
                .formLogin()
                    .loginPage("/login").permitAll()
                    .loginProcessingUrl("/loginUrl")
                .and()
                .logout().permitAll();
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }

Exercise 5

  • Implement remember me 
  • Try various options with remember me i.e tokenValiditySeconds, rememberMeCookieName, rememberMeParameter
  • Implement persistence remember me

Build In Spring Security Expressions

  • hasRole([role]) : Returns true if the current principal has the specified role.
  • hasAnyRole([role1,role2]) : Returns true if the current principal has any of the supplied roles (given as a comma-separated list of strings)
  • principal : Allows direct access to the principal object representing the current user
  • permitAll : Always evaluates to true
  • denyAll : Always evaluates to false
  • isAnonymous() : Returns true if the current principal is an anonymous user
  • isRememberMe() : Returns true if the current principal is a remember-me user
  • isAuthenticated() : Returns true if the user is not anonymous
  • isFullyAuthenticated() : Returns true if the user is not an anonymous or a remember-me user

 

Url Authorization using Expressions

.antMatchers("/home/index").access("hasRole('ADMIN')")
.antMatchers("/home/index").access("hasAnyRole('ADMIN','USER')")
.antMatchers("/home/index").access("isAuthenticated()")
.antMatchers("/home/index").access("permitAll")
.antMatchers("/home/index").access("hasRole('USER') and principal.username == 'user'")

Get Current User

package com.springmvc.service;


import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.security.Principal;

@Service
public class SpringSecurityService {

    @Autowired
    UserRepository userRepository;

    public com.springmvc.entity.User getCurrentUser(){
        User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        com.springmvc.entity.User systemUser=userRepository.findByUsername(user.getUsername());
        return systemUser;
    }
}

Exercise 6

  • Use Spring Security Expressions is access API.
  • Get currentUser 

On method Authorization with Expressions

In order to enable method level authorization we have to use @EnableGlobalMethodSecurity at the top of spring security config.

 



@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    // configuration code

}

@preAuthorize accepts spring security expressions

@RequestMapping(value = "/home/index")
    @PreAuthorize("isAuthenticated()")
    public String home(){
        return "home";
    }
@RequestMapping(value = "/home/index")
    @secured("ROLE_ADMIN")
    public String home(){
        return "home";
    }

@secured annotation accepts ROLE

Exercise 7

  • Demonstrate PreAuthorize and secured annonation for securing methods

Spring Security Basics

By Pulkit Pushkarna

Spring Security Basics

  • 1,790