Spring Security Intermediate

Agenda

  • Quick revision of basics
  • InMemory, JDBC and JPA user storage
  • Encrypting Password
  • Method Security using Running As
  • SecurityContext
  • Creating Custom Filter
  • Switch User
  • Creating Custom Authentication Provider
  • Tracking Active Users
  • Roles and privileges
  • Method Security with AOP
  • Spring Security Integration with Servlet API

In Memory Authentication

In Memory Authentication stores the credentials on server. Doesn't stores it into database. Below is the example on In memory Authentication.

//Spring Security Config

@Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        managerBuilder
                .inMemoryAuthentication()
                .withUser("user").password("pass").roles("USER").and()
                .withUser("user1").password("pass2").roles("ADMIN");
    }
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.web.util.matcher.AntPathRequestMatcher;

import javax.sql.DataSource;
import java.util.Date;

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    DataSource dateSource;

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

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


    }
}

JDBC Authentication

In case of JDBC authentication Credentials are stored in relational database like mysql, postgres etc

//Spring Security Config

@Autowired
    DataSource dateSource;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        managerBuilder
                .jdbcAuthentication().dataSource(dateSource)
                .withUser("user").password("pass").roles("USER").and()
                .withUser("user1").password("pass2").roles("ADMIN");
    }
create table users(
  username varchar(50) not null primary key, 
  password varchar(500) not null, 
  enabled boolean not null
);

create table authorities (
  username varchar(50) not null, 
  authority varchar(50) not null, 
  constraint fk_authorities_users foreign key(username) references users(username)
);

Tables needed by JDBC 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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.sql.DataSource;
import java.util.Date;

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    DataSource dateSource;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        managerBuilder
                .jdbcAuthentication().dataSource(dateSource)
                .withUser("user").password("pass").roles("USER").and()
                .withUser("user1").password("pass2").roles("ADMIN");
    }

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


    }
}

Hibernate/JPA user storage

In case Hibernate/JPA user storage for Authentication we need following to follow following steps:

  1. We need to a CustomUserDetailsService class which implements UserDetailsService. (Full class defination is on the next slide)
  2. Once we are ready with the CustomUserDetailsService then we will configure it in Spring Security Config
//Spring Security Config
@Autowired
    UserDetailsService userDetailsService;

    @Autowired
    UserRepository userRepository;

    @PostConstruct
    void postConstruct(){
        User user = new User();
        user.setUsername("user");
        user.setPassword("pass");
        userRepository.save(user);
    }

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

    }
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.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.Repository;
import org.springframework.stereotype.Service;

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_ADMIN"));
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                true,
                true,
                true,
                true,
                auths
        );
    }
}
package com.springmvc.config;

import com.springmvc.entity.User;
import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.annotation.PostConstruct;

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    UserRepository userRepository;

    @PostConstruct
    void postConstruct(){
        User user = new User();
        user.setUsername("user");
        user.setPassword("pass");
        userRepository.save(user);
    }

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

    }

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


    }
}

Encrypting Password

From security point of view it is very important to encrypt our password or else someone who get the access to out database can easily log into system by using the username and password.

In order to encode our password we can use following way:

  1. Firstly we need to create a bean of Standard password encoder
  2. Specify the passwordEncoder in configureGlobal
@Bean
    PasswordEncoder passwordEncoder(){
        return new StandardPasswordEncoder();
    }

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

StandardPasswordEncoder,  implementation has an internal salt generator. This is a 8-bit random salt value that’s generated via a Java SecureRandom and is stored as the first 8 bytes of the hash.

package com.springmvc.config;

import com.springmvc.entity.User;
import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.Date;

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    UserRepository userRepository;

    @PostConstruct
    void postConstruct(){
        User user = new User();
        user.setUsername("user");
        user.setPassword(passwordEncoder().encode("pass"));
        userRepository.save(user);
    }

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

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

    @Bean
    PasswordEncoder passwordEncoder(){
        return new StandardPasswordEncoder();
    }


}

