[JAVA] Un nouvel employé a tenté de créer une fonction d'authentification / autorisation à partir de zéro avec Spring Security

Si vous voulez écrire du code rapidement sans histoire longue, ignorez [1. Introduction](# 1-Introduction). Pour les débutants de Spring, allez dans [2. Créer un projet](# 2-Créer un projet). Pour ceux qui souhaitent implémenter une simple fonction d'authentification / autorisation, allez dans [3. Implémentation d'une simple fonction d'authentification / autorisation](# 3-Implémentation d'une simple fonction d'authentification / autorisation). Ceux qui souhaitent implémenter la fonction d'authentification / autorisation pour une entreprise réelle doivent commencer à lire [4. Implémentation de la fonction d'authentification / autorisation pour une entreprise réelle](# 4-Implémentation de la fonction d'authentification / autorisation pour une entreprise réelle).

1.Tout d'abord

1-1. Contexte

Tout d'abord, je vais vous expliquer l'authentification / l'autorisation. L'explication simple est la suivante. (Page de référence pour ceux qui veulent en savoir plus: Authentification et autorisation familières)

--Authentification Vérifiez avec qui vous communiquez </ font>.

--Autorisation Accordez l'autorisation d'accès aux ressources pour des conditions spécifiques telles que l'autorisation d'accès à l'écran et l'autorisation de traitement </ font>.

Lors de la mise en œuvre de cette fonction d'authentification / autorisation dans une application Web créée avec Spring Framework, elle peut être facilement implémentée à l'aide de Spring Security. Il existe de nombreux articles qui implémentent des fonctions d'authentification / autorisation à l'aide de Spring Security, mais je pense qu'il existe de nombreux articles tels que les suivants.

  • La base de données n'est pas utilisée.
  • Le nom d'utilisateur et le mot de passe sont écrits directement sur le code.
  • Le mot de passe n'est pas haché.
  • Seul le code source de la partie importante liée à l'authentification / autorisation est répertorié (c'est-à-dire qu'il est difficile à lire).

Cependant, l'utilisation de la base de données et le hachage des mots de passe sont monnaie courante </ font>. Lorsque j'ai créé un système Web avec des fonctions d'authentification / autorisation, j'ai eu beaucoup de mal à lire de nombreux articles et à décoder le code source sur GitHub. Sur la base de cette expérience, même les débutants en matière de sécurité Spring et d'authentification / autorisation peuvent utiliser la base de données et le hachage et ont besoin d'un article qui peut implémenter la fonction si vous lisez ceci </ font> J'ai pensé et écrit cet article. Aussi, dans le système utilisé sur le terrain, vous vous demandez comment sont implémentés le code source et les tables de base de données liés à l'authentification / autorisation. Par conséquent, j'ai écrit cet article avec l'intention d'étudier le type de fonction d'authentification / d'autorisation implémentée en faisant référence à notre exemple de construction de système </ font>.

1-2. Objet

――Vous n'avez pas besoin de lire le code source de GitHub, lisez simplement cet article, et même les débutants peuvent implémenter des fonctions d'authentification / autorisation à l'aide de Spring Security. Nous résumerons le contenu de l'enquête et des informations sur l'authentification et l'autorisation en se référant à notre exemple de construction de système comme méthode de mise en œuvre de la fonction d'authentification et d'autorisation pour les affaires réelles.

  • Veuillez pardonner le fait que le cas réel n'est pas le même que le cas réel en raison de la sélection des informations et de l'abstraction du cas dans le processus de création de l'article.

1-3. Cible

--Les personnes qui souhaitent implémenter des fonctions d'authentification / d'autorisation à l'aide de Spring Security

  • Les personnes qui développent des fonctions d'authentification / d'autorisation dans le travail réel
  • Les personnes qui ne veulent pas lire le code sur GitHub et qui veulent l'implémenter en lisant l'article

1-4. Prérequis

1-5. Objectifs de cet article

――Deux implémentations, une implémentation simple et une implémentation pour une entreprise réelle.

** Implémentation simple ** </ font> est une implémentation du traitement d'authentification / autorisation pour les débutants. Seule la partie implémentation du matériel de référence Spring Security est résumée par l'auteur. C'est parfait pour les débutants de Spring Security d'avoir une meilleure compréhension en l'exécutant sur leur propre appareil. ** La mise en œuvre pour les affaires réelles ** </ font> est basée sur des auditions internes sur la manière dont le traitement d'authentification / autorisation est réalisé dans un système réel. Nous mettrons en œuvre un traitement d'authentification / autorisation proche du système actuel. C'est une implémentation pour ceux qui développent le traitement d'authentification / autorisation en entreprise.

  • Les paramètres / fonctions présentés dans cet article doivent être compris comme l'un des paramètres / fonctions à prendre en compte lors de l'application d'un projet réel.

Les trois objectifs suivants s'appliquent à la fois à la mise en œuvre simple et à la mise en œuvre pratique </ font>.

  • Vous pouvez l'implémenter en copiant le code source de cet article.
  • Utilisez la base de données H2 et hachez le mot de passe. -Réaliser la fonction d'authentification / autorisation qui effectue des transitions de page comme indiqué dans l'image ci-dessous. (La transition de page est la même pour une implémentation simple et une implémentation pour une entreprise réelle.)
image

1-6. Environnement de développement / environnement d'exécution

  • 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 Même si vous ne lisez pas le code source sur GitHub, vous pouvez implémenter la fonction en lisant cet article, mais pour ceux qui disent "Il est plus rapide de lire GitHub", je posterai le code source sur GitHub.

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

--BasicAuth est le code source de la fonction d'authentification / autorisation d'implémentation simple.

  • L'autorisation avancée est le code source de la fonction d'autorisation à usage professionnel

Il est devenu.

2. Créez un projet

2-1. Changer de perspective vers Java EE

"Fenêtre" -> "Perspective" -> "Ouvrir la perspective" -> "Autre"

image

Sélectionnez "Java EE" pour ouvrir

image

2-2. Créer un nouveau projet

"Fichier" -> "Nouveau" -> "Projet Spring Starter"

image

Modifiez la valeur de chaque élément comme indiqué ci-dessous et cliquez sur "Suivant".

article valeur
Nom Valeur arbitraire
Moule Gradle (Buildship 3.x)
Version Java 11
Livrables Valeur arbitraire
image

Appuyez sur "Terminer"

image

2-3. Remplacez application.properties par application.yml (facultatif)

Puisque les propriétés et yml ne sont décrits que légèrement différemment, il n'y a pas de problème même s'ils sont laissés en tant que "application.properties". J'aime savoir si je dois le changer. Toutefois, dans cet article, le code source est basé sur application.yml.

image

3. Mise en œuvre d'une fonction d'authentification / autorisation simple

3-1. Image système à créer et configuration du projet

La figure ci-dessous est une image du système Web créé. Le flux de traitement détaillé du traitement de l'authentification et du traitement des autorisations est [3-5-1. Aperçu de la fonction d'authentification Spring Security](# 3-5-1-spring-security-Aperçu de la fonction d'authentification) et [3-6-1. Spring Aperçu de la fonction d'autorisation de sécurité](# 3-6-1-spring-security-aperçu de la fonction d'autorisation).

image

La figure ci-dessous montre la structure du projet du système.

image

3-2. Paramètres de dépendance

Écrivez build.gradle pour définir les dépendances.

** Afficher le code pour "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. Paramètres de la base de données H2

3-3-1. Présentation de la base de données H2 (H2DB)

H2DB est une base de données en mémoire écrite en Java. Les caractéristiques de H2DB sont les trois suivantes.

  1. Léger et facile à installer
  2. Opération rapide
  3. Vous pouvez basculer vers le comportement d'autres bases de données en définissant un mode tel que le mode Oracle ou le mode PostgreSQL.

Puisqu'il s'agit d'une base de données en mémoire, les données seront perdues une fois le processus terminé. Il peut également être utilisé comme base de données embarquée, mais cette fois, il sera installé afin d'améliorer le contenu de la base de données pour les affaires réelles dans [4. Implémentation de la fonction d'authentification / autorisation pour les affaires réelles](# 4-Implémentation de la fonction d'authentification / autorisation pour les affaires réelles). Utilisé comme base de données de mémoire. Puisqu'il est en mémoire, la valeur initiale de H2DB doit être définie en exécutant une instruction SQL à chaque démarrage du traitement. Dans le cas de Spring Boot, en décrivant l'instruction SQL dans "schema.sql" et "data.sql", l'instruction SQL peut être exécutée automatiquement à chaque démarrage du processus. </ font>

3-3-2. Structure de projet liée à la base de données H2

image

3-3-2. Application.yml (code supplémentaire)

Décrivez les paramètres H2DB dans application.yml.

** Afficher le code pour "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 (ajouter un fichier)

Décrivez la structure de table de la base de données en SQL.

** Afficher le code pour "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 (ajouter un fichier)

Insérez des données dans H2DB. La chaîne de 60 caractères commençant par "\ $ 2a \ $ 10 \ $" est le mot de passe haché. Les détails seront expliqués dans [3-7. Hashing Passwords](# 3-7-Hashing Passwords), veuillez donc copier et coller ici.

Mot de passe simple Mot de passe haché
admin $2a$10$bCR1jXhdqbh1oC8ckXplxePYW5Kyb/VjN28MZx2PwXf1ybzLIFUQG
user $2a$10$yyT1siJCep647RT/I7KjcuUB5noFVU6RBo0FUXUJX.hb2MIlWTbDe
** Afficher le code pour "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. Mise en œuvre des fonctions d'application

3-4-1. Présentation des fonctions de l'application

Créez une application qui peut afficher et passer aux cinq pages ci-dessous.

image

Le traitement de l'application pour réaliser cette transition de page est la partie entourée de vert clair dans la figure ci-dessous. Pour vous concentrer sur le traitement d'authentification / autorisation, créez uniquement Controller et View pour le traitement de l'application. (Service, Dao, etc. ne sont pas créés.)

image

3-4-2. Structure du projet liée aux fonctions de l'application

image

3-4-3. Contrôleur (ajouter un fichier)

image
** Afficher le code pour "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";
	}
}
** Afficher le code pour "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";
	}
}
** Afficher le code pour "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";
	}
}
** Afficher le code pour "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";
	}
}
** Afficher le code pour "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. Afficher (ajouter un fichier)

