Spring Security Basics
Agenda
- Basic Security Using Java Config
- URL Authorization
- Building a Login Form
- Implementing Logout
- Simple Registration Flow
- Authenticating Real Users
- Activate New User Via Email
- Remember Me with Cookie and Persistence
- Display Current User
- Url and On-Method Authorization with Expressions
How do we intercept a servlet ?
How does Spring MVC works ?
What is Spring Security?
Spring MVC
What is Spring Security ?
Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements
build.gradle
apply plugin: 'war'
apply plugin: 'com.bmuschko.tomcat'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.bmuschko:gradle-tomcat-plugin:2.2.2'
}
}
repositories {
mavenCentral()
jcenter()
}
dependencies {
providedCompile 'javax.servlet:servlet-api:2.5', 'javax.servlet:jsp-api:2.0'
compile 'org.hibernate:hibernate-entitymanager:4.1.7.Final'
compile 'mysql:mysql-connector-java:5.1.13'
compile 'org.springframework.data:spring-data-jpa:1.11.4.RELEASE'
compile 'org.springframework:spring-aspects:4.2.5.RELEASE'
compile 'org.springframework:spring-webmvc:4.2.5.RELEASE'
compile 'org.springframework.security:spring-security-web:4.2.3.RELEASE'
compile 'org.springframework.security:spring-security-config:4.2.3.RELEASE'
compile 'org.hibernate:hibernate-validator:4.2.0.Final'
def tomcatVersion = '7.0.57'
tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
"org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}",
"org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}"
}
tomcatRun {
contextPath = ""
httpPort = 8080
}
Spring MVC using Java Config
package com.springmvc.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class, SpringSecurityConfig.class, PersistenceConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
Spring java Configuration
package com.springmvc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
@Configuration
@EnableWebMvc
@ComponentScan("com.springmvc")
public class WebConfig extends WebMvcConfigurerAdapter{
@Bean(name ="templateResolver")
public ServletContextTemplateResolver getTemplateResolver(){
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
templateResolver.setPrefix("/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("XHTML");
return templateResolver;
}
@Bean(name ="templateEngine")
public SpringTemplateEngine getTemplateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(getTemplateResolver());
return templateEngine;
}
@Bean(name="viewResolver")
public ThymeleafViewResolver getViewResolver(){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(getTemplateEngine());
return viewResolver;
}
}
Spring Security Java Configuration
package com.springmvc.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
managerBuilder
.inMemoryAuthentication()
.withUser("user").password("pass").roles("USER");
}
}
Register the springSecurityFilterChain
package com.springmvc.config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer
extends AbstractSecurityWebApplicationInitializer {
}
Adding another user for In-memory authentication
package com.springmvc.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder managerBuilder)
throws Exception {
managerBuilder
.inMemoryAuthentication()
.withUser("user").password("pass").roles("USER")
.and()
.withUser("user2").password("pass2").roles("USER");
}
}
Url Authorization
package com.springmvc.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
// configuration code
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/home/**").hasAuthority("ROLE_ADMIN")
//.antMatchers("/home/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
}
}
A role is just an authority with a special ROLE_ prefix
.antMatchers("/home/index").hasAnyRole("ADMIN","USER")
.antMatchers("/home/index").hasAnyAuthority("ROLE_ADMIN","ROLE_USER")
.antMatchers("/home/index").not().hasAuthority("ROLE_USER")
.antMatchers("/home/index").anonymous()
.antMatchers("/home/index").permitAll()
Exercise 1
- Introduce Spring Security Java Config file.
- Introduce configure global method and introduce and create 2 users with in memory Authentication.
- Override the configure method to place restrictions on authorising certain requests.
- Use hasRole, hasAnyRole, hasAuthority, hasAnyAuthority, not, anonymous and permit all API's for request authorization.
Building a login form
package com.springmvc.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
// Configurations
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/loginUrl");
}
}
<html>
<head></head>
<body>
<form method="post" action="/loginUrl">
<table>
<tr>
<td>Username</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>Password</td>
<td><input type="text" name="password"></td>
</tr>
</table>
<input type="submit"/>
</form>
</body>
</html>
login.jsp
Controller changes for login
package com.springmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HomeController {
@RequestMapping(value = "/login")
public String login(){
return "login";
}
}
Implementing Logout
package com.springmvc.config;
// imports
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
//Configuration Code
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/loginUrl")
.and()
.logout()
.permitAll();
.logoutRequestMatcher(new AntPathRequestMatcher("/doLogout","GET"));
}
}
Authentication Success Handler
package com.springmvc.config;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class AuthenticationSuccess extends SimpleUrlAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("login Success");
super.onAuthenticationSuccess(request,response,authentication);
}
}
Logout Success Handler
package com.springmvc.config;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class LogoutSuccess extends SimpleUrlLogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println(">>>>>Successful Logout");
super.onLogoutSuccess(request,response,authentication);
}
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception{
httpSecurity
.csrf()
.disable()
.authorizeRequests()
.antMatchers( "/").hasAnyRole("USER","ADMIN")
.antMatchers("/user/home").anonymous()
.anyRequest()
.authenticated()
.and()
.formLogin().
successHandler(authenticationSuccess)
.loginPage("/login").permitAll()
.loginProcessingUrl("/loginUrl")
.and()
.logout()
.permitAll()
.logoutRequestMatcher(new AntPathRequestMatcher("/doLogout","GET")).
logoutSuccessHandler(logoutSuccess);
}
Spring Security Configuration for Handlers
Exercise 2
- Prepare a custom login form
- Implement logout
- Implement Authentication success and Logout success events
Authentication using real users
In order to login via Via Registered users . We need to follow the steps below:
- Create a Registration for with backend
- Create a class which implements User DetailsService
- Register the class in Spring security Config
Registration form UI
<html>
<head></head>
<body>
<form method="POST" action="/registerUser">
<table>
<tr>
<td>Username</td>
<td><input type="text" name="username"/></td>
</tr>
<tr>
<td>password</td>
<td><input type="text" name="password"/></td>
</tr>
</table>
<input type="submit"/>
</form>
</body>
</html>
Backend for Registration Form
package com.springmvc.controller;
import com.springmvc.entity.User;
import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HomeController {
@Autowired
UserRepository userRepository;
@RequestMapping(value = "/")
public String home(){
return "home";
}
@RequestMapping(value = "/login")
public String login(){
return "login";
}
@RequestMapping(value = "/register")
public String register(){
return "register";
}
@RequestMapping("/registerUser")
public String registerUser(User user){
userRepository.save(user);
return "login";
}
}
package com.springmvc.service;
import com.springmvc.entity.User;
import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user= null;
try {
user = userRepository.findByUsername(username);
}catch (Exception ex){
ex.printStackTrace();
}
if(user == null){
throw new UsernameNotFoundException("User does not exists");
}
List<SimpleGrantedAuthority> auths = new java.util.ArrayList<SimpleGrantedAuthority>();
auths.add(new SimpleGrantedAuthority("ROLE_USER"));
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
true,
true,
true,
true,
auths
);
}
}
UserDetailsService
Registering UserDetailsService
package com.springmvc.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService detailsService;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder managerBuilder) throws Exception {
managerBuilder
.userDetailsService(detailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/register/**").permitAll()
.antMatchers("/registerUser/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/loginUrl")
.and()
.logout().permitAll();
}
}
Exercise 3
- Create a registration flow to register new users.
- Implement UserDetailsSevice for custom Authentication
Email Verification
Steps for email Verification:
- Introduce enabled field in User Entity
- Set enable to false while saving the user and send the generated token to user.
- Create a controller action to verify the token
package com.springmvc.entity;
import javax.persistence.*;
@Entity
public class VerificationToken {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String token;
@OneToOne
private User user;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
VerificationToken Class
Set enable to false while saving the user and send the generated token to user
@RequestMapping("/registerUser")
public String registerUser(User user, HttpServletRequest httpServletRequest){
String token= UUID.randomUUID().toString();
user.setEnabled(false);
userRepository.save(user);
String authUrl="http://"+httpServletRequest.getServerName()
+ ":"
+httpServletRequest.getServerPort()
+ httpServletRequest.getContextPath()
+"/registrationConfirmation"
+"?token="+token;
VerificationToken verificationToken = new VerificationToken();
verificationToken.setToken(token);
verificationToken.setUser(user);
verificationTokenRepository.save(verificationToken);
return "login";
}
Action to verify the token
@RequestMapping("/registrationConfirmation")
public String registrationConfirmation(String token){
VerificationToken verificationToken = verificationTokenRepository.findByToken(token);
User user =verificationToken.getUser();
user.setEnabled(true);
userRepository.save(user);
return "redirect:/login";
}
Exercise 4
Implement email authorization for new registered user.
Remember Me
Enable Remember me in Spring Security Configuration
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/register/**").permitAll()
.antMatchers("/registerUser/**").permitAll()
.antMatchers("/registrationConfirmation/**").permitAll()
.anyRequest().authenticated()
.and()
.rememberMe()
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/loginUrl")
.and()
.logout()
.permitAll();
}
Parameter to be passed from the front end
<html>
<head></head>
<body>
<form method="post" action="/loginUrl">
<table>
<tr>
<td>Username</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>Password</td>
<td><input type="text" name="password"></td>
</tr>
<tr>
<td>Remember Me</td>
<td><input type="checkbox" name="remember-me"/></td>
</tr>
</table>
<input type="submit"/>
</form>
</body>
</html>
More Options with remember me
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/register/**").permitAll()
.antMatchers("/registerUser/**").permitAll()
.antMatchers("/registrationConfirmation/**").permitAll()
.anyRequest().authenticated()
.and()
.rememberMe()
//Set Expiration Time
.tokenValiditySeconds(3600)
//Change Cookie Name
.rememberMeCookieName("my-cookie")
// Change Parameter for cookie
.rememberMeParameter("remember")
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/loginUrl")
.and()
.logout().permitAll();
}
Remember Me with Persistence
create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null)
You need to have following table in your database
Make the following changes in Spring Security Configuration
@Autowired
DataSource dataSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/register/**").permitAll()
.antMatchers("/registerUser/**").permitAll()
.antMatchers("/registrationConfirmation/**").permitAll()
.anyRequest().authenticated()
//Remember to correct the name of remember me checkbox before this
.and()
.rememberMe()
.tokenRepository(persistentTokenRepository())
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/loginUrl")
.and()
.logout().permitAll();
}
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
Exercise 5
- Implement remember me
- Try various options with remember me i.e tokenValiditySeconds, rememberMeCookieName, rememberMeParameter
- Implement persistence remember me
Build In Spring Security Expressions
- hasRole([role]) : Returns true if the current principal has the specified role.
- hasAnyRole([role1,role2]) : Returns true if the current principal has any of the supplied roles (given as a comma-separated list of strings)
- principal : Allows direct access to the principal object representing the current user
- permitAll : Always evaluates to true
- denyAll : Always evaluates to false
- isAnonymous() : Returns true if the current principal is an anonymous user
- isRememberMe() : Returns true if the current principal is a remember-me user
- isAuthenticated() : Returns true if the user is not anonymous
- isFullyAuthenticated() : Returns true if the user is not an anonymous or a remember-me user
Url Authorization using Expressions
.antMatchers("/home/index").access("hasRole('ADMIN')")
.antMatchers("/home/index").access("hasAnyRole('ADMIN','USER')")
.antMatchers("/home/index").access("isAuthenticated()")
.antMatchers("/home/index").access("permitAll")
.antMatchers("/home/index").access("hasRole('USER') and principal.username == 'user'")
Get Current User
package com.springmvc.service;
import com.springmvc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import java.security.Principal;
@Service
public class SpringSecurityService {
@Autowired
UserRepository userRepository;
public com.springmvc.entity.User getCurrentUser(){
User user = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
com.springmvc.entity.User systemUser=userRepository.findByUsername(user.getUsername());
return systemUser;
}
}
Exercise 6
- Use Spring Security Expressions is access API.
- Get currentUser
On method Authorization with Expressions
In order to enable method level authorization we have to use @EnableGlobalMethodSecurity at the top of spring security config.
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
// configuration code
}
@preAuthorize accepts spring security expressions
@RequestMapping(value = "/home/index")
@PreAuthorize("isAuthenticated()")
public String home(){
return "home";
}
@RequestMapping(value = "/home/index")
@secured("ROLE_ADMIN")
public String home(){
return "home";
}
@secured annotation accepts ROLE
Exercise 7
- Demonstrate PreAuthorize and secured annonation for securing methods
Spring Security Basics
By Pulkit Pushkarna
Spring Security Basics
- 1,790