Bcrypt 

bcrypt uses a built-in salt value which is different for each stored password - so we can just use the built-in support and not configure anything extra to use a salt.

Note that the salt is a random 16 byte value. 

The amount of work it does can be tuned using the "strength" parameter which takes values from 4 to 31 (the default is 10). The higher the value, the more work has to be done to calculate the hash.

@Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(12);
    }
package com.springmvc.config;

import com.springmvc.entity.User;
import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.Date;

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    UserRepository userRepository;

    @PostConstruct
    void postConstruct(){
        User user = new User();
        user.setUsername("user");
        user.setPassword(passwordEncoder().encode("pass"));
        userRepository.save(user);
    }

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

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

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(12);
    }


}

Exercise 1

  • Implement following user storage startergies for Spring Security User
  1. InMemory Authentiation
  2. JDBC Authentication
  3. JPA User Storage Authentication

 

  • Encrypt the Password first by Standard Password encoder and then by Brypt Password Encoder 

 

Method Security using Run As

Scenario where Run-As is useful is if you simply need to temporarily elevate the privileges of the current user for an one-off operation.

let’s say we’re generating a new report that needs to access more data than the user may regularly need to see.

 

package com.springmvc.config;


import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.access.intercept.RunAsManagerImpl;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration{


    protected RunAsManager runAsManager(){
        RunAsManagerImpl runAsManager = new RunAsManagerImpl();
        runAsManager.setKey("MyRunAsKey");
        return runAsManager;
    }
}

Configuration steps for Run As:

  1. Create a class MethodSecurityConfig which extends GlobalMethodSecurityConfiguration and override the runAsManager method (See the next slide)
package com.springmvc.config;

import com.springmvc.entity.User;
import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.intercept.RunAsImplAuthenticationProvider;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.Date;

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    UserRepository userRepository;

    @PostConstruct
    void postConstruct(){
        User user = new User();
        user.setUsername("user");
        user.setPassword(passwordEncoder().encode("pass"));
        userRepository.save(user);
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        managerBuilder.authenticationProvider(runAsAuthenticationProvider());
        managerBuilder.authenticationProvider(daoAuthenticationProvider());
    }

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

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(12);
    }

    @Bean
    public AuthenticationProvider runAsAuthenticationProvider(){
        RunAsImplAuthenticationProvider runAsImplAuthenticationProvider = new RunAsImplAuthenticationProvider();
        runAsImplAuthenticationProvider.setKey("MyRunAsKey");
        return runAsImplAuthenticationProvider;
    }

    @Bean
    public AuthenticationProvider daoAuthenticationProvider(){
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }

}

2 . Create two bean of authentication provider one for DaoAuthenticationProvider and another for RunAsAuthenticationProvider.

package com.springmvc.controller;

import com.springmvc.service.RunAsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class RunAsController {

    @Autowired
    RunAsService runAsService;

    @RequestMapping("/runas")
    @ResponseBody
    @Secured({"ROLE_ADMIN", "RUN_AS_REPORTER"})
    public String runAs(){
        return runAsService.display();
    }

    @RequestMapping("/runas2")
    @ResponseBody
    @Secured({"ROLE_ADMIN"})
    public String runAs2(){
        return runAsService.display();
    }
}
package com.springmvc.service;

import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Service;

@Service
public class RunAsService {

    @Secured({"ROLE_RUN_AS_REPORTER"})
    public String display(){
        return "Run as successful";
    }
}

SecurityContext

Getting the current user

  • the SecurityContext - the fundamental security information associated to the running thread
  • the SecurityContextHolder - the storage mechanism for this security information.
SecurityContextHolder.getContext().setAuthentication(authResult);

By default, the SecurityContentHolder uses a ThreadLocal to store these details

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.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
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.Repository;
import org.springframework.stereotype.Service;

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_ADMIN"));
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                true,
                true,
                true,
                true,
                auths
        );
    }

   public UserDetails getCurrentUser(){
        UserDetails userDetails=(UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        return userDetails;
    }
}
package com.springmvc.controller;