Créez un fichier HTML. Le code source utilise Thymeleaf comme moteur de modèle. Si vous n'avez pas de dossier "templates", ajoutez-en un sous src / main / resources.

image
image
** Afficher le code pour "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>S'identifier</title>
</head>
<body>
	<h1>S'identifier</h1>
	<div th:if="${param.error}" > <!-- (1)Jugement des paramètres de la demande-->
		<div>
Le nom d'utilisateur ou le mot de passe est différent.<!-- (2)Message d'erreur d'authentification-->
		</div>
	</div>

	<form method="post" th:action="@{/authenticate}"> <!-- (3)Spécifiez le chemin du processus de connexion-->
		<label>UserName:</label>
		<input type="text" id="userName" name="userName"> <!-- (4)Entrée du nom d'utilisateur-->
		<br>
		<label>Password:</label>
		<input type="password" id="password" name="password"> <!-- (5)Section de saisie du mot de passe-->
		<br>
		<input type="submit" id="submit" value="S'identifier">
	</form>
</body>
</html>
Numéro d'article La description
(1) Jugez le message d'erreur défini dans le paramètre de demande.
3-5-6. JavaConfig(Ajouter le fichier) "WebSecurityConfig.java"DansModifiez le processus de jugement en fonction de la valeur définie dans "failureUrl"Veuillez noter que c'est nécessaire.
(2) Message d'exception à afficher lorsqu'une erreur d'authentification se produit
(3) Spécifiez la destination de la transition pour effectuer le traitement d'authentification dans l'attribut d'action du formulaire.
Le chemin de destination de la transition est3-5-6. JavaConfig(Ajouter le fichier) "WebSecurityConfig.java"Dans"Correspond à la valeur spécifiée dans loginProcessingUrl »Il y a un besoin.
Spécifiez "POST" pour la méthode HTTP.
Dans ce cas${pageContext.request.contextPath}/Le processus d'authentification est exécuté en accédant à authenticate.
(4) Un élément qui est traité comme un "nom d'utilisateur" dans le processus d'authentification.
L'attribut de nom est3-5-6. JavaConfig(Ajouter le fichier) "WebSecurityConfig.java"Dans"Correspond à la valeur spécifiée dans usernameParameter »Il y a un besoin.
(5) Un élément qui est traité comme un "mot de passe" dans le processus d'authentification.
L'attribut de nom est3-5-6. JavaConfig(Ajouter le fichier) "WebSecurityConfig.java"Dans"Correspond à la valeur spécifiée par passwordParameter »Il y a un besoin.
image
** Afficher le code pour "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="Vers AdminPage">
	</form>

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

	<form method="post" th:action="@{/logout}"> <!-- (1)Spécifiez le chemin de traitement de déconnexion-->
		<input type="submit" value="Se déconnecter">
	</form>
