Agenda
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:
//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:
@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
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:
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
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
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
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
Roles and Privileges
Privilege represents a granular, low level capability in the system
Role as a collection of Privileges, and a higher level concept that’s user facing
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
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