import com.springmvc.entity.User;
import com.springmvc.repositories.UserRepository;
import com.springmvc.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {

    @Autowired
    UserRepository userRepository;

    @Autowired
    CustomUserDetailsService userDetailsService;

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

    @RequestMapping(value = "/current/user")
    @responseBody
    public String home(){
        UserDetails userDetails = userDetailsService.getCurrentUser();
        return ""Hello "+userDetails.getUsername()";
    }


}

Exercise 2

  • Secure one of the service method by Run As Authenticate and try to access the secured method from authorised and unauthorised actions of the Controller
  • Get the CurrentUser and Display the name of the Current User on Browser

Custom Filter

In order to create a custom filter  we need to extend GenericFilterBean and override doFilter method. (See the next for code details). 

Following is the code to configure the custom filter.

// Spring Security Config

@Autowired
LoggingFilter loggingFilter;

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(loggingFilter, AnonymousAuthenticationFilter.class)
                .csrf()
                .disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll()
                .and()
                .logout()
                .permitAll()
                .logoutRequestMatcher(new AntPathRequestMatcher("/doLogout","GET"));
    }
package com.springmvc.filter;

import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
public class LoggingFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest)request;
        System.out.println("Curretly Executing URL---->>"+httpServletRequest.getRequestURL().toString());
        chain.doFilter(request,response);
    }
}
package com.springmvc.config;

import com.springmvc.entity.User;
import com.springmvc.filter.LoggingFilter;
import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.security.access.intercept.RunAsImplAuthenticationProvider;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.Date;

@EnableWebSecurity
@EnableAsync
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    UserRepository userRepository;

    @Autowired
    LoggingFilter loggingFilter;

    @PostConstruct
    void postConstruct(){
        User user = new User();
        user.setUsername("user");
        user.setPassword(passwordEncoder().encode("pass"));
        userRepository.save(user);
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        managerBuilder.authenticationProvider(runAsAuthenticationProvider());
        managerBuilder.authenticationProvider(daoAuthenticationProvider());
    }

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

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(12);
    }

    @Bean
    public AuthenticationProvider runAsAuthenticationProvider(){
        RunAsImplAuthenticationProvider runAsImplAuthenticationProvider = new RunAsImplAuthenticationProvider();
        runAsImplAuthenticationProvider.setKey("MyRunAsKey");
        return runAsImplAuthenticationProvider;
    }

    @Bean
    public AuthenticationProvider daoAuthenticationProvider(){
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }

}

Switch User

In order to switch user we need to create a bean of SwitchUserFilter and then configure it in configure method as follows:

@Bean
    public SwitchUserFilter switchUserFilter(){
        SwitchUserFilter switchUserFilter = new SwitchUserFilter();
        switchUserFilter.setUserDetailsService(userDetailsService);
        switchUserFilter.setSwitchUserUrl("/switch/user");
        switchUserFilter.setTargetUrl("/");
        return switchUserFilter;
    }


@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(loggingFilter, AnonymousAuthenticationFilter.class)
                .addFilter(switchUserFilter())
                .csrf()
                .disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll()
                .and()
                .logout()
                .permitAll()
                .logoutRequestMatcher(new AntPathRequestMatcher("/doLogout","GET"));
    }

Use /switch/user?username=user1 endpoint to switch user

package com.springmvc.config;

import com.springmvc.entity.User;
import com.springmvc.filter.LoggingFilter;
import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.security.access.intercept.RunAsImplAuthenticationProvider;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.Date;

@EnableWebSecurity