</body>
</html>
Numéro d'article La description
(1) Spécifiez le chemin pour exécuter le processus de déconnexion dans l'attribut action du formulaire.
Le chemin du processus de déconnexion est la valeur par défaut de Spring Security,/Spécifiez la déconnexion.
Spécifiez "POST" pour la méthode HTTP.
image
** Afficher le code pour "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>article</th>
			<th>Contenu</th>
		</tr>
		<tr>
			<td>UserName</td>
			<td><span sec:authentication="principal.username"></span></td> <!-- (1)Afficher le nom d'utilisateur-->
		</tr>
		<tr>
			<td>Name</td>
			<td><span sec:authentication="principal.name"></span></td> <!-- (2)Afficher le nom-->
		</tr>
		<tr>
			<td>Role</td>
			<td><span sec:authentication="principal.authorities"></span></td> <!-- (3)Afficher le rôle-->
		</tr>
	</table>

	<form method="get" th:action="@{/home}">
		<input type="submit" value="À la maison">
	</form>
	<form method="post" th:action="@{/logout}">
		<input type="submit" value="Se déconnecter">
	</form>
</body>
</html>
Numéro d'article La description
(1) Utilisez Thymeleaf pour accéder à l'objet d'authentification et afficher le nom d'utilisateur.
(2) Utilisez Thymeleaf pour accéder à l'objet d'authentification et afficher le nom.
(3) Utilisez Thymeleaf pour accéder à l'objet d'authentification et afficher le rôle.
image
** Afficher le code pour "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>article</th>
			<th>Contenu</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="À la maison">
	</form>
	<form method="post" th:action="@{/logout}">
		<input type="submit" value="Se déconnecter">
	</form>
</body>
</html>
image
** Afficher le code pour "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>Accès refusé</h1>

	<form method="get" th:action="@{/home}">
		<input type="submit" value="À la maison">
	</form>
	<form method="post" th:action="@{/logout}">
		<input type="submit" value="Se déconnecter">
	</form>
</body>
</html>

3-5. Mise en œuvre de la fonction d'authentification

3-5-1. Aperçu de la fonction d'authentification Spring Security

La partie entourée de rose dans la figure ci-dessous montre la fonction d'authentification.

image

Le déroulement du processus d'authentification est le suivant. (Le numéro de traitement correspond au numéro de la figure ci-dessus)

  1. Appelez le processus d'authentification de "Authentication Manager" à partir de "Filtre d'authentification par mot de passe par nom d'utilisateur".
  2. Depuis "AuthenticationManager", appelez le processus d'acquisition d'utilisateur de "AccountUserDetailsService" qui hérite de ʻUserDetailsService` ** (* 1) **.
  3. Appelez "Dao" depuis "AccountUserDetailsService".
  4. "Dao" convertit les informations utilisateur obtenues à partir de la base de données, telles que le nom d'utilisateur et le mot de passe, en une instance d'entité appelée "MyUser" et la renvoie à "AccountUserDetailsService".
  5. "AccountUserDetailsService" convertit les informations utilisateur (MyUser) en une instance de "AccountUserDetails" qui hérite de ʻUserDetails` ** (* 2) ** et les renvoie à "AuthenticationManager".
  6. Le "Gestionnaire d'authentification" fait correspondre le "AccountUserDetails" avec les informations d'identification spécifiées par le client et renvoie le résultat au "UsernamePasswordAuthenticationFilter".
  7. «UsernamePasswordAuthenticationFilter» reçoit le résultat d'authentification renvoyé par «AuthenticationManager» et contrôle la réponse en cas de réussite ou d'échec d'authentification.

