[JAVA] A new employee tried to create an authentication / authorization function from scratch with Spring Security

If you want to write code quickly without a long story, skip [1. Introduction](# 1-Introduction). For Spring beginners, go to [2. Create Project](# 2-Create Project) For those who want to implement a simple authentication / authorization function, go to [3. Implementation of a simple authentication / authorization function](# 3-Implementation of a simple authentication / authorization function). Those who want to implement the authentication / authorization function for actual business should start reading from [4. Implementation of authentication / authorization function for actual business](# 4-Implementation of authentication / authorization function for actual business).

1.First of all

1-1. Background

First of all, I will explain about authentication / authorization. The simple explanation is as follows. (Reference page for those who want to know more: Familiar authentication and authorization)

--Authentication Check who you are communicating with </ font>.

--Authorization Grant resource access permission for specific conditions such as screen access permission and processing permission </ font>.

When implementing this authentication / authorization function in a Web application created with Spring Framework, it can be easily implemented by using Spring Security. There are many articles that implement authentication / authorization functions using Spring Security, but I feel that there are many articles such as the following.

--The database is not used. --The user name and password are written directly on the code. --Password is not hashed. --Only the source code of the important part related to authentication / authorization is listed (that is, it is difficult to read).

However, database usage and password hashing are commonplace </ font>. When I created a Web system with authentication / authorization functions, I had a lot of trouble reading many articles and deciphering the source code on GitHub. Based on that experience, even beginners about Spring Security and authentication / authorization can use database and hashing & articles that can implement the function by reading this </ font> I thought, and wrote this article. Also, in the system used in the field, you are wondering how the source code and database tables related to authentication / authorization are implemented. Therefore, I wrote this article with the intention of investigating what kind of authentication / authorization function is implemented by referring to our system construction example </ font>.

1-2. Purpose

--Even beginners can implement authentication / authorization functions using Spring Security by reading this article without having to read the source code of GitHub. ――The contents of the investigation and hearing about authentication / authorization are summarized as the implementation method of the authentication / authorization function for actual business by referring to the system construction example at our company.

  • Please forgive the fact that the actual case is not the same as the actual case due to the selection of information and the abstraction of the case in the process of creating the article.

1-3. Target

--People who want to implement authentication / authorization function using Spring Security --People who develop authentication / authorization functions in actual work --People who don't want to read the code on GitHub and want to implement it by reading the article

1-4. Prerequisites

--Understand the MVC model. (Reference page for strangers: I will explain the MVC model in an easy-to-understand manner![For beginners]) --I have developed an MVC model web application using Spring. --Understand the basic usage of Gradle.

1-5. Goals of this article

--Implement two types, simple implementation and implementation for actual business.

** Simple implementation ** </ font> is an implementation of authentication / authorization processing for beginners. The author has compiled only the implementation part of the Spring Security reference material. It's perfect for spring security beginners to get a better understanding by running it on their device. ** Implementation for actual business ** </ font> is an in-house hearing on how authentication / authorization processing is realized in an actual system, and based on the content. We will implement authentication / authorization processing that is close to the actual system. It is an implementation for those who develop authentication / authorization processing in business.

--The settings / functions introduced in this article should be understood as one of the settings / functions to be considered when applying an actual project.

The following three goals apply to both simple implementation and real-world implementation </ font>.

--You can implement it by copying the source code of this article. --Hash passwords using H2 database. -Realize the authentication / authorization function that makes page transitions as shown in the image below. (The page transition is the same for both simple implementation and implementation for actual business.)

image

1-6. Development environment / execution environment

  • Eclipse Version: 2020-06 (4.16.0)
  • Spring Tools 4 (aka Spring Tool Suite 4) 4.7.1.RELEASE
  • Spring boot 2.3.3
  • Spring Security 5.3.4
  • Java11
  • Gradle 6.3

1-7. GitHub Even if you do not read the source code on GitHub, you can implement the function by reading this article, but for those who say "It is faster to read GitHub", I will post the source code on GitHub.

https://github.com/YukiYamagata/SpringSecurity-Qiita

--BasicAuth is the source code of the simple implementation authentication / authorization function. --Advanced Authorization is the source code of the authorization function for business use

It has become.

2. Create a project

2-1. Change perspective to Java EE

"Window"-> "Perspective"-> "Open Perspective"-> "Other"

image

Select "Java EE" to open

image

2-2. Creating a new project

"File"-> "New"-> "Spring Starter Project"

image

Change the value of each item as shown below and click "Next".

item value
name Arbitrary value
Type Gradle (Buildship 3.x)
Java version 11
Deliverables Arbitrary value
image

Press "Finish"

image

2-3. Change application.properties to application.yml (optional)

Since properties and yml are described only slightly differently, you can leave "application.properties" as it is. I like whether to change it. However, in this article, the source code is based on application.yml.

image

3. Implementation of simple authentication / authorization function

3-1. System image to be created & project configuration

The figure below is an image of the Web system created. The detailed processing flow of authentication processing and authorization processing is [3-5-1. Outline of Spring Security authentication function](# 3-5-1-spring-security-Outline of authentication function) and [3-6-1. Spring Overview of Security Authorization Function](# 3-6-1-spring-security-Overview of Authorization Function).

image

The figure below shows the project structure of the system.

image

3-2. Dependency settings

Write build.gradle to set the dependency.

** Show code for "build.gradle" **

build.gradle


plugins {
	id 'org.springframework.boot' version '2.3.3.RELEASE'
	id 'io.spring.dependency-management' version '1.0.10.RELEASE'
	id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-jdbc'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
	runtimeOnly 'com.h2database:h2'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
	testImplementation 'org.springframework.security:spring-security-test'
}

test {
	useJUnitPlatform()
}

3-3. H2 database settings

3-3-1. Overview of H2 database (H2DB)

H2DB is an in-memory database written in Java. The features of H2DB are the following three.

  1. Lightweight and easy to install
  2. Fast operation
  3. You can switch to other database behavior by setting a mode such as Oracle mode or PostgreSQL mode.

Since it is an in-memory database, the data will be lost when the process is completed. It can also be an embedded database, but in order to improve the contents of the database for actual business in [4. Implementation of authentication / authorization function for actual business](# 4-Implementation of authentication / authorization function for actual business), this time Use as a memory database. Since it is in-memory, the initial value of H2DB must be set by executing the SQL statement each time processing is started. In the case of Spring Boot, by describing the SQL statement in "schema.sql" and "data.sql", the SQL statement can be executed automatically every time the process starts. </ font>

3-3-2. H2 database related project structure

image

3-3-2. application.yml (additional code)

Describe the H2DB settings in application.yml.

** Show code for "application.yml" **

application.yml


##datasource
spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:testdb
    username: sa
    password: dm

3-3-3. schema.sql (add file)

Describe the table structure of the database in SQL.

** Show code for "schema.sql" **

schema.sql


CREATE TABLE IF NOT EXISTS users (
  username VARCHAR(18) NOT NULL PRIMARY KEY,
  password VARCHAR(255) NOT NULL,
  name VARCHAR(255) NOT NULL,
  rolename VARCHAR(10) NOT NULL
);

3-3-4. data.sql (add file)

Insert data into H2DB. The 60-character string starting with "\ $ 2a \ $ 10 \ $" is the hashed password. Details will be explained in [3-7. Hashing Password](# 3-7-Hashing Password), so please copy and paste here.

Plaintext password Hashed password
admin $2a$10$bCR1jXhdqbh1oC8ckXplxePYW5Kyb/VjN28MZx2PwXf1ybzLIFUQG
user $2a$10$yyT1siJCep647RT/I7KjcuUB5noFVU6RBo0FUXUJX.hb2MIlWTbDe
** Show code for "data.sql" **

data.sql


INSERT INTO users(username, password, name, rolename) VALUES ('admin', '$2a$10$bCR1jXhdqbh1oC8ckXplxePYW5Kyb/VjN28MZx2PwXf1ybzLIFUQG', 'admin-name', 'ADMIN');
INSERT INTO users(username, password, name, rolename) VALUES ('user' , '$2a$10$yyT1siJCep647RT/I7KjcuUB5noFVU6RBo0FUXUJX.hb2MIlWTbDe', 'user-name' , 'USER' );

3-4. Implementation of application functions

3-4-1. Overview of application functions

Create an application that can display and transition to the five pages shown below.

image

The application processing to realize this page transition is the part surrounded by light green in the figure below. To focus on authentication / authorization processing, create only Controller and View for application processing. (Service, Dao, etc. are not created.)

image

3-4-2. Project structure related to application functions

image

3-4-3. Controller (add file)

image
** Show code for "LoginController.java" **

LoginController.java


package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LoginController {
	@GetMapping("/loginForm")
	String loginForm() {
		return "login";
	}
}
** Show code for "HomeController.java" **

HomeController.java


package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
	@GetMapping("/home")
	public String home() {
		return "home";
	}
}
** Show code for "AdminPageController.java" **

AdminPageController.java


package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class AdminPageController {
	@GetMapping("/adminPage")
	public String adminPage() {
		return "adminPage";
	}
}
** Show code for "UserPageController.java" **

UserPageController.java


package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class UserPageController {
	@GetMapping("/userPage")
	public String userPage() {
		return "userPage";
	}
}
** Show code for "AccessDeniedPageController.java" **

AccessDeniedPageController.java


package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class AccessDeniedPageController {
	@GetMapping("/accessDeniedPage")
	public String accessDeniedPage() {
		return "accessDeniedPage";
	}
}

3-4-4. View (add file)

Create an HTML file. The source code uses Thymeleaf as a template engine. If you don't have a "templates" folder, add one under src / main / resources.

image
image
** Show code for "login.html" **

login.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
	<meta charset="UTF-8">
	<title>Login</title>
</head>
<body>
	<h1>Login</h1>
	<div th:if="${param.error}" > <!-- (1)Judgment of request parameters-->
		<div>
UserName or Password is different.<!-- (2)Authentication error message-->
		</div>
	</div>

	<form method="post" th:action="@{/authenticate}"> <!-- (3)Specify the login process path-->
		<label>UserName:</label>
		<input type="text" id="userName" name="userName"> <!-- (4)Username input-->
		<br>
		<label>Password:</label>
		<input type="password" id="password" name="password"> <!-- (5)Password input section-->
		<br>
		<input type="submit" id="submit" value="Login">
	</form>
</body>
</html>
Item number Description
(1) Judge the error message set in the request parameter.
3-5-6. JavaConfig(Add file) "WebSecurityConfig.java"InChange the judgment process according to the value set in "failureUrl"Please note that it is necessary.
(2) Exception message to be output when an authentication error occurs
(3) Specify the transition destination for performing authentication processing in the action attribute of form.
The transition destination path is3-5-6. JavaConfig(Add file) "WebSecurityConfig.java"In"Match the value specified in loginProcessingUrl ”There is a need.
Specify "POST" for the HTTP method.
In this case${pageContext.request.contextPath}/Authentication processing is executed by accessing authenticate.
(4) An element that is treated as a "user name" in the authentication process.
The name attribute is3-5-6. JavaConfig(Add file) "WebSecurityConfig.java"In"Match the value specified by usernameParameter ”There is a need.
(5) An element that is treated as a "password" in the authentication process.
The name attribute is3-5-6. JavaConfig(Add file) "WebSecurityConfig.java"In"Match the value specified by passwordParameter ”There is a need.
image
** Show code for "home.html" **

home.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
	<meta charset="UTF-8">
	<title>Home</title>
</head>
<body>
	<h1>Home</h1>
	
	<form method="get" th:action="@{/adminPage}">
		<input type="submit" value="To AdminPage">
	</form>

	<form method="get" th:action="@{/userPage}">
		<input type="submit" value="To UserPage">
	</form>

	<form method="post" th:action="@{/logout}"> <!-- (1)Specify the logout processing path-->
		<input type="submit" value="Logout">
	</form>
</body>
</html>
Item number Description
(1) Specify the path to execute logout process in action attribute of form.
The logout process path is the default value of Spring Security,/Specify logout.
Specify "POST" for the HTTP method.
image
** Show code for "adminPage.html" **

adminPage.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
	<meta charset="UTF-8">
	<title>AdminPage</title>
</head>
<body>
	<h1>Admin Page</h1>
	<table border="1">
		<tr>
			<th>item</th>
			<th>Contents</th>
		</tr>
		<tr>
			<td>UserName</td>
			<td><span sec:authentication="principal.username"></span></td> <!-- (1)Show username-->
		</tr>
		<tr>
			<td>Name</td>
			<td><span sec:authentication="principal.name"></span></td> <!-- (2)Show name-->
		</tr>
		<tr>
			<td>Role</td>
			<td><span sec:authentication="principal.authorities"></span></td> <!-- (3)Show Role-->
		</tr>
	</table>

	<form method="get" th:action="@{/home}">
		<input type="submit" value="To Home">
	</form>
	<form method="post" th:action="@{/logout}">
		<input type="submit" value="Logout">
	</form>
</body>
</html>
Item number Description
(1) Use Thymeleaf to access the authentication object and display the UserName.
(2) Use Thymeleaf to access the authentication object and display the Name.
(3) Use Thymeleaf to access the authentication object and view the Role.
image
** Show code for "userPage.html" **

userPage.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
	<meta charset="UTF-8">
	<title>UserPage</title>
</head>
<body>
	<h1>User Page</h1>
	<table border="1">
		<tr>
			<th>item</th>
			<th>Contents</th>
		</tr>
		<tr>
			<td>UserName</td>
			<td><span sec:authentication="principal.username"></span></td>
		</tr>
		<tr>
			<td>Name</td>
			<td><span sec:authentication="principal.name"></span></td>
		</tr>
		<tr>
			<td>Role</td>
			<td><span sec:authentication="principal.authorities"></span></td>
		</tr>
	</table>

	<form method="get" th:action="@{/home}">
		<input type="submit" value="To Home">
	</form>
	<form method="post" th:action="@{/logout}">
		<input type="submit" value="Logout">
	</form>
</body>
</html>
image
** Show code for "accessDeniedPage.html" **

accessDeniedPage.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
	<meta charset="UTF-8">
	<title>Access Denied</title>
</head>
<body>
	<h1>Access denied</h1>

	<form method="get" th:action="@{/home}">
		<input type="submit" value="To Home">
	</form>
	<form method="post" th:action="@{/logout}">
		<input type="submit" value="Logout">
	</form>
</body>
</html>

3-5. Implementation of authentication function

3-5-1. Outline of Spring Security Authentication Function

The part surrounded by pink in the figure below shows the authentication function.

image

The flow of the authentication process is as follows. (The processing number corresponds to the number in the above figure)

  1. Call the authentication process of "Authentication Manager" from "Username Password Authentication Filter".
  2. From "AuthenticationManager", call the user acquisition process of "AccountUserDetailsService" that inherits ʻUserDetailsService` ** (* 1) **.
  3. Call "Dao" from "AccountUserDetailsService".
  4. "Dao" converts the user information obtained from DB such as Username and Password into Entity instance called "MyUser" and returns it to "AccountUserDetailsService".
  5. "AccountUserDetailsService" converts user information (MyUser) into an instance of "AccountUserDetails" that inherits ʻUserDetails` ** (* 2) ** and returns it to" AuthenticationManager ".
  6. "AuthenticationManager" collates "AccountUserDetails" with the authentication information specified by the client and returns the result to "UsernamePasswordAuthenticationFilter".
  7. "UsernamePasswordAuthenticationFilter" receives the authentication result returned from "AuthenticationManager" and controls the response of authentication success or authentication failure.

(※1) UserDetailsService This interface is responsible for acquiring the credentials (user name and password) and user status required for the authentication process. (※2) UserDetails An interface that provides credentials and user status, created from ʻUserDetailsService`.

When performing authentication processing using DB, create UserDetailsService implementation class (AccountUserDetailsService in this article) and UserDetails implementation class (AccountUserDetails in this article) according to the requirements of the application. </ font> Must be.

3-5-2. Authentication function related project structure

image

3-5-3. Entity (add file)

image
** Show code for "MyUser.java" **

MyUser.java


package com.example.demo.entity;

import java.io.Serializable;

public class MyUser implements Serializable{
	private String userName;	//In H2DB, the users table"username"Field to store

	private String password;	//In H2DB, the users table"password"Field to store

	private String name;		//In H2DB, the users table"name"Field to store

	private String roleName;	//In H2DB, the users table"roleName"Field to store

	/**
	 * getter, setter
	 */
	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;
	}

	public String getName() {
		return name;
	}

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

	public String getRoleName() {
		return roleName;
	}

	public void setRoleName(String roleName) {
		this.roleName = roleName;
	}
}

3-5-4. DAO (add file)

Access the DB using Spring JDBC.

image
** Show code for "UserDao.java" **

UserDao.java


package com.example.demo.repository;

import com.example.demo.entity.MyUser;

public interface UserDao {
	MyUser findUserByUserName(String userName);
}
** Show code for "UserDaoImpl.java" **

UserDaoImpl.java


package com.example.demo.repository;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.example.demo.entity.MyUser;

@Repository
public class UserDaoImpl implements UserDao {
	private final JdbcTemplate jdbcTemplate;

	@Autowired
	public UserDaoImpl(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	/**
	 *Execute the SELECT statement with userName as the search condition to search for users registered in the DB.
	 * @param userName
	 * @return User
	 */
	@Override
	public MyUser findUserByUserName(String userName) {
		String sql = "SELECT username, password, name, rolename FROM users WHERE username = ?";

		//Get one user
		Map<String, Object> result = jdbcTemplate.queryForMap(sql, userName);

		//Entity class(User type)Conversion to
		MyUser user = convMapToUser(result);

		return user;
	}

	/**
	 *Result of executing SQL SELECT statement(Map<String, Object>)To User type
	 * @param Map<String, Object>
	 * @return User
	 */
	private MyUser convMapToUser(Map<String, Object> map) {
		MyUser user = new MyUser();

		user.setUserName((String) map.get("username"));
		user.setPassword((String) map.get("password"));
		user.setName((String) map.get("name"));
		user.setRoleName((String) map.get("rolename"));

		return user;
	}
}

3-5-5. Service (add file)

image
** Show code for "AccountUserDetailsService.java" **

AccountUserDetailsService.java


package com.example.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
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.example.demo.entity.MyUser;
import com.example.demo.repository.UserDao;

@Service
public class AccountUserDetailsService implements UserDetailsService {
	private final UserDao userDao;

	@Autowired
	public AccountUserDetailsService(UserDao userDao) {
		this.userDao = userDao;
	}

	@Override
	public UserDetails loadUserByUsername(String userName)
			throws UsernameNotFoundException {			// --- (1)Method to retrieve account information from database

		if (userName == null || "".equals(userName)) {
			throw new UsernameNotFoundException(userName + "is not found");
		}

		//Get one User Exception occurs if there is no userName
		try {
			//Get User
	    	MyUser myUser = userDao.findUserByUserName(userName);

	    	if (myUser != null) {
	    		return new AccountUserDetails(myUser); // --- (2)Generate implementation class of UserDetails

	    	} else {
	    		throw new UsernameNotFoundException(userName + "is not found");
	    	}

		} catch (EmptyResultDataAccessException e) {
			throw new UsernameNotFoundException(userName + "is not found");
		}
	}
}
Item number Description
(1) Search for account information from the database. Throw UsernameNotFoundException if account information not found
(2) If the account information is found, the UserDetails implementation class(AccountUserDetails)To generate.
image
** Show code for "AccountUserDetails.java" **

AccountUserDetails.java


package com.example.demo.service;

import java.util.Collection;

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

import com.example.demo.entity.MyUser;

public class AccountUserDetails implements UserDetails {
	private final MyUser myUser;

	public AccountUserDetails(MyUser myUser) {
		this.myUser = myUser;
	}

	public MyUser getUser() { // --- (1)Method to return MyUser which is Entity
		return myUser;
	}

	public String getName() { // --- (2)Method to return name
		return this.myUser.getName();
	}


	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() { // --- (3)Method to return the permission list given to the user
		return AuthorityUtils.createAuthorityList("ROLE_" + this.myUser.getRoleName());
	}

	@Override
	public String getPassword() { // --- (4)Method to return the registered password
		return this.myUser.getPassword();
	}

	@Override
	public String getUsername() { // --- (5)Method to return user name
		return this.myUser.getUserName();
	}

	@Override
	public boolean isAccountNonExpired() { // --- (6)Method to determine the expiration status of an account
		return true;
	}

	@Override
	public boolean isAccountNonLocked() { // --- (7)Method to determine account lock status
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() { // --- (8)Method to determine the expiration status of credentials
		return true;
	}

	@Override
	public boolean isEnabled() { // --- (9)Method to determine if it is a valid user
		return true;
	}
}
Item number Description
(1) A method that returns MyUser, which is an Entity.
Prepare a getter method so that the account information can be accessed in the process after successful authentication process.
(2) A method that returns a name.
(3) A method that returns the permission list given to the user.
This method is used in authorization process. In the authorization process of Spring Security, "ROLE_The authority information starting with "is treated as a role. Therefore, "ROLE_"Is added.
(4) A method to return the registered password.
The password returned by this method is used for comparison with the password specified by the client.
(5) A method that returns a username.
(6) A method that determines the expiration status of an account.
If it is within the expiration date, true is returned, and if it has expired, false is returned.
In this program, only true is returned for the sake of simplicity.
(7) A method that determines the locked state of an account.
If it is not locked, ture is returned, and if the account is locked, false is returned.
In this program, only true is returned for the sake of simplicity.
(8) A method that determines the expiration status of credentials.
If it is within the expiration date, true is returned, and if it has expired, false is returned.
In this program, only true is returned for the sake of simplicity.
(9) A method that determines if the user is valid.
If it is valid, it returns true, and if it is an invalid user, it returns false.
In this program, only true is returned for the sake of simplicity.

3-5-6. JavaConfig (add file)

image
** Show code for "WebSecurityConfig.java" **

WebSecurityConfig.java


package com.example.demo.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.example.demo.service.AccountUserDetailsService;

@Configuration
@EnableWebSecurity // --- (1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	AccountUserDetailsService userDetailsService;

	PasswordEncoder passwordEncoder() {
		//Hash passwords using BCrypt algorithm
		return new BCryptPasswordEncoder(); // --- (2)Uses BCrypt algorithm
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		//Set the implemented UserDetailsService in AuthenticationManagerBuilder
		auth.userDetailsService(userDetailsService)		// --- (3)Set the created UserDetailsService
				.passwordEncoder(passwordEncoder());	// --- (2)Specify password hashing method(BCrypt algorithm)
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		//Authorization settings
		http.authorizeRequests()
				.antMatchers("/loginForm").permitAll()	// --- (4) /loginForm allows access from all users
				.anyRequest().authenticated();			// --- (5) /Requires authentication except for loginForm

		//Login settings
		http.formLogin()								// --- (6)Enable forms authentication
				.loginPage("/loginForm")				// --- (7)Path to display login form
				.loginProcessingUrl("/authenticate")	// --- (8)Path of forms authentication process
				.usernameParameter("userName")			// --- (9)Username request parameter name
				.passwordParameter("password")			// --- (10)Password request parameter name
				.defaultSuccessUrl("/home")				// --- (11)Default path to transition when authentication is successful
				.failureUrl("/loginForm?error=true");	// --- (12)Path to transition when authentication fails

		//Logout settings
		http.logout()
				.logoutSuccessUrl("/loginForm")			// --- (13)Path to transition when logout is successful
				.permitAll();							// --- (14)Allow access to all users
	}
}
Item number Description
(1) @EnableWebSecurityIf is specified, the bean definition of the component required to use Spring Security is automatically performed.
(2) Set to hash passwords using the BCrypt algorithm.
(3) AuthenticationManagerBuilderCreated inUserDetailsServiceTo set.
(4) /loginForm allows access from all users.
(5) /Other than loginForm, it requires authentication.
Unauthenticated users are redirected to the login screen.
Users who are authenticated but not authorized are denied access.
(6) formLoginCalling the method enables forms authentication.
(7) Specify the path to display the login form. If an anonymous user attempts to access a page that requires authentication, they will be redirected to the path specified here.
(8) Specify the path of forms authentication processing.3-4-4. View(Add file)Login.In htmlMatch with action attribute of form tagThere is a need.
(9) Specify the request parameter name of the user name that is the credential.3-4-4. View(Add file)Login.In htmlMatch the name attribute of the input tag that inputs the user nameThere is a need.
(10) Specify the request parameter name of the password that is the credential information.3-4-4. View(Add file)Login.In htmlMatch the name attribute of the input tag to enter the passwordThere is a need.
(11) Specify the default path to transition when authentication is successful.
(12) Specify the path to transition when authentication fails.
(13) Specify the path to transition to when logout is successful.
(14) Allow all users access to the logout and the path that transitions when the logout is successful.

3-6. Implementation of authorization function

3-6-1. Outline of Spring Security Authorization Function

The part surrounded by light blue in the figure below shows the authorization function.

image
In the authorization process, it is determined whether the URL requested by "Filter Security Interceptor" is permitted. Use `@PreAuthorize` to set the permissions for each user. `@PreAuthorize` can be described for each Controller or Method.

--If access is granted Processes the next filter.

--If access is denied The "Filter Security Interceptor" throws the exception ʻAccessDeniedException`. Exception is caught by "Exception Translation Filter", and if the access is from an unauthenticated user, a response prompting authentication (transition to the login screen), and if the access is from an authenticated user, a response that notifies an authorization error (access denied) (Transition to screen) will be returned.

3-6-2. Project structure related to authorization function

The file to which the code is added is highlighted in blue.

image

3-6-3. JavaConfig (additional code)

image

If you try to access a page that is not allowed, you will be taken to a page that indicates that access has been denied.

** Show code for "WebSecurityConfig.java" **

WebSecurityConfig.java


/*abridgement*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //Postscript--- (1)Enable method authorization processing
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

/*abridgement*/

	protected void configure(HttpSecurity http) throws Exception {
		//Authorization settings
		http.exceptionHandling()	//Postscript
				.accessDeniedPage("/accessDeniedPage")	//Postscript--- (2)Path to transition when access is denied
			.and()					//Postscript
			.authorizeRequests()
				.antMatchers("/loginForm").permitAll()
				.anyRequest().authenticated();
/*abridgement*/
Item number Description
(1) @PreAuthorizeOr@PostAuthorizeEnable the method authorization process by.
(2) Access denied(Authorization error)When it is done, specify the transition path.

3-6-4. Controller (additional code)

image

Describe @PreAuthorize in each Method to set the access authority.

** Show code for "AdminPageController.java" **

AdminPageController.java


/*abridgement*/
	@GetMapping("/adminPage")
	@PreAuthorize("hasRole('ROLE_ADMIN')") //Postscript--- (1) ROLE_Allow access only to ADMIN users
	public String adminPage() {
/*abridgement*/
item Description
(1) 「ROLE_Allow access only to users who have "ADMIN".
** Show code for "UserPageController.java" **

UserPageController.java


/*abridgement*/
	@GetMapping("/userPage")
	@PreAuthorize("hasRole('ROLE_USER')") //Postscript--- (1) ROLE_Allow access only to USER users
	public String userPage() {
/*abridgement*/
item Description
(1) 「ROLE_Only users who have "USER" are allowed access.

3-6-5. View (additional code)

image

On the "home" page, the "to adminPage" and "to userPage" buttons will be displayed according to the login information.

** Show code for "home.html" **

home.html


<!--abridgement-->
<body>
	<h1>Home</h1>

	<div sec:authorize="hasRole('ADMIN')">				<!--Postscript(1) ROLE_Show only ADMIN users-->
		<form method="get" th:action="@{/adminPage}">
			<input type="submit" value="To AdminPage">
		</form>
	</div>

	<div sec:authorize="hasRole('USER')">				<!--Postscript(2) ROLE_Show only USER users-->
		<form method="get" th:action="@{/userPage}">
			<input type="submit" value="To UserPage">
		</form>
	</div>

	<div sec:authorize="isAuthenticated()">				<!--Postscript(3)Show only logged-in users-->
		<form method="post" th:action="@{/logout}">
			<input type="submit" value="Logout">
		</form>
	</div>
</body>
</html>
item Description
(1) 「ROLE_Only users who have "ADMIN" are displayed.
(2) 「ROLE_Only users who have "USER" are displayed.
(3) You are logged in(Certified)Only the user is displayed.

3-7. Password hashing

3-7-1. Outline of hashing

Hashing is the conversion of a string into another value (hash value) using a specific algorithm (hash function). It is often used for storing passwords. The difference between encryption and hashing is whether it can be restored (decrypted) to its original value. Encrypted values can be decrypted, but hashed values cannot be decrypted </ font>. Therefore, it is extremely difficult for a third party to determine the original data from the hash value </ font>. Spring Security recommends using BCryptPasswordEncoder unless you have specific requirements for hashing.

If you want to know more about hashing, please refer to the following page. [Basic knowledge] What is encryption? What is hashing? How to prevent password leakage?

3-7-2. Creating a password hashing program

I think it is better to create the following program with a newly created project. (Spring Security still needs to be set as a dependency in the new project.) You can create a program in an existing project, but the author thinks that it is better to create a new project because multiple main functions can be created in the same system. Change the rawPassword value (admin in your code) to the value you want to set as the password.

** Show code for "GeneratePassword.java" **

GeneratePassword.java


package com.example.demo.password;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class GeneratePassword {

	public static void main(String[] args) {
		//Enter the password you want to hash
		String rawPassword = "admin";
		
		//Hash password
		String password = getEncodePassword(rawPassword);
		
		//Show hashed value
		System.out.println(password);
	}

	private static String getEncodePassword(String rawPassword) {
		BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder ();
		return passwordEncoder.encode(rawPassword);
	}
}

3-7-3. Try password hashing

Execute the program created in [3-7-2. Create password hashing program](# 3-7-2-Create password hashing program) in the Java application.

The value displayed on the console is the hashed password.

image

4. Implementation of authentication / authorization function for actual business

4-1. Authentication function for actual business

Introducing how to implement Multi-Factor Authentication (MFA). In recent years, many systems have introduced MFA to improve security. Also, since MFA is installed in many of our systems, I would like to write an article about how to implement MFA as a practical implementation. The implementation of MFA will be a bit long and will be explained in detail in the article below. It is an implementation article of MFA using password and one-time password. Currently being created (please wait a little longer)

4-2. Authorization function for actual business

As a result of hearing about the actual system in the company, one of them was "store access control information in DB and perform authorization processing". (In [3. Implementation of simple authentication / authorization function](# 3-Implementation of simple authentication / authorization function), authorization processing was performed using annotations.) I would like to write an article about how to implement authorization processing based on the access control information stored in this DB as an implementation for actual business.

4-2-1. Advantages of implementation

In the simple implementation, the authorization function was implemented using @PreAuthorize. However, in an actual system, it seems that the authorization function is often implemented using DB. The reason is that it's easier to manage the pages that allow access to Role </ font>. When @PreAuthorize is used, annotations are added for each Controller and Method, so it is distributed to various files and it becomes difficult to know which page is permitted to which Role. This can lead to very poor visibility and inefficiencies when rereading the code later or modifying the system. On the other hand, by creating the following table in DB and managing access control information only in DB, it is much easier to manage Role and pages that allow access </ font> > Becomes. Here is an example of a part of the table used this time. The * in Role is a wildcard, which means to grant permissions to all Roles (including unauthenticated users). It also means permitting / adminPage to ʻADMIN Role and / userPage to ʻUSER Role.

Role Authorize Page
* /loginForm
ADMIN /adminPage
USER /userPage

4-2-2. Outline of authorization function for actual business to be implemented

The page transition is the same as the simple implementation, as shown in the figure below.

image

The part surrounded by blue in the figure below shows the authorization process for actual business to be implemented. Create voting process, Service, and DAO in authorization process. The authorization function is realized by querying the database to see if the page (URL) that the client user information (Role) is trying to access is allowed.

image

The flow of authorization processing is as follows. (The processing number corresponds to the number in the above figure)

  1. "FilterSecurityInterceptor" calls the authorization process of "AffirmativeBased" that implements ʻAccessDecisionManager` ** (* 1) **.
  2. "Affirmative Based" calls the voting process of "MyVoter" that implements ʻAccessDecisionVoter ** (* 2) **. (In "Affirmative Based", it is possible to set multiple implementation classes of ʻAccessDecisionVoter and call each voting process, but this implementation only sets" MyVoter ".)
  3. "MyVoter" acquires the Role of the client and the requested URL, and calls the "Authorization Service" that determines the access permission.
  4. "Authorization Service" calls "Dao".
  5. "Dao" asks the DB if the combination of the client Role and the requested URL exists, and if it exists, it converts it to an Entity instance called "AccessAuthorization" and returns it to the "AuthorizationService".
  6. The "Authorization Service" returns the access permission (true) if the access permission information (AccessAuthorization) exists, and the access denied (false) if it does not exist, to "MyVoter".
  7. For "MyVoter", grant (ʻACCESS_GRANTED) when access permission (true) is returned from "MyVoter", and deny (ʻACCESS_DENIED) when access denied (false) is returned" Affirmative Based Return to. (Details ** (* 2) **)
  8. "Affirmative Based" returns the access permission if the voting result of "MyVoter" is granted (ʻACCESS_GRANTED), and throws an exception ʻAccessDeniedException if it is denied (ʻACCESS_DENIED`). (Details ** (* 1) **)
  9. "FilterSecurityInterceptor" is "AffirmativeBased", and if access is permitted, the process is sent to the next filter, and if access is denied, ʻAccessDeniedException is thrown. ʻAccessDeniedException is caught by the" Exception Translation Filter ".

(※1) AccessDecisionManager It is an interface that checks whether the resource you are trying to access has access rights. There are three types of implementation classes provided by Spring Security, but all of them call the vote method of the interface called ʻAccessDecisionVoter to determine whether to grant access rights. ʻAccessDecisionVoter votes for" grant "," denial ", or" abstain ", and the implementation class of ʻAccessDecisionManager aggregates the voting results to determine the final access right. If it determines that you do not have access rights, it will raise ʻAccessDeniedException and deny access. The table below shows the implementation classes provided by Spring Security.

Implementation class Description
AffirmativeBased AccessDecisionVoterTo voteGive access when one "grant" is votedImplementation class.
デフォルトで使用されるImplementation class.
ConsensusBased All ofAccessDecisionVoterTo voteGive access when the number of "grant" votes is largeImplementation class.
UnanimousBased AccessDecisionVoterTo voteDon't give access when one "deny" is votedImplementation class.

(※2) AccessDecisionVoter ʻAccessDecisionVoter is an interface for voting whether to grant access by referring to the access policy specified for the resource you are trying to access. ʻAccess Decision Voter votes for" grant, "" reject, "or abstain. The table below shows the main implementation classes provided by Spring Security.

Implementation class Description
WebExpressionVoter Spring Expression Language (SpEL)User credentials using(Authentication)And request information(HttpServletRequest)Implementation class to vote by referring to.
RoleVoter Implementation class that votes by referring to the Role of the user.
RoleHierarchyVoter Implementation class that votes by referring to the layered Role that the user has.
AuthenticatedVoter Implementation class that votes by referring to the authentication status.

In this implementation, we will create a new class called "MyVoter" that implements ʻAccessDecisionVoter, and set only that" MyVoter "to the voting process called by ʻAffirmativeBased.

4-2-3. Project structure of authorization function for actual business

Based on the one implemented in [3. Implementation of simple authentication / authorization function](# 3-Implementation of simple authentication / authorization function), highlight the files to be added in red and the files to add or modify the code in blue. I have.

image

4-2-4. H2 database (additional code)

** Show code for "schema.sql" **

schema.sql


/*Add the following code to the end of the file*/
CREATE TABLE IF NOT EXISTS access_authorization (
  rolename VARCHAR(10) NOT NULL,
  uri VARCHAR(255) NOT NULL,
  PRIMARY KEY(rolename, uri)
);
** Show code for "data.sql" **

data.sql


/*Add the following code to the end of the file*/
/*All Role permissions*/
INSERT INTO access_authorization(rolename, uri) VALUES ('*', '/loginForm');
INSERT INTO access_authorization(rolename, uri) VALUES ('*',  '/accessDeniedPage');
INSERT INTO access_authorization(rolename, uri) VALUES ('*', '/logout');

/*ADMIN Role permissions*/
INSERT INTO access_authorization(rolename, uri) VALUES ('ADMIN', '/home');
INSERT INTO access_authorization(rolename, uri) VALUES ('ADMIN', '/adminPage');

/*USER Role permissions*/
INSERT INTO access_authorization(rolename, uri) VALUES ('USER',  '/home');
INSERT INTO access_authorization(rolename, uri) VALUES ('USER',  '/userPage');

A roleName of "*" is a wildcard that means grant access to all Roles </ font>.

4-2-5. Controller (code correction)

image
Comment out unnecessary `@PreAuthorize` to set authorization in DB.
** Show code for "AdminPageController.java" **

AdminPageController.java


/*abridgement*/
	@GetMapping("/adminPage")
	// @PreAuthorize("hasRole('ROLE_ADMIN')")Comment out
	public String adminPage() {
/*abridgement*/
** Show code for "UserPageController.java" **

UserPageController.java


/*abridgement*/
	@GetMapping("/userPage")
	// @PreAuthorize("hasRole('ROLE_USER')")Comment out
	public String userPage() {
/*abridgement*/

4-2-6. Entity (add file)

image
** Show code for "AccessAuthorization.java" **

AccessAuthorization.java


package com.example.demo.entity;

import java.io.Serializable;

public class AccessAuthorization implements Serializable {
	String roleName;	//Access in H2DB_Of the authorization table"rolename"Field to store
	String uri;			//Access in H2DB_Of the authorization table"uri"Field to store

	/**
	 * getter, setter
	 */
	public String getRoleName() {
		return roleName;
	}
	public void setRoleName(String roleName) {
		this.roleName = roleName;
	}
	public String getUri() {
		return uri;
	}
	public void setUri(String uri) {
		this.uri = uri;
	}
}

4-2-7. DAO (add file)

Access the DB using Spring JDBC.

image
** Show code for "AccessAuthorizationDao.java" **

AccessAuthorizationDao.java


package com.example.demo.repository;

import com.example.demo.entity.AccessAuthorization;

public interface AccessAuthorizationDao {
	AccessAuthorization find(String roleName, String uri);
}
** Show code for "AccessAuthorizationDaoImpl.java" **

AccessAuthorizationDaoImpl.java


package com.example.demo.repository;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.example.demo.entity.AccessAuthorization;

@Repository
public class AccessAuthorizationDaoImpl implements AccessAuthorizationDao {
	private final JdbcTemplate jdbcTemplate;

	@Autowired
	public AccessAuthorizationDaoImpl(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	/**
	 *Execute the SELECT statement with roleName and uri as the search condition to search whether it is registered in the DB.
	 * @param roleName
	 * @param uri
	 * @return AccessAuthorization
	 */
	@Override
	public AccessAuthorization find(String roleName, String uri) {
		String sql = "SELECT rolename, uri FROM access_authorization WHERE rolename = ? AND uri = ?";

		//Get one user
		Map<String, Object> result = jdbcTemplate.queryForMap(sql, roleName, uri);

		//Entity class(User type)Conversion to
		AccessAuthorization auth = convMapToAccessAuthorization(result);

		return auth;
	}

	/**
	 *Result of executing SQL SELECT statement(Map<String, Object>)To Access Authorization type
	 * @param Map<String, Object>
	 * @return AccessAuthorization
	 */
	private AccessAuthorization convMapToAccessAuthorization(Map<String, Object> map) {
		AccessAuthorization auth = new AccessAuthorization();

		auth.setRoleName((String) map.get("rolename"));
		auth.setUri((String) map.get("uri"));

		return auth;
	}
}

4-2-8. Service (add file)

image
** Show code for "AuthorizationService.java" **

AuthorizationService.java


package com.example.demo.service;

public interface AuthorizationService {
	boolean isAuthorized(String roleName, String uri);
}
** Show code for "AuthorizationServiceImpl.java" **

AuthorizationServiceImpl.java


package com.example.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.example.demo.entity.AccessAuthorization;
import com.example.demo.repository.AccessAuthorizationDao;

@Service
public class AuthorizationServiceImpl implements AuthorizationService {
	private final AccessAuthorizationDao authDao;

    @Autowired
    public AuthorizationServiceImpl(AccessAuthorizationDao authDao) {
        this.authDao = authDao;
    }

    /**
     *Determine if the combination of RoleName and URI passed in the argument is permitted.
     * @param roleName
     * @param uri
     * @return boolean
     */
    @Override
    public boolean isAuthorized(String roleName, String uri) { // ---(1)Method to determine if access is allowed

        if (StringUtils.isEmpty(roleName)) {
            throw new IllegalArgumentException("RoleName is empty.");
        }

        if (StringUtils.isEmpty(uri)) {
            throw new IllegalArgumentException("The URI is empty.");
        }

        //Get one AccessAuthorization Exception occurs if there is no AccessAuthorization
        try {
            AccessAuthorization auth = authDao.find(roleName, uri); // ---(2)Get an AccessAuthorization instance

            if (auth != null) { // ---(3)Permissions
                return true;

            } else {            // ---(4)Access denied
                return false;

            }
        } catch (EmptyResultDataAccessException e) { // ---(5)Access denied
            return false;
        }
    }
}
item Description
(1) User roleName and path to access(URI)Is a method that determines if access is granted.
(2) Pass roleName and URI to Dao and be EntityAccessAuthorizationGet an instance of.
(3) AccessAuthorizationIf you can get an instance of, it is determined that access is permitted, andtruereturn it.
(4) From DaoAccessAuthorizationCan't get an instance ofnullIf is returned, it is judged that access is denied and access is denied.falsereturn it.

4-2-9. Voter (add file)

image
** Show code for "MyVoter.java" **

MyVoter.java


package com.example.demo.voter;

import java.util.Collection;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Component;

import com.example.demo.service.AccountUserDetails;
import com.example.demo.service.AuthorizationService;

@Component
public class MyVoter implements AccessDecisionVoter<FilterInvocation> {

	private final AuthorizationService authorizationService;

	@Autowired
	public MyVoter(AuthorizationService authorizationService) {
		this.authorizationService = authorizationService;
	}

	@Override
    public boolean supports(ConfigAttribute attribute) {	// ---(1)Method to determine if voting is required or not
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {				// ---(1)Method to determine if voting is required or not
		return true;
    }

    @Override
    public int vote(Authentication authentication, FilterInvocation filterInvocation,
    		Collection<ConfigAttribute> attributes) { 		// ---(2)A method to vote whether to grant access

    	HttpServletRequest request = filterInvocation.getHttpRequest();	// --- (3)Get HttpServletRequest
    	String uri = request.getRequestURI();							// --- (4)Get URI from request

    	if(authorizationService.isAuthorized("*", uri)) {	// --- (5)Determine if all Roles are allowed access
        	return ACCESS_GRANTED;
        }

		Object principal = authentication.getPrincipal();	// --- (6)Acquisition of user identification information

		if (!principal.getClass().equals(AccountUserDetails.class)) { // ---(7)Determine if the acquired identification information is Account User Details
			return ACCESS_DENIED;
		}

		String roleName = ((AccountUserDetails) principal).getUser().getRoleName(); // ---(8)Get the user's Role

		if(authorizationService.isAuthorized(roleName, uri)) { // ---(9)Determine if the acquired Role is authorized
        	return ACCESS_GRANTED;
        }

    	return ACCESS_DENIED;
    }
}
Item number Description
(1) A method that determines whether voting is necessary or not by referring to the argument value.
(2) A method to vote whether to grant access(Detail is,4-2-2.Overview of authorization function for actual business to be implemented)
(3) HttpServletRequestTo get.
FilterInvocationIsHttpServletRequestOrHttpServletRequestHolds objects associated with HTTP filters such as.
(4) HttpServletRequestGet the URI you are trying to access from.
(5) Determine if the retrieved URI is allowed access to all Roles.
(6) Acquire the identification information of the accessing user
(7) The acquired identification information isAccountUserDetailsDetermine if it is a class.
If not authenticated, the identification information is acquired in the String class, so an error will occur if this determination process is not performed.
(8) AccountUserDetailsGet the user's Role from an instance of.
(9) Determine if the user's Role and the path you are trying to access are authorized.

4-2-10. JavaConfig (code modification)

image

Set the created "MyVoter" to vote for access rights.

** Show code for "WebSecurityConfig.java" **

WebSecurityConfig.java


/*abridgement*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    AccountUserDetailsService userDetailsService;

    @Autowired										//Postscript
    AccessDecisionVoter<FilterInvocation> myVoter;	//Postscript

	public AccessDecisionManager createAccessDecisionManager() { //Postscript
		return new AffirmativeBased(Arrays.asList(myVoter)); //Postscript---(1)Affirmative Based is used for authorization processing, and MyVoter is used for voting processing.
	} //Postscript

/*abridgement*/

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //Authorization settings
        http.exceptionHandling()
        		.accessDeniedPage("/accessDeniedPage")
        	.and()
        	.authorizeRequests()
				.antMatchers("/**").authenticated() //Fix
					.accessDecisionManager(createAccessDecisionManager()); //Postscript---(2)Apply authorization process for all access

/*abridgement*/
Item number Description
(1) AccessDecisionManagerAs an implementation class ofAffirmativeBasedTo apply.
AccessDecisionVoterAs an implementation class ofMyVoterTo apply.
AffirmativeBasedWhen instantiatingAccessDecisionVoterYou can set multiple voting processes by passing an instance of.
(2) Authorization processing for all access(MyVoterVoting process)Is set to be executed.

5. Summary

Nowadays, security issues are frequently covered in the news. Therefore, be sure to implement security measures in your web application. If you are developing a web application using the Spring Framework, you should take advantage of Spring Security for security measures. You can introduce simple and robust security measures rather than taking security measures by yourself. On the other hand, from a practical point of view, it is necessary to implement a partial extension of Spring Security due to requirements such as wanting to refer to the DB in the authorization process, but I think that this article has shown a little how to extend it. .. The idea that "on the basis of a standard product, additional functions necessary for business and operation are implemented on top of it" is important in actual business, and the author himself can learn through hearings and writing articles. I did. In this article, Spring Security has implemented the "authentication function" and "authorization function", which are the basic functions of security measures. Other security measures are also provided by Spring Security. I still have a lot of things I don't know, so I want to learn.

bonus

This is an article written by a newcomer (my synchronization) in the next section. If you would like to introduce SSO (single sign-on), please read the following article. Explains the procedure for linking Keycloak and Azure AD. Try external ID linkage using Keycloak

Reference material

-Thorough introduction to Spring Java application development with Spring Framework

Recommended Posts