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:
- We need to a CustomUserDetailsService class which implements UserDetailsService. (Full class defination is on the next slide)
- 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:
- Firstly we need to create a bean of Standard password encoder
- 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
- InMemory Authentiation
- JDBC Authentication
- 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:
- 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.
Spring Security Intermediate
By Pulkit Pushkarna
Spring Security Intermediate
- 1,168