(※1) UserDetailsService C'est une interface qui est responsable de l'acquisition des informations d'identification (nom d'utilisateur et mot de passe) requises pour le processus d'authentification et l'état de l'utilisateur. (※2) UserDetails Une interface qui fournit les informations d'identification et l'état de l'utilisateur, créée à partir de ʻUserDetailsService`.

Lors de l'exécution du traitement d'authentification à l'aide de DB, créez la classe d'implémentation UserDetailsService (AccountUserDetailsService dans cet article) et la classe d'implémentation UserDetails (AccountUserDetails dans cet article) en fonction des exigences de l'application. </ font> Doit être.

3-5-2. Configuration du projet liée à la fonction d'authentification

image

3-5-3. Entité (ajouter un fichier)

image
** Afficher le code pour "MyUser.java" **

MyUser.java


package com.example.demo.entity;

import java.io.Serializable;

public class MyUser implements Serializable{
	private String userName;	//Dans H2DB, la table des utilisateurs"username"Champ à stocker

	private String password;	//Dans H2DB, la table des utilisateurs"password"Champ à stocker

	private String name;		//Dans H2DB, la table des utilisateurs"name"Champ à stocker

	private String roleName;	//Dans H2DB, la table des utilisateurs"roleName"Champ à stocker

	/**
	 * 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 (ajouter un fichier)

Accédez à la base de données à l'aide de Spring JDBC.

image
** Afficher le code pour "UserDao.java" **

UserDao.java


package com.example.demo.repository;

import com.example.demo.entity.MyUser;

public interface UserDao {
	MyUser findUserByUserName(String userName);
}
** Afficher le code pour "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;
	}

	/**
	 *Exécutez l'instruction SELECT avec userName comme condition de recherche pour rechercher les utilisateurs enregistrés dans la base de données.
	 * @param userName
	 * @return User
	 */
	@Override
	public MyUser findUserByUserName(String userName) {
		String sql = "SELECT username, password, name, rolename FROM users WHERE username = ?";

		//Obtenez un utilisateur
		Map<String, Object> result = jdbcTemplate.queryForMap(sql, userName);

		//Classe d'entité(Type d'utilisateur)Conversion en
		MyUser user = convMapToUser(result);

		return user;
	}

	/**
	 *Résultat de l'exécution de l'instruction SQL SELECT(Map<String, Object>)Au type d'utilisateur
	 * @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 (ajouter un fichier)

image
** Afficher le code pour "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)Méthode pour récupérer les informations de compte de la base de données

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

		//Obtenir une exception d'utilisateur se produit s'il n'y a pas de nom d'utilisateur
		try {
			//Obtenir un utilisateur
	    	MyUser myUser = userDao.findUserByUserName(userName);

	    	if (myUser != null) {
	    		return new AccountUserDetails(myUser); // --- (2)Générer une classe d'implémentation pour UserDetails

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

		} catch (EmptyResultDataAccessException e) {
			throw new UsernameNotFoundException(userName + "is not found");
		}
	}
}
Numéro d'article La description
(1) Recherchez les informations de compte dans la base de données. Lancer une exception UsernameNotFoundException si les informations de compte ne sont pas trouvées
(2) Si les informations de compte sont trouvées, la classe d'implémentation de UserDetails(AccountUserDetails)Générer.
image
** Afficher le code pour "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)Méthode pour renvoyer MyUser qui est Entity
		return myUser;
	}

	public String getName() { // --- (2)Méthode pour renvoyer le nom
		return this.myUser.getName();
	}


	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() { // --- (3)Méthode pour renvoyer la liste des autorisations données à l'utilisateur
		return AuthorityUtils.createAuthorityList("ROLE_" + this.myUser.getRoleName());
	}

	@Override
	public String getPassword() { // --- (4)Méthode pour renvoyer le mot de passe enregistré
		return this.myUser.getPassword();
	}

	@Override
	public String getUsername() { // --- (5)Méthode pour renvoyer le nom d'utilisateur
		return this.myUser.getUserName();
	}

	@Override
	public boolean isAccountNonExpired() { // --- (6)Méthode pour déterminer le statut d'expiration d'un compte
		return true;
	}

	@Override
	public boolean isAccountNonLocked() { // --- (7)Méthode pour déterminer l'état de verrouillage d'un compte
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() { // --- (8)Une méthode pour déterminer l'état d'expiration des informations d'identification
		return true;
	}

	@Override
	public boolean isEnabled() { // --- (9)Méthode pour déterminer s'il s'agit d'un utilisateur valide
		return true;
	}
}
Numéro d'article La description
(1) Une méthode qui renvoie MyUser, qui est une entité.
Préparez une méthode getter afin que les informations de compte soient accessibles dans le processus après un processus d'authentification réussi.
(2) Une méthode qui renvoie le nom.
(3) Une méthode qui renvoie la liste d'autorisations donnée à l'utilisateur.
Cette méthode est utilisée dans le processus d'autorisation. Dans le processus d'autorisation de Spring Security, "ROLE_Les informations d'autorité commençant par "sont traitées comme un rôle. Par conséquent, "ROLE_"Est ajouté.
(4) Une méthode pour renvoyer le mot de passe enregistré.
Le mot de passe renvoyé par cette méthode est utilisé pour la comparaison avec le mot de passe spécifié par le client.
(5) Une méthode qui renvoie un nom d'utilisateur.
(6) Une méthode pour déterminer le statut d'expiration d'un compte.
S'il est dans la date d'expiration, true est renvoyé, et s'il a expiré, false est renvoyé.
Dans ce programme, seul vrai est renvoyé par souci de simplicité.
(7) Une méthode pour déterminer l'état de verrouillage d'un compte.
S'il n'est pas verrouillé, ture est retourné, et si le compte est verrouillé, false est retourné.
Dans ce programme, seul vrai est renvoyé par souci de simplicité.
(8) Une méthode qui détermine l'état d'expiration des informations d'identification.
S'il est dans la date d'expiration, true est renvoyé, et s'il a expiré, false est renvoyé.
Dans ce programme, seul vrai est renvoyé par souci de simplicité.
(9) Une méthode qui détermine si l'utilisateur est valide.
S'il est valide, il renvoie vrai, et s'il s'agit d'un utilisateur non valide, il renvoie faux.
Dans ce programme, seul vrai est renvoyé par souci de simplicité.

3-5-6. JavaConfig (ajouter un fichier)

image
** Afficher le code pour "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 des mots de passe à l'aide de l'algorithme BCrypt
		return new BCryptPasswordEncoder(); // --- (2)Utiliser l'algorithme BCrypt
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		//Définir le UserDetailsService implémenté dans AuthenticationManagerBuilder
		auth.userDetailsService(userDetailsService)		// --- (3)Définir le UserDetailsService créé
				.passwordEncoder(passwordEncoder());	// --- (2)Spécifiez comment hacher le mot de passe(Algorithme BCrypt)
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		//Paramètres d'autorisation
		http.authorizeRequests()
				.antMatchers("/loginForm").permitAll()	// --- (4) /loginForm permet l'accès de tous les utilisateurs
				.anyRequest().authenticated();			// --- (5) /Demander l'authentification sauf loginForm

		//Paramètres de connexion
		http.formLogin()								// --- (6)Activer l'authentification par formulaire
				.loginPage("/loginForm")				// --- (7)Chemin pour afficher le formulaire de connexion
				.loginProcessingUrl("/authenticate")	// --- (8)Chemin du processus d'authentification par formulaire
				.usernameParameter("userName")			// --- (9)Nom du paramètre de demande de nom d'utilisateur
				.passwordParameter("password")			// --- (10)Nom du paramètre de demande de mot de passe
				.defaultSuccessUrl("/home")				// --- (11)Chemin par défaut de la transition lorsque l'authentification est réussie
				.failureUrl("/loginForm?error=true");	// --- (12)Chemin vers la transition lorsque l'authentification échoue

		//Paramètres de déconnexion
		http.logout()
				.logoutSuccessUrl("/loginForm")			// --- (13)Chemin vers la transition lorsque la déconnexion est réussie
				.permitAll();							// --- (14)Autoriser l'accès à tous les utilisateurs
	}
}
Numéro d'article La description
(1) @EnableWebSecuritySi est spécifié, la définition du bean du composant requis pour utiliser Spring Security est automatiquement effectuée.
(2) Utilisez l'algorithme BCrypt pour définir le hachage du mot de passe.
(3) AuthenticationManagerBuilderCréé enUserDetailsServiceMettre en place.
(4) /loginForm permet l'accès de tous les utilisateurs.
(5) /Autre que loginForm, il nécessite une authentification.
Les utilisateurs non authentifiés sont redirigés vers l'écran de connexion.
Les utilisateurs authentifiés mais non autorisés se voient refuser l'accès.
(6) formLoginL'authentification par formulaire est activée lorsque vous appelez la méthode.
(7) Spécifiez le chemin pour afficher le formulaire de connexion. Si un utilisateur anonyme tente d'accéder à une page nécessitant une authentification, il sera redirigé vers le chemin spécifié ici.
(8) Spécifiez le chemin pour le traitement de l'authentification par formulaire.3-4-4. View(Ajouter le fichier)S'identifier.En htmlFaire correspondre l'attribut d'action de la balise formIl y a un besoin.
(9) Spécifiez le nom du paramètre de demande du nom d'utilisateur qui est l'information d'identification.3-4-4. View(Ajouter le fichier)S'identifier.En htmlFaites correspondre l'attribut name de la balise d'entrée pour saisir le nom d'utilisateurIl y a un besoin.
(10) Spécifiez le nom du paramètre de demande du mot de passe qui est les informations d'identification.3-4-4. View(Ajouter le fichier)S'identifier.En htmlFaites correspondre l'attribut name de la balise d'entrée pour saisir le mot de passeIl y a un besoin.
(11) Spécifiez le chemin par défaut de la transition lorsque l'authentification est réussie.
(12) Spécifiez le chemin vers lequel effectuer la transition en cas d'échec de l'authentification.
(13) Spécifiez le chemin vers lequel effectuer la transition lorsque la déconnexion est réussie.
(14) Autorisez tous les utilisateurs à accéder aux déconnexions et aux chemins qui transitent après une déconnexion réussie.

3-6. Mise en œuvre de la fonction d'autorisation

3-6-1. Présentation de la fonction d'autorisation de Spring Security

La partie entourée de bleu clair dans la figure ci-dessous montre la fonction d'autorisation.

image
Dans le processus d'autorisation, il est déterminé si l'URL demandée par "Filter Security Interceptor" est autorisée. Utilisez `@ PreAuthorize` pour définir les autorisations de chaque utilisateur. «@ PreAuthorize» peut être décrit pour chaque contrôleur ou méthode.

--Si l'accès est accordé Passez au filtre suivant.

  • Si l'accès est refusé Le "Filter Security Interceptor" lève une exception appelée ʻAccessDeniedException`. Une exception est interceptée par «Exception Translation Filter», et une réponse demandant l'authentification (passage à l'écran de connexion) en cas d'accès d'un utilisateur non authentifié, et une réponse notifiant une erreur d'autorisation en cas d'accès d'un utilisateur authentifié (accès refusé) (Transition à l'écran) sera retourné.