public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    UserRepository userRepository;

    @Autowired
    LoggingFilter loggingFilter;

    @PostConstruct
    void postConstruct(){
        User user = new User();
        user.setUsername("user");
        user.setPassword(passwordEncoder().encode("pass"));
        userRepository.save(user);
        User user1 = new User();
        user1.setUsername("user1");
        user1.setPassword(passwordEncoder().encode("pass1"));
        userRepository.save(user1);
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        managerBuilder.authenticationProvider(runAsAuthenticationProvider());
        managerBuilder.authenticationProvider(daoAuthenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(loggingFilter, AnonymousAuthenticationFilter.class)
                .addFilter(switchUserFilter())
                .csrf()
                .disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll()
                .and()
                .logout()
                .permitAll()
                .logoutRequestMatcher(new AntPathRequestMatcher("/doLogout","GET"));
    }

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(12);
    }

    @Bean
    public AuthenticationProvider runAsAuthenticationProvider(){
        RunAsImplAuthenticationProvider runAsImplAuthenticationProvider = new RunAsImplAuthenticationProvider();
        runAsImplAuthenticationProvider.setKey("MyRunAsKey");
        return runAsImplAuthenticationProvider;
    }

    @Bean
    public AuthenticationProvider daoAuthenticationProvider(){
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }

    @Bean
    public SwitchUserFilter switchUserFilter(){
        SwitchUserFilter switchUserFilter = new SwitchUserFilter();
        switchUserFilter.setUserDetailsService(userDetailsService);
        switchUserFilter.setSwitchUserUrl("/switch/user");
        switchUserFilter.setTargetUrl("/");
        return switchUserFilter;
    }

}

Exercise 3

  • Introduce a custom filter before AnonymousAuthenticationFilter to log the currently Executing Urls
  • Implement the Functionality of Switching User

Custom Authentication Provider

Sometimes we need have more control over authentication process for which we can create our customAuthenticationProvider (See the code the next slide)

 

Once we are ready with our CustomAuthenticationProvider

we can configure it in Spring Security Config in following way:


@Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        managerBuilder.authenticationProvider(customAuthenticationProvider);
    }

@Autowired
    CustomAuthenticationProvider customAuthenticationProvider;
package com.springmvc.authenticationProvider;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
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.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String name = authentication.getName();
        String password = authentication.getCredentials().toString();
        if(!isAuthenticationSupported(authentication)){
            return  null;
        }

        if(!isThirdPartyAuthenticated()){
            throw  new BadCredentialsException("Credentials Not Supported");
        }else {
            return new UsernamePasswordAuthenticationToken(name, password, new ArrayList<GrantedAuthority>(Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"))));
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }

    boolean isAuthenticationSupported(Authentication authentication){
       return true;
    }

    boolean isThirdPartyAuthenticated(){
        return true;
    }

}
package com.springmvc.config;

import com.springmvc.authenticationProvider.CustomAuthenticationProvider;
import com.springmvc.entity.User;
import com.springmvc.filter.LoggingFilter;
import com.springmvc.repositories.UserRepository;
import com.springmvc.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.security.access.intercept.RunAsImplAuthenticationProvider;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.Date;

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    UserRepository userRepository;

    @Autowired
    LoggingFilter loggingFilter;

    @Autowired
    CustomAuthenticationProvider customAuthenticationProvider;

    @PostConstruct
    void postConstruct(){
        User user = new User();
        user.setUsername("user");
        user.setPassword(passwordEncoder().encode("pass"));
        userRepository.save(user);
        User user1 = new User();
        user1.setUsername("user1");
        user1.setPassword(passwordEncoder().encode("pass1"));
        userRepository.save(user1);
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
        managerBuilder.authenticationProvider(customAuthenticationProvider);
//        managerBuilder.authenticationProvider(runAsAuthenticationProvider());
//        managerBuilder.authenticationProvider(daoAuthenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(loggingFilter, AnonymousAuthenticationFilter.class)
                .addFilter(switchUserFilter())
                .csrf()
                .disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll()
                .and()
                .logout()
                .permitAll()
                .logoutRequestMatcher(new AntPathRequestMatcher("/doLogout","GET"));
    }

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(12);
    }

    @Bean
    public AuthenticationProvider runAsAuthenticationProvider(){
        RunAsImplAuthenticationProvider runAsImplAuthenticationProvider = new RunAsImplAuthenticationProvider();
        runAsImplAuthenticationProvider.setKey("MyRunAsKey");
        return runAsImplAuthenticationProvider;
    }

    @Bean
    public AuthenticationProvider daoAuthenticationProvider(){
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        return daoAuthenticationProvider;
    }

    @Bean
    public SwitchUserFilter switchUserFilter(){
        SwitchUserFilter switchUserFilter = new SwitchUserFilter();
        switchUserFilter.setUserDetailsService(userDetailsService);
        switchUserFilter.setSwitchUserUrl("/switch/user");
        switchUserFilter.setTargetUrl("/");
        return switchUserFilter;
    }

}

