Friday, 16 August 2019

Spring Security: Form based authentication

In this post, I am going to explain how to add form based authentication to the application.

Step 1: Extend 'WebSecurityConfigurerAdapter' class and configure form based authentication.
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfiguration extends WebSecurityConfigurerAdapter {


 @Override
 protected void configure(HttpSecurity httpSecurity) throws Exception {
  httpSecurity.csrf().disable().authorizeRequests().antMatchers("/", "/public/*", "/css/*", "/js/*", "/hello").permitAll()
    .anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().logout()
    .invalidateHttpSession(true).clearAuthentication(true)
    .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/logoutSuccess")
    .permitAll();

 }

}


Above snippet tells the api /login redirect to login page and once user successfully logged out, he will be redirected to logoutSuccess api.



Below APIs do not require any authentication.

a.   /public/*

b.   /css/*

c.    /js/*

d.   /hello



Step 2: Define a controller that take care of /login and /logoutSuccess apis.

@Controller
public class HomeController {
 @GetMapping("/hello")
 public ModelAndView sayHello(Model model) {
  ModelAndView modelAndView = new ModelAndView("hello");
  return modelAndView;
 }

 @GetMapping("/secure")
 public ModelAndView securePage(Model model) {
  ModelAndView modelAndView = new ModelAndView("secure");
  return modelAndView;
 }

 @GetMapping("/login")
 public ModelAndView loginPage(Model model) {
  ModelAndView modelAndView = new ModelAndView("login");
  return modelAndView;
 }

 @GetMapping("/logoutSuccess")
 public ModelAndView logoutPage(Model model) {
  ModelAndView modelAndView = new ModelAndView("logoutSuccess");
  return modelAndView;
 }

 @GetMapping("/")
 public ModelAndView homePage(Model model) {
  ModelAndView modelAndView = new ModelAndView("homePage");
  return modelAndView;
 }

}


Define login.jsp, secure.jsp, logoutSuccess.jsp files under src/main/webapp/WEB-INF/views folder.

Total project structure looks like below.

Find the below working application.

User.java
package com.sample.app.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "user")
public class User {

 @Id
 @Column(name = "user_id")
 @GeneratedValue
 private int id;

 @Column(name = "user_name", nullable = false, unique = true)
 private String userName;

 @Column(name = "user_password", nullable = false)
 private String password;

 public int getId() {
  return id;
 }

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

 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;
 }

}


UserAuthorizations.java
package com.sample.app.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "user_authorizations")
public class UserAuthorizations {

 @Id
 @Column(name = "user_auth_group_id")
 @GeneratedValue
 private int id;

 @Column(name = "user_name")
 private String userName;

 @Column(name = "auth_group")
 private String authGroup;

 public int getId() {
  return id;
 }

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

 public String getUserName() {
  return userName;
 }

 public void setUserName(String userName) {
  this.userName = userName;
 }

 public String getAuthGroup() {
  return authGroup;
 }

 public void setAuthGroup(String authGroup) {
  this.authGroup = authGroup;
 }

}


UserPrincipal.java
package com.sample.app.model;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class UserPrincipal implements UserDetails {

 private static final long serialVersionUID = 1L;
 private User user;
 private List<UserAuthorizations> authGroups;

 public UserPrincipal(final User user, final List<UserAuthorizations> authGroups) {
  super();
  this.user = user;
  this.authGroups = authGroups;
 }

 @Override
 public Collection<? extends GrantedAuthority> getAuthorities() {
  if(authGroups == null) return Collections.EMPTY_SET;
  
  Set<GrantedAuthority> grantedAuthorities = new HashSet<> ();
  
  for(UserAuthorizations authGroup: authGroups) {
   grantedAuthorities.add(new SimpleGrantedAuthority(authGroup.getAuthGroup()));
  }
  
  return grantedAuthorities;
  
 }

 @Override
 public String getPassword() {
  return user.getPassword();
 }

 @Override
 public String getUsername() {
  return user.getUserName();
 }

 @Override
 public boolean isAccountNonExpired() {
  return true;
 }

 @Override
 public boolean isAccountNonLocked() {
  return true;
 }

 @Override
 public boolean isCredentialsNonExpired() {
  return true;
 }

 @Override
 public boolean isEnabled() {
  return true;
 }


}


UserAuthorizationRepository.java
package com.sample.app.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import com.sample.app.model.UserAuthorizations;

public interface UserAuthorizationRepository extends JpaRepository<UserAuthorizations, Integer>{

 List<UserAuthorizations> findByUserName(String userName);
}


UserRepository.java
package com.sample.app.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.sample.app.model.User;

public interface UserRepository extends JpaRepository<User, Integer> {

 User findByUserName(String userName);
}


UserDetailsServiceImpl.java
package com.sample.app.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
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 com.sample.app.model.UserAuthorizations;
import com.sample.app.model.User;
import com.sample.app.model.UserPrincipal;
import com.sample.app.repository.UserAuthorizationRepository;
import com.sample.app.repository.UserRepository;

@Service
public class UserDetailsServiceImpl implements UserDetailsService{
 
 @Autowired
 private UserRepository userRepository;
 
 @Autowired
 private UserAuthorizationRepository authGroupRepository;

 @Override
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  User user = userRepository.findByUserName(username);
  
  if(user == null) {
   throw new UsernameNotFoundException("User not exist");
  }
  
  List<UserAuthorizations> authGroups = authGroupRepository.findByUserName(user.getUserName());
  
  
  return new UserPrincipal(user, authGroups);
 }

}


EmployeeController.java
package com.sample.app.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("employees/")
public class EmployeeController {

 
 @RequestMapping(value = "registered/count", method = RequestMethod.GET)
 @PreAuthorize("hasRole('ADMIN')")
 public String countEmps() {
  return "Total Registered Employees : "+  1024;
 }
 
 @RequestMapping(value = "greetMe", method = RequestMethod.GET)
 @PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
 public String greetMe() {
  return "Very Good Day to you";
 }
}


HomeController.java

package com.sample.app.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HomeController {
 @GetMapping("/hello")
 public ModelAndView sayHello(Model model) {
  ModelAndView modelAndView = new ModelAndView("hello");
  return modelAndView;
 }

 @GetMapping("/secure")
 public ModelAndView securePage(Model model) {
  ModelAndView modelAndView = new ModelAndView("secure");
  return modelAndView;
 }

 @GetMapping("/login")
 public ModelAndView loginPage(Model model) {
  ModelAndView modelAndView = new ModelAndView("login");
  return modelAndView;
 }

 @GetMapping("/logoutSuccess")
 public ModelAndView logoutPage(Model model) {
  ModelAndView modelAndView = new ModelAndView("logoutSuccess");
  return modelAndView;
 }

 @GetMapping("/")
 public ModelAndView homePage(Model model) {
  ModelAndView modelAndView = new ModelAndView("homePage");
  return modelAndView;
 }

}


ApplicationSecurityConfiguration.java
package com.sample.app.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.sample.app.service.UserDetailsServiceImpl;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfiguration extends WebSecurityConfigurerAdapter {

 @Autowired
 private UserDetailsServiceImpl userDetailsService;

 @Autowired
 private PasswordEncoder passwordEncoder;

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

 @Override
 protected void configure(HttpSecurity httpSecurity) throws Exception {
  httpSecurity.csrf().disable().authorizeRequests().antMatchers("/", "/public/*", "/css/*", "/js/*", "/hello").permitAll()
    .anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().logout()
    .invalidateHttpSession(true).clearAuthentication(true)
    .logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/logoutSuccess")
    .permitAll();

 }

 @Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.authenticationProvider(authenticationProvider());
 }

 @Bean
 public GrantedAuthoritiesMapper authoritiesMapper() {
  SimpleAuthorityMapper authorityMapper = new SimpleAuthorityMapper();
  authorityMapper.setConvertToUpperCase(true);
  authorityMapper.setDefaultAuthority("ROLE_USER");
  return authorityMapper;
 }

}


App.java
package com.sample.app;

import com.sample.app.model.User;
import com.sample.app.model.UserAuthorizations;

import java.util.Arrays;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.sample.app.repository.UserAuthorizationRepository;
import com.sample.app.repository.UserRepository;

@SpringBootApplication
public class App {
 public static void main(String[] args) {

  SpringApplication.run(App.class, args);
 }

 @Bean
 public CommandLineRunner demo(UserRepository userRepository,
   UserAuthorizationRepository userAuthorizationRepository) {
  return (args) -> {

   User user1 = new User();
   user1.setUserName("krishna");
   user1.setPassword(passwordEncoder().encode("password123"));

   User user2 = new User();
   user2.setUserName("ram");
   user2.setPassword(passwordEncoder().encode("ram123"));

   UserAuthorizations userAuthorization1 = new UserAuthorizations();
   userAuthorization1.setUserName("krishna");
   userAuthorization1.setAuthGroup("ROLE_ADMIN");

   UserAuthorizations userAuthorization2 = new UserAuthorizations();
   userAuthorization2.setUserName("ram");
   userAuthorization2.setAuthGroup("ROLE_USER");

   userRepository.saveAll(Arrays.asList(user1, user2));
   userAuthorizationRepository.saveAll(Arrays.asList(userAuthorization1, userAuthorization2));
  };
 }

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


Create application.properties under src/main/resources folder.

application.properties
logging.level.root=WARN
logging.level.org.hibernate=ERROR

## H2 specific properties
spring.h2.console.enabled=true
spring.h2.console.path=/h2

spring.datasource.url=jdbc:h2:file:~/db/myOrg.db;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1;

spring.datasource.username=krishna
spring.datasource.password=password123

spring.datasource.driverClassName=org.h2.Driver

## JPA specific properties
# Creates the schema, destroying previous data.
spring.jpa.hibernate.ddl-auto=create

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

#spring.jpa.show-sql=true
#spring.jpa.properties.hibernate.format_sql=true

## Database connection pooling properties
# Number of ms to wait before throwing an exception if no connection is available.
spring.datasource.max-wait=10000

# Maximum number of active connections that can be allocated from this pool at the same time.
spring.datasource.tomcat.max-active=10
spring.datasource.tomcat.max-idle=5
spring.datasource.tomcat.min-idle=3

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

spring.mvc.view.prefix = /WEB-INF/views/
spring.mvc.view.suffix = .jsp


Create jsp files under src/main/webapp/WEB-INF/views folder.

hello.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
 <h1>
  Hello World<br />
 </h1>
</body>
</html>

homepage.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
 <h1>Home Page</h1>

 
</body>
</html>


login.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
 <h1>Login Page</h1>
 <center>
  <h2>Signup Details</h2>
  <form action="/login" method="post">
   <br />Username:<input type="text" name="username"> 
   <br />Password:<input type="password" name="password"> 
   <br /> <input type="submit" value="Submit">
  </form>
 </center>
</body>
</html>

logoutSuccess.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
 <h1>Logout successful</h1>
</body>
</html>

secure.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>This is protected file</h1>
</body>
</html>

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>springSecurity</groupId>
 <artifactId>springSecurity</artifactId>
 <version>1</version>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.1.6.RELEASE</version>
 </parent>

 <dependencies>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>


  <dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
  </dependency>

  <dependency>
   <groupId>org.apache.tomcat.embed</groupId>
   <artifactId>tomcat-embed-jasper</artifactId>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
  </dependency>


 </dependencies>
</project>

Run App.java.

Open the url ‘http://localhost:8080/’ in browser, you can see home page.


Open the url ‘http://localhost:8080/secure’, you will be redirected to login page.



Login with user name ‘krishna’ and password ‘password123’. Click on Submit button after entering credentials, you will be redirected to secure.jsp page.    
   
You can download complete working application from this link.
    

Previous                                                    Next                                                    Home

No comments:

Post a Comment