3-6-2. Structure du projet liée à la fonction d'autorisation

Le fichier auquel le code est ajouté est surligné en bleu.

image

3-6-3. JavaConfig (code supplémentaire)

image

Si vous essayez d'accéder à une page qui n'est pas autorisée, vous serez redirigé vers une page qui indique que l'accès a été refusé.

** Afficher le code pour "WebSecurityConfig.java" **

WebSecurityConfig.java


/*réduction*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //Postscript--- (1)Activer le traitement des autorisations de méthode
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

/*réduction*/

	protected void configure(HttpSecurity http) throws Exception {
		//Paramètres d'autorisation
		http.exceptionHandling()	//Postscript
				.accessDeniedPage("/accessDeniedPage")	//Postscript--- (2)Chemin vers la transition lorsque l'accès est refusé
			.and()					//Postscript
			.authorizeRequests()
				.antMatchers("/loginForm").permitAll()
				.anyRequest().authenticated();
/*réduction*/
Numéro d'article La description
(1) @PreAuthorizeOu@PostAuthorizeActivez le processus d'autorisation de méthode en.
(2) Accès refusé(Erreur d'autorisation)Une fois terminé, spécifiez le chemin vers lequel effectuer la transition.

3-6-4. Contrôleur (code supplémentaire)

image

Décrivez «@ PreAuthorize» dans chaque méthode pour définir l'autorité d'accès.

** Afficher le code pour "AdminPageController.java" **

AdminPageController.java


/*réduction*/
	@GetMapping("/adminPage")
	@PreAuthorize("hasRole('ROLE_ADMIN')") //Postscript--- (1) ROLE_Autoriser l'accès uniquement aux utilisateurs ADMIN
	public String adminPage() {
/*réduction*/
article La description
(1) 「ROLE_Seuls les utilisateurs qui ont "ADMIN" sont autorisés à accéder.
** Afficher le code pour "UserPageController.java" **

UserPageController.java


/*réduction*/
	@GetMapping("/userPage")
	@PreAuthorize("hasRole('ROLE_USER')") //Postscript--- (1) ROLE_Autoriser l'accès uniquement aux utilisateurs USER
	public String userPage() {
/*réduction*/
article La description
(1) 「ROLE_Seuls les utilisateurs qui ont "USER" ont accès.

3-6-5. Afficher (code supplémentaire)

image

Sur la page "home", les boutons "to adminPage" et "to userPage" seront affichés en fonction des informations de connexion.

** Afficher le code pour "home.html" **

home.html


<!--réduction-->
<body>
	<h1>Home</h1>

	<div sec:authorize="hasRole('ADMIN')">				<!--Postscript(1) ROLE_Afficher uniquement les utilisateurs ADMIN-->
		<form method="get" th:action="@{/adminPage}">
			<input type="submit" value="Vers AdminPage">
		</form>
	</div>

	<div sec:authorize="hasRole('USER')">				<!--Postscript(2) ROLE_Afficher uniquement les utilisateurs USER-->
		<form method="get" th:action="@{/userPage}">
			<input type="submit" value="Vers UserPage">
		</form>
	</div>

	<div sec:authorize="isAuthenticated()">				<!--Postscript(3)Afficher uniquement les utilisateurs connectés-->
		<form method="post" th:action="@{/logout}">
			<input type="submit" value="Se déconnecter">
		</form>
	</div>
</body>
</html>
article La description
(1) 「ROLE_Seuls les utilisateurs qui ont "ADMIN" sont affichés.
(2) 「ROLE_Seuls les utilisateurs qui ont "USER" sont affichés.
(3) Vous êtes connecté(Agréé)Seul l'utilisateur est affiché.

3-7. Hachage du mot de passe

3-7-1. Aperçu du hachage

Le hachage est la conversion d'une chaîne en une autre valeur (valeur de hachage) à l'aide d'un algorithme spécifique (fonction de hachage). Il est souvent utilisé pour stocker les mots de passe. La différence entre le chiffrement et le hachage est de savoir s'il peut être restauré (déchiffré) à sa valeur d'origine. Les valeurs chiffrées peuvent être déchiffrées, mais les valeurs hachées ne peuvent pas être déchiffrées </ font>. Par conséquent, il est extrêmement difficile pour un tiers de déterminer les données d'origine à partir de la valeur de hachage </ font>. Spring Security recommande d'utiliser BCryptPasswordEncoder sauf si vous avez des exigences spécifiques pour le hachage.

Si vous souhaitez en savoir plus sur le hachage, veuillez vous référer à la page suivante. [Connaissances de base] Qu'est-ce que le cryptage? Qu'est-ce que le hachage? Comment éviter les fuites de mot de passe?

3-7-2. Création d'un programme de hachage de mot de passe

Je pense qu'il est préférable de créer le programme suivant avec un projet nouvellement créé. (Spring Security doit toujours être défini comme une dépendance dans le nouveau projet.) Vous pouvez créer un programme avec un projet existant, mais l'auteur pense qu'il est préférable de créer un nouveau projet car plusieurs fonctions principales peuvent être créées dans le même système. Remplacez la valeur rawPassword (admin dans votre code) par la valeur que vous souhaitez définir comme mot de passe.

** Afficher le code pour "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) {
		//Entrez le mot de passe que vous souhaitez hacher
		String rawPassword = "admin";
		
		//Mot de passe de hachage
		String password = getEncodePassword(rawPassword);
		
		//Afficher la valeur hachée
		System.out.println(password);
	}

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

3-7-3. Essayez de hacher les mots de passe

Exécutez le programme créé dans [3-7-2. Création d'un programme de hachage de mot de passe](# 3-7-2-Créer un programme de hachage de mot de passe) dans une application Java.

La valeur affichée sur la console est le mot de passe haché.

image

4. Mise en œuvre de la fonction d'authentification / d'autorisation pour les affaires réelles

4-1. Fonction d'authentification pour les affaires réelles

Présentation de la mise en œuvre de l'authentification multifacteur (MFA). Ces dernières années, de nombreux systèmes ont introduit l'authentification multifacteur pour améliorer la sécurité. De plus, étant donné que MFA est installé dans beaucoup de nos systèmes, j'aimerais écrire un article sur la façon d'implémenter MFA en tant qu'implémentation pratique. La mise en œuvre de MFA sera un peu longue et sera expliquée en détail dans l'article ci-dessous. Il s'agit d'un article d'implémentation de MFA utilisant un mot de passe et un mot de passe à usage unique. En cours de création (veuillez patienter un peu plus longtemps)

4-2. Fonction d'autorisation pour les affaires réelles

Après avoir entendu parler du système actuel de l'entreprise, l'un d'entre eux était de «stocker les informations de contrôle d'accès dans la base de données et d'effectuer le traitement des autorisations». (Dans [3. Implémentation de la fonction d'authentification / autorisation simple](# 3-Implémentation de la fonction d'authentification / d'autorisation simple), le traitement des autorisations a été effectué à l'aide d'annotations.) Je voudrais écrire un article sur la façon de mettre en œuvre le traitement des autorisations basé sur les informations de contrôle d'accès stockées dans cette base de données en tant qu'implémentation pour une entreprise réelle.

4-2-1. Avantages de la mise en œuvre

Dans l'implémentation simple, la fonction d'autorisation a été implémentée en utilisant @ PreAuthorize. Cependant, dans un système réel, il semble que la fonction d'autorisation soit souvent implémentée à l'aide de DB. La raison en est que gère facilement les pages qui permettent d'accéder à Role </ font>. Lorsque @ PreAuthorize est utilisé, des annotations sont ajoutées pour chaque contrôleur et méthode, il est donc distribué dans divers fichiers et il devient difficile de savoir quelle page est autorisée à quel rôle. Cela peut entraîner une très mauvaise visibilité et une inefficacité lors de la relecture ultérieure du code ou de la modification du système. D'autre part, en créant le tableau suivant dans DB et en gérant les informations de contrôle d'accès uniquement dans DB, il est beaucoup plus facile de gérer les rôles et les pages qui permettent l'accès </ font> > Devient. Voici un exemple d'une partie du tableau utilisée cette fois. Le «*» dans le rôle est un caractère générique, ce qui signifie accorder des autorisations à tous les rôles (y compris les utilisateurs non authentifiés). Cela signifie également autoriser / adminPage à ʻADMIN Role et / userPage à ʻUSER Role.

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

4-2-2. Aperçu de la fonction d'autorisation pour les activités réelles à mettre en œuvre

Les transitions de page sont les mêmes que dans l'implémentation simple, comme illustré dans la figure ci-dessous.

image

La partie entourée de bleu dans la figure ci-dessous montre le processus d'approbation des affaires réelles. Créez un processus de vote, un service et un DAO dans le processus d'autorisation. La fonction d'autorisation est réalisée en interrogeant la base de données pour voir si la page (URL) à laquelle les informations de l'utilisateur client (rôle) essaient d'accéder est autorisée.

image

Le déroulement du traitement des autorisations est le suivant. (Le numéro de traitement correspond au numéro de la figure ci-dessus)

  1. "FilterSecurityInterceptor" appelle le processus d'autorisation de "Affirmative Based" qui implémente ʻAccessDecisionManager` ** (* 1) **.
  2. "Affirmative Based" appelle le processus de vote de "MyVoter" qui implémente ʻAccessDecisionVoter ** (* 2) **. (Dans "Affirmative Based", il est possible de définir plusieurs classes d'implémentation de ʻAccessDecisionVoter et d'appeler chaque processus de vote, mais cette implémentation ne définit que "MyVoter".)
  3. "MyVoter" obtient le rôle du client et l'URL demandée, et appelle le "AuthorizationService" qui détermine l'autorisation d'accès.
  4. «Service d'autorisation» appelle «Dao».
  5. "Dao" demande à la base de données si la combinaison du rôle client et de l'URL demandée existe et, si elle existe, la convertit en une instance d'entité appelée "Autorisation d'accès" et la renvoie au "Service d'autorisation".
  6. «Service d'autorisation» renvoie l'autorisation d'accès («true») si les informations d'autorisation d'accès (autorisation d'accès) existent, et le refus d'accès («false») si elles n'existent pas, à «Mon électeur».
  7. Pour "MyVoter", accordez (ʻACCESS_GRANTED) lorsque l'autorisation d'accès (true) est renvoyée de" MyVoter ", et refusez (ʻACCESS_DENIED) lorsque l'accès refusé ( false) est renvoyé "AffirmativeBased Retourner à. (Détails ** (* 2) **)
  8. "Affirmative Based" renvoie l'autorisation d'accès si le résultat du vote de "MyVoter" est accordé (ʻACCESS_GRANTED), et lève une exception ʻAccessDeniedException si elle est refusée (ʻACCESS_DENIED`). (Détails ** (* 1) **)
  9. "FilterSecurityInterceptor" est "AffirmativeBased", et si l'accès est autorisé, le processus est envoyé au filtre suivant, et si l'accès est refusé, ʻAccessDeniedException est renvoyé. ʻAccessDeniedException est intercepté par le "Filtre de traduction d'exception".

(※1) AccessDecisionManager Une interface qui vérifie si vous avez accès à la ressource à laquelle vous essayez d'accéder. Il existe trois types de classes d'implémentation fournies par Spring Security, mais toutes appellent la méthode vote de l'interface appelée ʻAccessDecisionVoter pour déterminer s'il faut accorder des droits d'accès. ʻAccessDecisionVoter vote pour "accorder", "refuser" ou "retirer", et la classe d'implémentation de ʻAccessDecisionManager agrège les résultats du vote pour déterminer le droit d'accès final. S'il détermine que vous n'avez pas de droits d'accès, il lèvera ʻAccessDeniedException et refusera l'accès. Le tableau ci-dessous montre les classes d'implémentation fournies par Spring Security.

Classe d'implémentation La description
AffirmativeBased AccessDecisionVoterVoterAccorder l’accès lors du vote d’une «subvention»Classe d'implémentation.
デフォルトで使用されるClasse d'implémentation.
ConsensusBased TousAccessDecisionVoterVoterDonner l'accès lorsque le nombre de votes "accordés" est élevéClasse d'implémentation.
UnanimousBased AccessDecisionVoterVoterAucun droit d'accès lorsqu'un seul "refus" est votéClasse d'implémentation.

(※2) AccessDecisionVoter ʻAccessDecisionVoter est une interface pour voter d'accorder ou non l'accès en se référant à la politique d'accès spécifiée pour la ressource à laquelle vous essayez d'accéder. ʻAccessDecisionVoter vote pour "accorder", "rejeter" ou retirer. Le tableau ci-dessous présente les principales classes d'implémentation fournies par Spring Security.

Classe d'implémentation La description
WebExpressionVoter Spring Expression Language (SpEL)Informations d'identification de l'utilisateur utilisant(Authentication)Et demander des informations(HttpServletRequest)Une classe d'implémentation qui fait référence et vote.
RoleVoter Une classe d'implémentation qui fait référence au rôle et aux votes de l'utilisateur.
RoleHierarchyVoter Une classe d'implémentation qui vote en se référant au rôle en couches que possède l'utilisateur.
AuthenticatedVoter Une classe d'implémentation qui vote en se référant à l'état d'authentification.

Dans cette implémentation, une nouvelle classe appelée "MyVoter" qui implémente ʻAccessDecisionVoter est créée, et seul ce "MyVoter" est défini pour le processus de vote appelé par ʻAffirmativeBased.

4-2-3. Structure du projet de la fonction d'autorisation pour les activités réelles

Sur la base de ce qui a été implémenté dans [3. Implémentation de la fonction d'authentification / autorisation simple](# 3-Implémentation de la fonction d'authentification / autorisation simple), mettez en surbrillance les fichiers à ajouter en rouge et les fichiers à ajouter ou modifier du code en bleu. Je suis.

image

4-2-4. Base de données H2 (code ajouté)

** Afficher le code pour "schema.sql" **

schema.sql


/*Ajoutez le code suivant à la fin du fichier*/
CREATE TABLE IF NOT EXISTS access_authorization (
  rolename VARCHAR(10) NOT NULL,
  uri VARCHAR(255) NOT NULL,
  PRIMARY KEY(rolename, uri)
);
** Afficher le code pour "data.sql" **

data.sql


/*Ajoutez le code suivant à la fin du fichier*/
/*Toutes les autorisations de rôle*/
INSERT INTO access_authorization(rolename, uri) VALUES ('*', '/loginForm');
INSERT INTO access_authorization(rolename, uri) VALUES ('*',  '/accessDeniedPage');
INSERT INTO access_authorization(rolename, uri) VALUES ('*', '/logout');

/*Autorisations de rôle ADMIN*/
INSERT INTO access_authorization(rolename, uri) VALUES ('ADMIN', '/home');
INSERT INTO access_authorization(rolename, uri) VALUES ('ADMIN', '/adminPage');

/*Autorisations du rôle USER*/
INSERT INTO access_authorization(rolename, uri) VALUES ('USER',  '/home');
INSERT INTO access_authorization(rolename, uri) VALUES ('USER',  '/userPage');

Un roleName de "*" est un caractère générique qui signifie que accorde l'accès à tous les rôles </ font>.

4-2-5. Contrôleur (correction de code)

image
Commentez «@ PreAuthorize» inutile car l'autorisation est définie dans la base de données.
** Afficher le code pour "AdminPageController.java" **

AdminPageController.java


/*réduction*/
	@GetMapping("/adminPage")
	// @PreAuthorize("hasRole('ROLE_ADMIN')")Commenter
	public String adminPage() {
/*réduction*/
** Afficher le code pour "UserPageController.java" **

UserPageController.java


/*réduction*/
	@GetMapping("/userPage")
	// @PreAuthorize("hasRole('ROLE_USER')")Commenter
	public String userPage() {
/*réduction*/

4-2-6. Entité (ajouter un fichier)

image
** Afficher le code pour "AccessAuthorization.java" **

AccessAuthorization.java


package com.example.demo.entity;

import java.io.Serializable;

public class AccessAuthorization implements Serializable {
	String roleName;	//Accès dans H2DB_De la table d'autorisation"rolename"Champ à stocker
	String uri;			//Accès dans H2DB_De la table d'autorisation"uri"Champ à stocker

	/**
	 * 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 (ajouter un fichier)

Accédez à la base de données à l'aide de Spring JDBC.

image
** Afficher le code pour "AccessAuthorizationDao.java" **

AccessAuthorizationDao.java


package com.example.demo.repository;

import com.example.demo.entity.AccessAuthorization;

public interface AccessAuthorizationDao {
	AccessAuthorization find(String roleName, String uri);
}
** Afficher le code pour "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;
	}

	/**
	 *Exécutez l'instruction SELECT avec roleName et uri comme conditions de recherche pour rechercher s'ils sont enregistrés dans la base de données.
	 * @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 = ?";

		//Obtenez un utilisateur
		Map<String, Object> result = jdbcTemplate.queryForMap(sql, roleName, uri);

		//Classe d'entité(Type d'utilisateur)Conversion en
		AccessAuthorization auth = convMapToAccessAuthorization(result);

		return auth;
	}

	/**
	 *Résultat de l'exécution de l'instruction SQL SELECT(Map<String, Object>)Pour accéder au type d'autorisation
	 * @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 (ajouter un fichier)

image
** Afficher le code pour "AuthorizationService.java" **

AuthorizationService.java


package com.example.demo.service;

public interface AuthorizationService {
	boolean isAuthorized(String roleName, String uri);
}
** Afficher le code pour "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;
    }

    /**
     *Détermine si la combinaison RoleName et URI passée dans l'argument est autorisée.
     * @param roleName
     * @param uri
     * @return boolean
     */
    @Override
    public boolean isAuthorized(String roleName, String uri) { // ---(1)Méthode pour déterminer si l'accès est autorisé

        if (StringUtils.isEmpty(roleName)) {
            throw new IllegalArgumentException("RoleName est vide.");
        }

        if (StringUtils.isEmpty(uri)) {
            throw new IllegalArgumentException("L'URI est vide.");
        }

        //Obtenir une exception AccessAuthorization se produit s'il n'y a pas d'AccessAuthorization
        try {
            AccessAuthorization auth = authDao.find(roleName, uri); // ---(2)Obtenir une instance AccessAuthorization

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

            } else {            // ---(4)Accès refusé
                return false;

            }
        } catch (EmptyResultDataAccessException e) { // ---(5)Accès refusé
            return false;
        }
    }
}
article La description
(1) Rôle de l'utilisateur Nom et chemin d'accès(URI)Est une méthode qui détermine si l'accès est autorisé.
(2) Passez le roleName et l'URI à Dao et devenez une entitéAccessAuthorizationObtenez une instance de.
(3) AccessAuthorizationSi vous pouvez obtenir une instance de, déterminez que l'accès est autorisé ettruerends le.
(4) Depuis DaoAccessAuthorizationImpossible d'obtenir une instance denullSi est retourné, il est jugé que l'accès est refusé et l'accès est refusé.falserends le.

4-2-9. Électeur (ajouter un fichier)

image
** Afficher le code pour "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)Méthode pour déterminer si le vote est requis ou non
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {				// ---(1)Méthode pour déterminer si le vote est requis ou non
		return true;
    }

    @Override
    public int vote(Authentication authentication, FilterInvocation filterInvocation,
    		Collection<ConfigAttribute> attributes) { 		// ---(2)Une méthode pour voter pour accorder l'accès

    	HttpServletRequest request = filterInvocation.getHttpRequest();	// --- (3)Obtenir HttpServletRequest
    	String uri = request.getRequestURI();							// --- (4)Obtenir l'URI de la demande

    	if(authorizationService.isAuthorized("*", uri)) {	// --- (5)Déterminer si tous les rôles sont autorisés à accéder
        	return ACCESS_GRANTED;
        }

		Object principal = authentication.getPrincipal();	// --- (6)Acquisition des informations d'identification de l'utilisateur

		if (!principal.getClass().equals(AccountUserDetails.class)) { // ---(7)Déterminer si les informations d'identification acquises sont les détails de l'utilisateur du compte
			return ACCESS_DENIED;
		}

		String roleName = ((AccountUserDetails) principal).getUser().getRoleName(); // ---(8)Obtenir le rôle de l'utilisateur

		if(authorizationService.isAuthorized(roleName, uri)) { // ---(9)Déterminer si le rôle acquis est autorisé
        	return ACCESS_GRANTED;
        }

    	return ACCESS_DENIED;
    }
}
Numéro d'article La description
(1) Une méthode qui fait référence à la valeur de l'argument pour déterminer si le vote est nécessaire ou non
(2) Une méthode pour voter pour accorder l'accès(Le détail est,4-2-2.Vue d'ensemble de la fonction d'autorisation pour les activités réelles à mettre en œuvre)
(3) HttpServletRequestObtenir.
FilterInvocationEstHttpServletRequestOuHttpServletRequestContient les objets associés aux filtres HTTP tels que.
(4) HttpServletRequestObtenez l'URI à partir duquel vous essayez d'accéder.
(5) Déterminez si l'URI acquis est autorisé à accéder à tous les rôles.
(6) Acquérir les informations d'identification de l'utilisateur accédant
(7) Les informations d'identification acquises sontAccountUserDetailsDéterminez s'il s'agit d'une classe.
Si elles ne sont pas authentifiées, les informations d'identification sont acquises par la classe String, de sorte qu'une erreur se produira à moins que ce processus de détermination ne soit effectué.
(8) AccountUserDetailsObtenez le rôle de l'utilisateur à partir d'une instance de.
(9) Déterminez si le rôle de l'utilisateur et le chemin d'accès auquel vous essayez d'accéder sont autorisés.

4-2-10. JavaConfig (modification du code)

image

Définissez le "MyVoter" créé pour voter pour les droits d'accès.

** Afficher le code pour "WebSecurityConfig.java" **

WebSecurityConfig.java


/*réduction*/
@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 est utilisé pour le traitement de l'approbation, et MyVoter est utilisé pour le traitement du vote.
	} //Postscript

/*réduction*/

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //Paramètres d'autorisation
        http.exceptionHandling()
        		.accessDeniedPage("/accessDeniedPage")
        	.and()
        	.authorizeRequests()
				.antMatchers("/**").authenticated() //Réparer
					.accessDecisionManager(createAccessDecisionManager()); //Postscript---(2)Appliquer le processus d'autorisation pour tous les accès

/*réduction*/
Numéro d'article La description
(1) AccessDecisionManagerEn tant que classe d'implémentation deAffirmativeBasedPostuler.
AccessDecisionVoterEn tant que classe d'implémentation deMyVoterPostuler.
AffirmativeBasedAu moment de l'instanciation deAccessDecisionVoterPlusieurs processus de vote peuvent être définis en passant des instances de.
(2) Traitement des autorisations pour tous les accès(MyVoterProcessus de vote)Est configuré pour être exécuté.

5. Résumé

De nos jours, les questions de sécurité sont fréquemment couvertes par l'actualité. Par conséquent, assurez-vous de mettre en œuvre des mesures de sécurité dans votre application Web. Si vous développez une application Web à l'aide de Spring Framework, vous devez tirer parti de Spring Security pour les mesures de sécurité. Vous pouvez introduire des mesures de sécurité simples et robustes plutôt que des mesures de sécurité auto-conçues. D'un autre côté, d'un point de vue pratique, il est nécessaire d'implémenter une partie de Spring Security à étendre en raison d'exigences telles que vouloir faire référence à la DB dans le processus d'autorisation, mais je pense que cet article a montré un peu comment l'étendre. .. L'idée que «sur la base d'un produit standard, des fonctions supplémentaires nécessaires à l'entreprise et à l'exploitation sont mises en œuvre en plus» est importante dans les affaires réelles, et l'auteur lui-même peut apprendre à travers des auditions et la rédaction d'articles. J'ai fait. Dans cet article, Spring Security a implémenté la «fonction d'authentification» et la «fonction d'autorisation», qui sont les fonctions de base des mesures de sécurité. D'autres fonctionnalités de sécurité sont également fournies par Spring Security. J'ai encore beaucoup de choses que je ne sais pas, alors je veux apprendre.

prime

Ceci est un article écrit par un nouveau venu (ma synchronisation) dans la section suivante. Si vous souhaitez introduire SSO (authentification unique), veuillez lire l'article suivant. Explique la procédure de liaison de Keycloak et d'Azure AD. Essayez la liaison d'identifiant externe à l'aide de Keycloak

Matériel de référence

Recommended Posts