Tracking Active users

In order to track active users on our application via Spring Security we can to the follow the steps below.

Create a session registry bean in Spring Security Config

@Bean
    SessionRegistry sessionRegistry(){
        return new SessionRegistryImpl();
    }
@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(loggingFilter, AnonymousAuthenticationFilter.class)
                .addFilter(switchUserFilter())
                .csrf()
                .disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll()
                .and()
                .logout()
                .permitAll()
                .logoutRequestMatcher(new AntPathRequestMatcher("/doLogout","GET"))
                .and().sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry());

    }

Configure the bean with HttpSecurity

Create Service to get the active users

@Service
public class ActiveUserService {
    @Autowired
    private SessionRegistry sessionRegistry;

    public List<String> getAllActiveUsers() {
        List<String> userList=new ArrayList<>();
        List<Object> principals = sessionRegistry.getAllPrincipals();
         for (Object object:principals){
             User user=(User)object;
             if(!sessionRegistry.getAllSessions(user,false).isEmpty()){
                 userList.add(user.getUsername());
             }
         }
        return userList;
    }
}
@RequestMapping("/active/users")
    @ResponseBody
    public String activeUsers(){
        return activeUserService.getAllActiveUsers().toString();
    }

Introduce action to get active users

Exercise 4

  • Introduce Custom Authetication Provider and authenticate via it.
  • Track the active user of your app

Roles and Privileges

Privilege represents a granular, low level capability in the system

  • can delete a Resource 1 - that’s a Privilege
  • can update Resource 1 - is another Privilege
  • can delete Resource 2 - is yet another, different Privilege

Role as a collection of Privileges, and a higher level concept that’s user facing

  • ADMIN
  • USER

Demo For Roles and Privileges

First we need to create 3 entity classes

User class

package com.springmvc.entity;

import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
public class User implements Serializable{

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

    private String username;

    private String password;

    @ManyToMany(fetch = FetchType.EAGER)
    Set<Role> roles= new HashSet<>();

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

Role Class

package com.springmvc.entity;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
public class Role {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    Integer id;

    @ManyToMany(fetch = FetchType.EAGER)
    private Set<Privilege> privileges = new HashSet<>();

    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }


    public Set<Privilege> getPrivileges() {
        return privileges;
    }

    public void setPrivileges(Set<Privilege> privileges) {
        this.privileges = privileges;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Privilege Class

package com.springmvc.entity;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
public class Privilege {

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

    private String name;

    @ManyToMany(mappedBy = "privileges")
    Set<Role> roles=new HashSet<>();

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}

Now we need to create 3 repositories for our entity classes

package com.springmvc.repositories;

import com.springmvc.entity.Privilege;
import org.springframework.data.repository.CrudRepository;

public interface PrivilegeRepository extends CrudRepository<Privilege,Integer> {

}
package com.springmvc.repositories;

import com.springmvc.entity.Role;
import org.springframework.data.repository.CrudRepository;


public interface RoleRepository extends CrudRepository<Role,Integer> {

}
package com.springmvc.repositories;

import com.springmvc.entity.User;
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<User,Integer> {

    User findByUsername(String username);
}

Privilege related changes in user detail service

package com.springmvc.service;

import com.springmvc.entity.Privilege;
import com.springmvc.entity.Role;
import com.springmvc.entity.User;
import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
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.Repository;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class CustomUserDetailsService implements UserDetailsService{

    @Autowired
    UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("load user >>>>>"+username);
        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>();


        for(Role role:user.getRoles()) {
            for(Privilege privilege : role.getPrivileges()) {
                auths.add(new SimpleGrantedAuthority("ROLE_"+privilege.getName()));
            }
        }

        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                true,
                true,
                true,
                true,
                auths
        );
    }


   public UserDetails getCurrentUser(){
            UserDetails userDetails=(UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            return userDetails;
    }
}

Bootstrap data for roles and privileges in Spring Security Config

@PostConstruct
    void postConstruct(){


        Privilege privilege1 = new Privilege();
        privilege1.setName("READ");
        privilegeRepository.save(privilege1);

        Privilege privilege2 = new Privilege();
        privilege2.setName("WRITE");
        privilegeRepository.save(privilege2);

        Role role1 = new Role();
        role1.setName("ROLE_ADMIN");
        role1.setPrivileges(new HashSet<>(Arrays.asList(privilege1,privilege2)));
        roleRepository.save(role1);

        Role role2= new Role();
        role2.setName("ROLE_USER");
        role2.setPrivileges(new HashSet<>(Arrays.asList(privilege1)));
        roleRepository.save(role2);

        User user = new User();
        user.setUsername("user");
        user.setPassword(passwordEncoder().encode("pass"));
        user.setRoles(new HashSet<>(Arrays.asList(role1)));
        userRepository.save(user);

        User user1 = new User();
        user1.setUsername("user1");
        user1.setPassword(passwordEncoder().encode("pass1"));
        user1.setRoles(new HashSet<>(Arrays.asList(role2)));
        userRepository.save(user1);

    }

Introduce action to get the authorities

@RequestMapping("authority")
    @ResponseBody
    public String userAuthority(){
        return SecurityContextHolder.getContext().getAuthentication().getAuthorities().toString();

    }

Exercise 5

  • Configure Roles in User Entity (Many to Many)
  • Configure Privileges in Role Entity(Many to Many)
  • BootStrap the data for User, Role and Privilege Entity.
  • Try to access authority of Currently logged in User

Secure Method Invocation with AOP

In case if we want to restrict the method invocation through Spring Security without using annotations. Then we can go with following approach of AOP. We can either restrict methods of entire class or single method with the help of pointcut expression as follows.

//Method Security Config 

@Override
    protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
        Map<String,List<ConfigAttribute>> methodMap=new HashMap<>();
        methodMap.put("com.springmvc.controller.HomeController.*", SecurityConfig.createList("ROLE_ADMIN"));
        return new MapBasedMethodSecurityMetadataSource(methodMap);
    }

Spring security integration with Servlet API

HttpServletRequest.getRemoteUser() : It returns the current Username.

@RequestMapping("/servletAPI")
    @ResponseBody()
    public String servletAPI(HttpServletRequest httpServletRequest){
        return httpServletRequest.getRemoteUser();
    }

HttpServletRequest.getUserPrinciple : It returns the UserDetails instance which is created from loadUserByUsername method

@RequestMapping("/servletAPI")
    @ResponseBody()
    public String servletAPI(HttpServletRequest httpServletRequest){
        return httpServletRequest.getUserPrincipal().toString();
    }

httpServletRequest.isUserInRole : Determines if the user contains particular role or not

@RequestMapping("/servletAPI")
    @ResponseBody()
    public String servletAPI(HttpServletRequest httpServletRequest){
        return ""+httpServletRequest.isUserInRole("ADMIN");
    }

Exercise 6

  • Secure method invocation with AOP so that only ADMIN is able to access HomeController.
  • Try out different Servlet API methods which are integrated with Spring Security.
Made with Slides.com