[JAVA] Neuer Mitarbeiter hat versucht, mit Spring Security eine Authentifizierungs- / Autorisierungsfunktion von Grund auf neu zu erstellen

Wenn Sie schnell und ohne lange Geschichte Code schreiben möchten, überspringen Sie [1. Einführung](Nr. 1 - Einführung). Für Frühlingsanfänger gehen Sie zu [2. Projekt erstellen](# 2 - Projekt erstellen). Für diejenigen, die eine einfache Authentifizierungs- / Autorisierungsfunktion implementieren möchten, siehe [3. Implementierung einer einfachen Authentifizierungs- / Autorisierungsfunktion](# 3-Implementierung einer einfachen Authentifizierungs- / Autorisierungsfunktion) Diejenigen, die die Authentifizierungs- / Autorisierungsfunktion für das tatsächliche Geschäft implementieren möchten, sollten mit dem Lesen von [4. Implementierung der Authentifizierungs- / Autorisierungsfunktion für das tatsächliche Geschäft] beginnen (# 4 - Implementierung der Authentifizierungs- / Autorisierungsfunktion für das tatsächliche Geschäft).

1. Zuallererst

1-1. Hintergrund

Zunächst werde ich die Authentifizierung / Autorisierung erläutern. Die einfache Erklärung lautet wie folgt. (Referenzseite für diejenigen, die mehr wissen möchten: Vertraute Authentifizierung und Autorisierung)

--Authentifizierung Überprüfen Sie, mit wem Sie kommunizieren </ font>.

--Genehmigung Gewähren Sie die Ressourcenzugriffsberechtigung für bestimmte Bedingungen wie Bildschirmzugriffsberechtigung und Verarbeitungsberechtigung </ font>.

Wenn Sie diese Authentifizierungs- / Autorisierungsfunktion in einer mit Spring Framework erstellten Webanwendung implementieren, kann sie mithilfe von Spring Security problemlos implementiert werden. Es gibt viele Artikel, die Authentifizierungs- / Autorisierungsfunktionen mit Spring Security implementieren, aber ich bin der Meinung, dass es viele Artikel wie die folgenden gibt.

  • Die Datenbank wird nicht verwendet.
  • Der Benutzername und das Passwort werden direkt in den Code geschrieben.
  • Das Passwort ist nicht gehasht.
  • Es wird nur der Quellcode des wichtigen Teils im Zusammenhang mit der Authentifizierung / Autorisierung aufgelistet (dh es ist schwer zu lesen).

Datenbanknutzung und Passwort-Hashing sind jedoch an der Tagesordnung </ font>. Als ich ein Websystem mit Authentifizierungs- / Autorisierungsfunktionen erstellte, hatte ich große Probleme, viele Artikel zu lesen und den Quellcode auf GitHub zu dekodieren. Basierend auf dieser Erfahrung benötigen selbst Anfänger in Bezug auf Spring Security und Authentifizierung / Autorisierung eine Datenbank sowie Hashing und Artikel, die die Funktion implementieren können, indem sie diese </ font> lesen. Ich dachte und schrieb diesen Artikel. In dem vor Ort verwendeten System fragen Sie sich außerdem, wie der Quellcode und die Datenbanktabellen für die Authentifizierung / Autorisierung implementiert sind. Daher habe ich diesen Artikel mit der Absicht geschrieben, zu untersuchen, welche Art von Authentifizierungs- / Autorisierungsfunktion implementiert ist, indem ich auf , unser Beispiel für die Systemkonstruktion </ font>, verwiesen habe.

1-2. Zweck

――Sie müssen den Quellcode von GitHub nicht lesen, sondern nur diesen Artikel lesen, und selbst Anfänger können Authentifizierungs- / Autorisierungsfunktionen mit Spring Security implementieren. ――Wir fassen den Inhalt der Untersuchung und Anhörung zur Authentifizierung und Autorisierung unter Bezugnahme auf unser Beispiel für den Systemaufbau als Methode zur Implementierung der Authentifizierungs- und Autorisierungsfunktion für das tatsächliche Geschäft zusammen.

  • Bitte verzeihen Sie, dass der tatsächliche Fall aufgrund der Auswahl der Informationen und der Abstraktion des Falls bei der Erstellung des Artikels nicht mit dem tatsächlichen Fall identisch ist.

1-3. Ziel

  • Personen, die Authentifizierungs- / Autorisierungsfunktionen mithilfe von Spring Security implementieren möchten
  • Personen, die Authentifizierungs- / Autorisierungsfunktionen in der tatsächlichen Arbeit entwickeln
  • Personen, die den Code auf GitHub nicht lesen und durch Lesen des Artikels implementieren möchten

1-4. Voraussetzungen

1-5. Ziele dieses Artikels

――Zwei Implementierungen, einfache Implementierung und Implementierung für das tatsächliche Geschäft.

** Einfache Implementierung ** </ font> ist eine Implementierung der Authentifizierungs- / Autorisierungsverarbeitung für Anfänger. Nur der Implementierungsteil des Spring Security-Referenzmaterials wird vom Autor zusammengefasst. Es ist perfekt für Anfänger von Spring Security, um ein besseres Verständnis zu erlangen, indem sie es auf ihrem eigenen Gerät ausführen. ** Implementierung für das eigentliche Geschäft ** </ font> basiert auf internen Anhörungen darüber, wie die Authentifizierungs- / Autorisierungsverarbeitung in einem tatsächlichen System realisiert wird. Wir werden eine Authentifizierungs- / Autorisierungsverarbeitung implementieren, die dem tatsächlichen System nahe kommt. Es ist eine Implementierung für diejenigen, die die Authentifizierungs- / Autorisierungsverarbeitung im Geschäftsleben entwickeln.

--Die in diesem Artikel vorgestellten Einstellungen / Funktionen sollten als eine der Einstellungen / Funktionen verstanden werden, die bei der Anwendung eines tatsächlichen Projekts berücksichtigt werden müssen.

Die folgenden drei Ziele gelten sowohl für die einfache Implementierung als auch für die praktische Implementierung </ font>.

  • Sie können es implementieren, indem Sie den Quellcode dieses Artikels kopieren. --Verwenden Sie die H2-Datenbank und hashen Sie das Passwort.
  • Realisieren Sie die Authentifizierungs- / Autorisierungsfunktion, mit der Seitenübergänge durchgeführt werden (siehe Abbildung unten). (Der Seitenübergang ist sowohl für die einfache Implementierung als auch für die Implementierung für das tatsächliche Geschäft gleich.)
image

1-6 Entwicklungsumgebung / Ausführungsumgebung

  • 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 Auch wenn Sie den Quellcode nicht auf GitHub lesen, können Sie die Funktion implementieren, indem Sie diesen Artikel lesen. Für diejenigen, die sagen "Es ist schneller, GitHub zu lesen", werde ich den Quellcode auf GitHub veröffentlichen.

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

--BasicAuth ist der Quellcode der einfachen Implementierungsauthentifizierungs- / Autorisierungsfunktion.

  • Erweiterte Autorisierung ist der Quellcode der Autorisierungsfunktion für den geschäftlichen Gebrauch

Es ist geworden.

2. Erstellen Sie ein Projekt

2-1. Ändern Sie die Perspektive in Java EE

"Fenster" -> "Perspektive" -> "Offene Perspektive" -> "Andere"

image

Wählen Sie zum Öffnen "Java EE"

image

2-2. Erstellen eines neuen Projekts

"Datei" -> "Neu" -> "Spring Starter Project"

image

Ändern Sie den Wert jedes Elements wie unten gezeigt und klicken Sie auf "Weiter".

Artikel Wert
Name Beliebiger Wert
Schimmel Gradle (Buildship 3.x)
Java-Version 11
Ergebnisse Beliebiger Wert
image

Drücken Sie "Fertig stellen"

image

2-3. Ändern Sie application.properties in application.yml (optional).

Da Eigenschaften und yml nur geringfügig unterschiedlich beschrieben werden, gibt es kein Problem, auch wenn sie als "application.properties" belassen werden. Ich mag, ob ich es ändern soll. In diesem Artikel basiert der Quellcode jedoch auf application.yml.

image

3. Implementierung einer einfachen Authentifizierungs- / Autorisierungsfunktion

3-1. Zu erstellendes Systemabbild und Projektkonfiguration

Die folgende Abbildung zeigt ein Bild des erstellten Websystems. Der detaillierte Verarbeitungsablauf für die Authentifizierungs- und Autorisierungsverarbeitung lautet [3-5-1. Überblick über die Spring Security-Authentifizierungsfunktion](# 3-5-1-spring-security-Überblick über die Authentifizierungsfunktion) und [3-6-1. Spring Übersicht über die Sicherheitsautorisierungsfunktion](# 3-6-1-spring-Sicherheitsübersicht über die Autorisierungsfunktion).

image

Die folgende Abbildung zeigt die Projektstruktur des Systems.

image

3-2. Abhängigkeitseinstellungen

Schreiben Sie build.gradle, um die Abhängigkeiten festzulegen.

** Code für "build.gradle" anzeigen **

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-Datenbankeinstellungen

3-3-1. Übersicht über die H2-Datenbank (H2DB)

H2DB ist eine in Java geschriebene In-Memory-Datenbank. Die Merkmale von H2DB sind die folgenden drei.

  1. Leicht und einfach zu installieren
  2. Schneller Betrieb
  3. Sie können zum Verhalten anderer Datenbanken wechseln, indem Sie einen Modus wie den Oracle-Modus oder den PostgreSQL-Modus festlegen.

Da es sich um eine In-Memory-Datenbank handelt, gehen die Daten nach Abschluss des Vorgangs verloren. Es kann auch als eingebettete Datenbank verwendet werden, diesmal wird es jedoch installiert, um den Inhalt der Datenbank für das tatsächliche Geschäft in [4. Implementierung der Authentifizierungs- / Autorisierungsfunktion für das tatsächliche Geschäft] zu verbessern (# 4 - Implementierung der Authentifizierungs- / Autorisierungsfunktion für das tatsächliche Geschäft). Wird als Speicherdatenbank verwendet. Da es sich im Arbeitsspeicher befindet, muss der Anfangswert von H2DB durch Ausführen einer SQL-Anweisung bei jedem Start der Verarbeitung festgelegt werden. Im Fall von Spring Boot kann die SQL-Anweisung durch Beschreiben der SQL-Anweisung in "schema.sql" und "data.sql" bei jedem Start des Prozesses automatisch ausgeführt werden. </ font>

3-3-2. H2 datenbankbezogene Projektstruktur

image

3-3-2. Application.yml (zusätzlicher Code)

Beschreiben der H2DB-Einstellungen in application.yml.

** Code für "application.yml" anzeigen **

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 (Datei hinzufügen)

Beschreiben der Tabellenstruktur der Datenbank in SQL.

** Code für "schema.sql" anzeigen **

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 (Datei hinzufügen)

Fügen Sie Daten in H2DB ein. Die 60-stellige Zeichenfolge, die mit "\ $ 2a \ $ 10 \ $" beginnt, ist das Hash-Passwort. Details werden in [3-7. Hashing-Passwörter](# 3-7-Hashing-Passwörter) erläutert. Bitte kopieren Sie diese und fügen Sie sie hier ein.

Einfaches Passwort Hashed Passwort
admin $2a$10$bCR1jXhdqbh1oC8ckXplxePYW5Kyb/VjN28MZx2PwXf1ybzLIFUQG
user $2a$10$yyT1siJCep647RT/I7KjcuUB5noFVU6RBo0FUXUJX.hb2MIlWTbDe
** Code für "data.sql" anzeigen **

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. Implementierung von Anwendungsfunktionen

3-4-1. Übersicht der Anwendungsfunktionen

Erstellen Sie eine Anwendung, die die fünf unten gezeigten Seiten anzeigen und zu diesen wechseln kann.

image

Die Anwendungsverarbeitung zur Realisierung dieses Seitenübergangs ist der Teil, der in der folgenden Abbildung von hellgrün umgeben ist. Um sich auf die Authentifizierungs- / Autorisierungsverarbeitung zu konzentrieren, erstellen Sie nur Controller und View für die Anwendungsverarbeitung. (Service, Dao usw. werden nicht erstellt.)

image

3-4-2. Projektstruktur in Bezug auf Anwendungsfunktionen

image

3-4-3. Controller (Datei hinzufügen)

image
** Code für "LoginController.java" anzeigen **

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";
	}
}
** Code für "HomeController.java" anzeigen **

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";
	}
}
** Code für "AdminPageController.java" anzeigen **

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";
	}
}
** Code für "UserPageController.java" anzeigen **

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";
	}
}
** Code für "AccessDeniedPageController.java" anzeigen **

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. Ansicht (Datei hinzufügen)

Erstellen Sie eine HTML-Datei. Der Quellcode verwendet Thymeleaf als Template-Engine. Wenn Sie keinen "Vorlagen" -Ordner haben, fügen Sie einen unter src / main / resources hinzu.

image
image
** Code für "login.html" anzeigen **

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>Einloggen</title>
</head>
<body>
	<h1>Einloggen</h1>
	<div th:if="${param.error}" > <!-- (1)Beurteilung der Anforderungsparameter-->
		<div>
Benutzername oder Passwort ist unterschiedlich.<!-- (2)Authentifizierungsfehlermeldung-->
		</div>
	</div>

	<form method="post" th:action="@{/authenticate}"> <!-- (3)Geben Sie den Anmeldeprozesspfad an-->
		<label>UserName:</label>
		<input type="text" id="userName" name="userName"> <!-- (4)Eingabe des Benutzernamens-->
		<br>
		<label>Password:</label>
		<input type="password" id="password" name="password"> <!-- (5)Abschnitt zur Passworteingabe-->
		<br>
		<input type="submit" id="submit" value="Einloggen">
	</form>
</body>
</html>
Artikelnummer Erläuterung
(1) Beurteilen Sie die im Anforderungsparameter festgelegte Fehlermeldung.
3-5-6. JavaConfig(Datei hinzufügen) "WebSecurityConfig.java"ImÄndern Sie den Beurteilungsprozess gemäß dem in "failUrl" festgelegten Wert.Bitte beachten Sie, dass dies erforderlich ist.
(2) Ausnahmemeldung, die ausgegeben werden soll, wenn ein Authentifizierungsfehler auftritt
(3) Geben Sie im Aktionsattribut des Formulars das Übergangsziel für die Authentifizierungsverarbeitung an.
Der Übergangszielpfad ist3-5-6. JavaConfig(Datei hinzufügen) "WebSecurityConfig.java"Im"Passen Sie den in loginProcessingUrl angegebenen Wert an. ”Es gibt Bedarf.
Geben Sie "POST" für die HTTP-Methode an.
In diesem Fall${pageContext.request.contextPath}/Der Authentifizierungsprozess wird durch Zugriff auf die Authentifizierung ausgeführt.
(4) Ein Element, das im Authentifizierungsprozess als "Benutzername" behandelt wird.
Das Namensattribut lautet3-5-6. JavaConfig(Datei hinzufügen) "WebSecurityConfig.java"Im"Passen Sie den in usernameParameter angegebenen Wert an. “Es gibt Bedarf.
(5) Ein Element, das bei der Authentifizierung als "Kennwort" behandelt wird.
Das Namensattribut lautet3-5-6. JavaConfig(Datei hinzufügen) "WebSecurityConfig.java"Im"Passen Sie den von passwordParameter angegebenen Wert an. “Es gibt Bedarf.
image
** Code für "home.html" anzeigen **

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="Zu AdminPage">
	</form>

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

	<form method="post" th:action="@{/logout}"> <!-- (1)Geben Sie den Abmeldeverarbeitungspfad an-->
		<input type="submit" value="Ausloggen">
	</form>
</body>
</html>
Artikelnummer Erläuterung
(1) Geben Sie den Pfad zum Ausführen des Abmeldevorgangs im Aktionsattribut des Formulars an.
Der Abmeldeprozesspfad ist der Standardwert für Spring Security./Geben Sie die Abmeldung an.
Geben Sie "POST" für die HTTP-Methode an.
image
** Code für "adminPage.html" anzeigen **

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>Artikel</th>
			<th>Inhalt</th>
		</tr>
		<tr>
			<td>UserName</td>
			<td><span sec:authentication="principal.username"></span></td> <!-- (1)Benutzername anzeigen-->
		</tr>
		<tr>
			<td>Name</td>
			<td><span sec:authentication="principal.name"></span></td> <!-- (2)Name anzeigen-->
		</tr>
		<tr>
			<td>Role</td>
			<td><span sec:authentication="principal.authorities"></span></td> <!-- (3)Rolle anzeigen-->
		</tr>
	</table>

	<form method="get" th:action="@{/home}">
		<input type="submit" value="Nach Hause">
	</form>
	<form method="post" th:action="@{/logout}">
		<input type="submit" value="Ausloggen">
	</form>
</body>
</html>
Artikelnummer Erläuterung
(1) Verwenden Sie Thymeleaf, um auf das Authentifizierungsobjekt zuzugreifen und den Benutzernamen anzuzeigen.
(2) Verwenden Sie Thymeleaf, um auf das Authentifizierungsobjekt zuzugreifen und den Namen anzuzeigen.
(3) Verwenden Sie Thymeleaf, um auf das Authentifizierungsobjekt zuzugreifen und die Rolle anzuzeigen.
image
** Code für "userPage.html" anzeigen **

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>Artikel</th>
			<th>Inhalt</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="Nach Hause">
	</form>
	<form method="post" th:action="@{/logout}">
		<input type="submit" value="Ausloggen">
	</form>
</body>
</html>
image
** Code für "accessDeniedPage.html" anzeigen **

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>Zugriff verweigert</h1>

	<form method="get" th:action="@{/home}">
		<input type="submit" value="Nach Hause">
	</form>
	<form method="post" th:action="@{/logout}">
		<input type="submit" value="Ausloggen">
	</form>
</body>
</html>

3-5. Implementierung der Authentifizierungsfunktion

3-5-1. Überblick über die Spring Security-Authentifizierungsfunktion

Der in der folgenden Abbildung von Rosa umgebene Teil zeigt die Authentifizierungsfunktion.

image

Der Ablauf des Authentifizierungsprozesses ist wie folgt. (Die Verarbeitungsnummer entspricht der Nummer in der obigen Abbildung)

  1. Rufen Sie den Authentifizierungsprozess von "Authentication Manager" über "Benutzername Passwort Passwort Authentifizierungsfilter" auf.
  2. Rufen Sie unter "AuthenticationManager" den Benutzererfassungsprozess von "AccountUserDetailsService" auf, der "UserDetailsService" ** (* 1) ** erbt.
  3. Rufen Sie "Dao" von "AccountUserDetailsService" auf.
  4. "Dao" konvertiert die von der Datenbank erhaltenen Benutzerinformationen wie Benutzername und Kennwort in eine Entitätsinstanz mit dem Namen "MyUser" und gibt sie an "AccountUserDetailsService" zurück.
  5. "AccountUserDetailsService" konvertiert Benutzerinformationen (MyUser) in eine Instanz von "AccountUserDetails", die "UserDetails" ** (* 2) ** erbt und an "AuthenticationManager" zurückgibt.
  6. Der "Authentication Manager" vergleicht die "AccountUserDetails" mit den vom Client angegebenen Anmeldeinformationen und gibt das Ergebnis an den "UsernamePasswordAuthenticationFilter" zurück.
  7. "UsernamePasswordAuthenticationFilter" empfängt das von "AuthenticationManager" zurückgegebene Authentifizierungsergebnis und steuert die Reaktion auf Authentifizierungserfolg oder Authentifizierungsfehler.

(※1) UserDetailsService Es ist eine Schnittstelle, die für den Erwerb der für den Authentifizierungsprozess und den Benutzerstatus erforderlichen Anmeldeinformationen (Benutzername und Kennwort) verantwortlich ist. (※2) UserDetails Eine Schnittstelle, die Anmeldeinformationen und Benutzerstatus bereitstellt und aus "UserDetailsService" erstellt wurde.

Erstellen Sie bei der Authentifizierungsverarbeitung mit DB die UserDetailsService-Implementierungsklasse (AccountUserDetailsService in diesem Artikel) und die UserDetails-Implementierungsklasse (AccountUserDetails in diesem Artikel) gemäß den Anforderungen der Anwendung. </ font> Muss sein.

3-5-2. Projektkonfiguration in Bezug auf die Authentifizierungsfunktion

image

3-5-3. Entität (Datei hinzufügen)

image
** Code für "MyUser.java" anzeigen **

MyUser.java


package com.example.demo.entity;

import java.io.Serializable;

public class MyUser implements Serializable{
	private String userName;	//In H2DB die Benutzertabelle"username"Feld zum Speichern

	private String password;	//In H2DB die Benutzertabelle"password"Feld zum Speichern

	private String name;		//In H2DB die Benutzertabelle"name"Feld zum Speichern

	private String roleName;	//In H2DB die Benutzertabelle"roleName"Feld zum Speichern

	/**
	 * 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 (Datei hinzufügen)

Greifen Sie mit Spring JDBC auf die Datenbank zu.

image
** Code für "UserDao.java" anzeigen **

UserDao.java


package com.example.demo.repository;

import com.example.demo.entity.MyUser;

public interface UserDao {
	MyUser findUserByUserName(String userName);
}
** Code für "UserDaoImpl.java" anzeigen **

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

	/**
	 *Führen Sie die SELECT-Anweisung mit userName als Suchbedingung aus, um nach in der Datenbank registrierten Benutzern zu suchen.
	 * @param userName
	 * @return User
	 */
	@Override
	public MyUser findUserByUserName(String userName) {
		String sql = "SELECT username, password, name, rolename FROM users WHERE username = ?";

		//Holen Sie sich einen Benutzer
		Map<String, Object> result = jdbcTemplate.queryForMap(sql, userName);

		//Entitätsklasse(Benutzertyp)Umstellung auf
		MyUser user = convMapToUser(result);

		return user;
	}

	/**
	 *Ergebnis der Ausführung der SQL SELECT-Anweisung(Map<String, Object>)Zum Benutzertyp
	 * @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 (Datei hinzufügen)

image
** Code für "AccountUserDetailsService.java" anzeigen **

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)Methode zum Abrufen von Kontoinformationen aus der Datenbank

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

		//Eine Benutzerausnahme abrufen tritt auf, wenn kein Benutzername vorhanden ist
		try {
			//Benutzer abrufen
	    	MyUser myUser = userDao.findUserByUserName(userName);

	    	if (myUser != null) {
	    		return new AccountUserDetails(myUser); // --- (2)Generieren Sie eine Implementierungsklasse für UserDetails

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

		} catch (EmptyResultDataAccessException e) {
			throw new UsernameNotFoundException(userName + "is not found");
		}
	}
}
Artikelnummer Erläuterung
(1) Suchen Sie nach Kontoinformationen aus der Datenbank. Wirf UsernameNotFoundException, wenn keine Kontoinformationen gefunden werden
(2) Wenn die Kontoinformationen gefunden werden, die Implementierungsklasse von UserDetails(AccountUserDetails)Generieren.
image
** Code für "AccountUserDetails.java" anzeigen **

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)Methode zur Rückgabe von MyUser als Entität
		return myUser;
	}

	public String getName() { // --- (2)Methode zur Rückgabe des Namens
		return this.myUser.getName();
	}


	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() { // --- (3)Methode zum Zurückgeben der dem Benutzer erteilten Berechtigungsliste
		return AuthorityUtils.createAuthorityList("ROLE_" + this.myUser.getRoleName());
	}

	@Override
	public String getPassword() { // --- (4)Methode zur Rückgabe des registrierten Passworts
		return this.myUser.getPassword();
	}

	@Override
	public String getUsername() { // --- (5)Methode zur Rückgabe des Benutzernamens
		return this.myUser.getUserName();
	}

	@Override
	public boolean isAccountNonExpired() { // --- (6)Methode zum Ermitteln des Ablaufstatus eines Kontos
		return true;
	}

	@Override
	public boolean isAccountNonLocked() { // --- (7)Methode zum Ermitteln des Sperrstatus eines Kontos
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() { // --- (8)Eine Methode zum Ermitteln des Ablaufstatus von Anmeldeinformationen
		return true;
	}

	@Override
	public boolean isEnabled() { // --- (9)Methode, um festzustellen, ob es sich um einen gültigen Benutzer handelt
		return true;
	}
}
Artikelnummer Erläuterung
(1) Eine Methode, die MyUser zurückgibt, eine Entität.
Bereiten Sie eine Getter-Methode vor, damit nach erfolgreichem Authentifizierungsprozess auf die Kontoinformationen zugegriffen werden kann.
(2) Eine Methode, die den Namen zurückgibt.
(3) Eine Methode, die die dem Benutzer erteilte Berechtigungsliste zurückgibt.
Diese Methode wird im Autorisierungsprozess verwendet. Im Autorisierungsprozess von Spring Security wird "ROLE_Die mit "beginnenden Berechtigungsinformationen werden als Rolle behandelt. Deshalb "ROLLE_"Hinzugefügt.
(4) Eine Methode zum Zurückgeben des registrierten Passworts.
Das von dieser Methode zurückgegebene Kennwort wird zum Vergleich mit dem vom Client angegebenen Kennwort verwendet.
(5) Eine Methode, die einen Benutzernamen zurückgibt.
(6) Eine Methode zum Ermitteln des Ablaufstatus eines Kontos.
Wenn es innerhalb des Ablaufdatums liegt, wird true zurückgegeben, und wenn es abgelaufen ist, wird false zurückgegeben.
In diesem Programm wird der Einfachheit halber nur true zurückgegeben.
(7) Eine Methode zum Ermitteln des Sperrstatus eines Kontos.
Wenn es nicht gesperrt ist, wird ture zurückgegeben, und wenn das Konto gesperrt ist, wird false zurückgegeben.
In diesem Programm wird der Einfachheit halber nur true zurückgegeben.
(8) Eine Methode, die den Ablaufstatus von Anmeldeinformationen bestimmt.
Wenn es innerhalb des Ablaufdatums liegt, wird true zurückgegeben, und wenn es abgelaufen ist, wird false zurückgegeben.
In diesem Programm wird der Einfachheit halber nur true zurückgegeben.
(9) Eine Methode, die bestimmt, ob der Benutzer gültig ist.
Wenn es gültig ist, gibt es true zurück, und wenn es ein ungültiger Benutzer ist, wird false zurückgegeben.
In diesem Programm wird der Einfachheit halber nur true zurückgegeben.

3-5-6. JavaConfig (Datei hinzufügen)

image
** Code für "WebSecurityConfig.java" anzeigen **

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-Passwörter mit dem BCrypt-Algorithmus
		return new BCryptPasswordEncoder(); // --- (2)Verwenden Sie den BCrypt-Algorithmus
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		//Legen Sie den implementierten UserDetailsService in AuthenticationManagerBuilder fest
		auth.userDetailsService(userDetailsService)		// --- (3)Legen Sie den erstellten UserDetailsService fest
				.passwordEncoder(passwordEncoder());	// --- (2)Geben Sie an, wie das Kennwort gehasht werden soll(BCrypt-Algorithmus)
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		//Autorisierungseinstellungen
		http.authorizeRequests()
				.antMatchers("/loginForm").permitAll()	// --- (4) /loginForm ermöglicht den Zugriff aller Benutzer
				.anyRequest().authenticated();			// --- (5) /Fragen Sie nach der Authentifizierung außer loginForm

		//Login-Einstellungen
		http.formLogin()								// --- (6)Aktivieren Sie die Formularauthentifizierung
				.loginPage("/loginForm")				// --- (7)Pfad zum Anzeigen des Anmeldeformulars
				.loginProcessingUrl("/authenticate")	// --- (8)Pfad des Formularauthentifizierungsprozesses
				.usernameParameter("userName")			// --- (9)Benutzername Anfrage Parameter Name
				.passwordParameter("password")			// --- (10)Name des Parameters für die Kennwortanforderung
				.defaultSuccessUrl("/home")				// --- (11)Standardpfad für den Übergang, wenn die Authentifizierung erfolgreich ist
				.failureUrl("/loginForm?error=true");	// --- (12)Pfad zum Übergang, wenn die Authentifizierung fehlschlägt

		//Abmeldeeinstellungen
		http.logout()
				.logoutSuccessUrl("/loginForm")			// --- (13)Pfad zum Übergang, wenn die Abmeldung erfolgreich ist
				.permitAll();							// --- (14)Ermöglichen Sie allen Benutzern den Zugriff
	}
}
Artikelnummer Erläuterung
(1) @EnableWebSecurityWenn angegeben, wird die Bean-Definition der Komponente, die zur Verwendung von Spring Security erforderlich ist, automatisch ausgeführt.
(2) Verwenden Sie den BCrypt-Algorithmus, um das Kennwort-Hashing festzulegen.
(3) AuthenticationManagerBuilderErstellt inUserDetailsServiceEinstellen.
(4) /loginForm ermöglicht den Zugriff aller Benutzer.
(5) /Anders als bei loginForm ist eine Authentifizierung erforderlich.
Nicht authentifizierte Benutzer werden zum Anmeldebildschirm umgeleitet.
Benutzern, die authentifiziert, aber nicht autorisiert sind, wird der Zugriff verweigert.
(6) formLoginDie Formularauthentifizierung wird aktiviert, wenn Sie die Methode aufrufen.
(7) Geben Sie den Pfad an, um das Anmeldeformular anzuzeigen. Wenn ein anonymer Benutzer versucht, auf eine Seite zuzugreifen, für die eine Authentifizierung erforderlich ist, wird er auf den hier angegebenen Pfad umgeleitet.
(8) Geben Sie den Pfad für die Verarbeitung der Formularauthentifizierung an.3-4-4. View(Datei hinzufügen)Einloggen.In HTMLPassen Sie das Aktionsattribut des Formular-Tags anEs gibt Bedarf.
(9) Geben Sie den Anforderungsparameternamen des Benutzernamens an, der der Berechtigungsnachweis ist.3-4-4. View(Datei hinzufügen)Einloggen.In HTMLPassen Sie das Namensattribut des Eingabe-Tags an, um den Benutzernamen einzugebenEs gibt Bedarf.
(10) Geben Sie den Namen des Anforderungsparameters des Kennworts an, das die Anmeldeinformationen enthält.3-4-4. View(Datei hinzufügen)Einloggen.In HTMLPassen Sie das Namensattribut des Eingabe-Tags an, um das Kennwort einzugebenEs gibt Bedarf.
(11) Geben Sie den Standardpfad für den Übergang an, wenn die Authentifizierung erfolgreich ist.
(12) Geben Sie den Pfad an, zu dem der Übergang erfolgen soll, wenn die Authentifizierung fehlschlägt.
(13) Geben Sie den Pfad an, zu dem der Übergang erfolgen soll, wenn die Abmeldung erfolgreich ist.
(14) Ermöglichen Sie allen Benutzern den Zugriff auf Abmeldungen und Pfade, die nach erfolgreichen Abmeldungen übergehen.

3-6. Implementierung der Autorisierungsfunktion

3-6-1. Überblick über die Spring Security-Autorisierungsfunktion

Der in der folgenden Abbildung von Hellblau umgebene Teil zeigt die Berechtigungsfunktion.

image
Im Autorisierungsprozess wird festgelegt, ob die von "Filter Security Interceptor" angeforderte URL zulässig ist. Verwenden Sie "@ PreAuthorize", um die Berechtigungen für jeden Benutzer festzulegen. "@ PreAuthorize" kann für jeden Controller oder jede Methode beschrieben werden.
  • Wenn der Zugriff gewährt wird Weiter zum nächsten Filter.

  • Wenn der Zugriff verweigert wird Der "Filter Security Interceptor" löst eine Ausnahme namens "AccessDeniedException" aus. "Exception Translation Filter" fängt die Ausnahme ab und wenn der Zugriff von einem nicht authentifizierten Benutzer stammt, eine Antwort, die zur Authentifizierung auffordert (Übergang zum Anmeldebildschirm), und wenn der Zugriff von einem authentifizierten Benutzer stammt, eine Antwort, die einen Autorisierungsfehler benachrichtigt (Zugriff verweigert) (Übergang zum Bildschirm) wird zurückgegeben.

3-6-2. Projektstruktur in Bezug auf die Berechtigungsfunktion

Die Datei, zu der der Code hinzugefügt wird, ist blau hervorgehoben.

image

3-6-3. JavaConfig (zusätzlicher Code)

image

Wenn Sie versuchen, auf eine Seite zuzugreifen, die nicht zulässig ist, werden Sie zu einer Seite weitergeleitet, die angibt, dass der Zugriff verweigert wurde.

** Code für "WebSecurityConfig.java" anzeigen **

WebSecurityConfig.java


/*Kürzung*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //Nachtrag--- (1)Aktivieren Sie die Verarbeitung der Methodenautorisierung
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

/*Kürzung*/

	protected void configure(HttpSecurity http) throws Exception {
		//Autorisierungseinstellungen
		http.exceptionHandling()	//Nachtrag
				.accessDeniedPage("/accessDeniedPage")	//Nachtrag--- (2)Pfad zum Übergang, wenn der Zugriff verweigert wird
			.and()					//Nachtrag
			.authorizeRequests()
				.antMatchers("/loginForm").permitAll()
				.anyRequest().authenticated();
/*Kürzung*/
Artikelnummer Erläuterung
(1) @PreAuthorizeOder@PostAuthorizeAktivieren Sie den Methodenautorisierungsprozess mit.
(2) Zugriff verweigert(Autorisierungsfehler)Wenn dies erledigt ist, geben Sie den Pfad an, zu dem der Übergang erfolgen soll.

3-6-4. Controller (zusätzlicher Code)

image

Beschreiben Sie "@ PreAuthorize" in jeder Methode, um die Zugriffsberechtigung festzulegen.

** Code für "AdminPageController.java" anzeigen **

AdminPageController.java


/*Kürzung*/
	@GetMapping("/adminPage")
	@PreAuthorize("hasRole('ROLE_ADMIN')") //Nachtrag--- (1) ROLE_Zugriff nur für ADMIN-Benutzer zulassen
	public String adminPage() {
/*Kürzung*/
Artikel Erläuterung
(1) 「ROLE_Nur Benutzer mit "ADMIN" haben Zugriff.
** Code für "UserPageController.java" anzeigen **

UserPageController.java


/*Kürzung*/
	@GetMapping("/userPage")
	@PreAuthorize("hasRole('ROLE_USER')") //Nachtrag--- (1) ROLE_Erlauben Sie den Zugriff nur USER-Benutzern
	public String userPage() {
/*Kürzung*/
Artikel Erläuterung
(1) 「ROLE_Nur Benutzer mit "USER" haben Zugriff.

3-6-5. Ansicht (zusätzlicher Code)

image

Auf der "Homepage" werden die Schaltflächen "to adminPage" und "to userPage" entsprechend den Anmeldeinformationen angezeigt.

** Code für "home.html" anzeigen **

home.html


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

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

	<div sec:authorize="hasRole('USER')">				<!--(2) ROLE_-->
		<form method="get" th:action="@{/userPage}">
			<input type="submit" value="Addendum-Anzeige nur für ADMIN-Benutzer zu AdminPage Addendum-Anzeige nur für USER-Benutzer zu UserPage">
		</form>
	</div>

	<div sec:authorize="isAuthenticated()">				<!--Nachtrag(3)Nur angemeldete Benutzer anzeigen-->
		<form method="post" th:action="@{/logout}">
			<input type="submit" value="Ausloggen">
		</form>
	</div>
</body>
</html>
Artikel Erläuterung
(1) 「ROLE_Es werden nur Benutzer mit "ADMIN" angezeigt.
(2) 「ROLE_Es werden nur Benutzer mit "USER" angezeigt.
(3) Du bist eingeloggt(Zertifiziert)Es wird nur der Benutzer angezeigt.

3-7. Passwort-Hashing

3-7-1. Überblick über das Hashing

Hashing ist die Konvertierung eines Strings in einen anderen Wert (Hash-Wert) unter Verwendung eines bestimmten Algorithmus (Hash-Funktion). Es wird häufig zum Speichern von Passwörtern verwendet. Der Unterschied zwischen Verschlüsselung und Hashing besteht darin, ob der ursprüngliche Wert wiederhergestellt (entschlüsselt) werden kann. Verschlüsselte Werte können entschlüsselt werden, aber Hash-Werte können nicht entschlüsselt werden </ font>. Daher ist es für einen Dritten äußerst schwierig, die Originaldaten aus dem Hashwert </ font> zu ermitteln. Spring Security empfiehlt die Verwendung von BCryptPasswordEncoder, es sei denn, Sie haben spezielle Anforderungen für das Hashing.

Wenn Sie mehr über Hashing erfahren möchten, lesen Sie bitte die folgende Seite. Grundkenntnisse Was ist Verschlüsselung? Was ist Hashing? Wie kann ein Passwortverlust verhindert werden? ](Https://qiita.com/bayasist/items/da4be3a90a97b8cf38fe)

3-7-2. Erstellen eines Passwort-Hashing-Programms

Ich denke, es ist besser, das folgende Programm mit einem neu erstellten Projekt zu erstellen. (Spring Security muss im neuen Projekt noch als Abhängigkeit festgelegt werden.) Sie können ein Programm mit einem vorhandenen Projekt erstellen. Der Autor ist jedoch der Ansicht, dass es besser ist, ein neues Projekt zu erstellen, da mehrere Hauptfunktionen im selben System erstellt werden können. Ändern Sie den rawPassword-Wert (admin in Ihrem Code) in den Wert, den Sie als Kennwort festlegen möchten.

** Code für "GeneratePassword.java" anzeigen **

GeneratePassword.java


package com.example.demo.password;

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

public class GeneratePassword {

	public static void main(String[] args) {
		//Geben Sie das Passwort ein, das Sie hashen möchten
		String rawPassword = "admin";
		
		//Hash-Passwort
		String password = getEncodePassword(rawPassword);
		
		//Hash-Wert anzeigen
		System.out.println(password);
	}

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

3-7-3. Versuchen Sie, Passwörter zu hashen

Führen Sie das in [3-7-2. Erstellen eines Kennwort-Hashing-Programms](# 3-7-2-Erstellen eines Kennwort-Hashing-Programms) in einer Java-Anwendung erstellte Programm aus.

Der auf der Konsole angezeigte Wert ist das Hash-Passwort.

image

4. Implementierung der Authentifizierungs- / Autorisierungsfunktion für das tatsächliche Geschäft

4-1. Authentifizierungsfunktion für das tatsächliche Geschäft

Einführung in die Implementierung der Multi-Factor-Authentifizierung (MFA). In den letzten Jahren haben viele Systeme MFA eingeführt, um die Sicherheit zu verbessern. Da MFA in vielen unserer Systeme installiert ist, möchte ich einen Artikel darüber schreiben, wie MFA als praktische Implementierung implementiert wird. Die Implementierung von MFA wird etwas langwierig sein und im folgenden Artikel ausführlich erläutert. Es ist ein Implementierungsartikel von MFA, der ein Kennwort und ein Einmalkennwort verwendet. Wird gerade erstellt (bitte etwas länger warten)

4-2. Autorisierungsfunktion für das tatsächliche Geschäft

Als Ergebnis der Anhörung über das tatsächliche System im Unternehmen war eine davon "Zugriffskontrollinformationen in der Datenbank speichern und Berechtigungsverarbeitung durchführen". (In [3. Implementierung einer einfachen Authentifizierungs- / Autorisierungsfunktion](# 3 - Implementierung einer einfachen Authentifizierungs- / Autorisierungsfunktion) wurde die Autorisierungsverarbeitung unter Verwendung von Anmerkungen durchgeführt.) Ich möchte einen Artikel darüber schreiben, wie die Berechtigungsverarbeitung basierend auf den in dieser Datenbank gespeicherten Zugriffssteuerungsinformationen als Implementierung für das tatsächliche Geschäft implementiert wird.

4-2-1. Vorteile der Implementierung

In der einfachen Implementierung wurde die Autorisierungsfunktion mit @ PreAuthorize implementiert. In einem tatsächlichen System scheint die Autorisierungsfunktion jedoch häufig mithilfe von DB implementiert zu werden. Der Grund dafür ist, dass die Seiten, die den Zugriff auf die Rolle </ font> ermöglichen, einfach zu verwalten sind. Wenn "@ PreAuthorize" verwendet wird, werden für jeden Controller und jede Methode Anmerkungen hinzugefügt, sodass sie in verschiedenen Dateien verteilt werden und es schwierig wird zu wissen, welche Seite für welche Rolle zulässig ist. Dies kann sehr unklar und ineffizient sein, wenn Sie den Code später erneut lesen oder das System ändern. Auf der anderen Seite ist es viel einfacher, Rollen und Seiten zu verwalten, die den Zugriff </ font> ermöglichen, indem Sie die folgende Tabelle in der Datenbank erstellen und Zugriffssteuerungsinformationen nur in der Datenbank verwalten > Wird. Hier ist ein Beispiel für einen Teil der Tabelle, der dieses Mal verwendet wird. Das "*" in "Rolle" ist ein Platzhalter. Dies bedeutet, dass allen Rollen (einschließlich nicht authentifizierten Benutzern) Berechtigungen erteilt werden. Es bedeutet auch, dass "/ adminPage" die Rolle "ADMIN" und "/ userPage" die Rolle "USER" erhält.

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

4-2-2. Übersicht über die Berechtigungsfunktion für das tatsächlich auszuführende Geschäft

Die Seitenübergänge sind die gleichen wie in der einfachen Implementierung, wie in der folgenden Abbildung gezeigt.

image

Der in der folgenden Abbildung von Blau umgebene Teil zeigt den Genehmigungsprozess für das tatsächliche Geschäft. Erstellen Sie im Autorisierungsprozess Abstimmungsprozess, Service und DAO. Die Autorisierungsfunktion wird realisiert, indem die Datenbank abgefragt wird, um festzustellen, ob die Seite (URL), auf die die Clientbenutzerinformationen (Rolle) zugreifen möchten, zulässig ist.

image

Der Ablauf der Autorisierungsverarbeitung ist wie folgt. (Die Verarbeitungsnummer entspricht der Nummer in der obigen Abbildung)

  1. "FilterSecurityInterceptor" ruft den Autorisierungsprozess von "Affirmative Based" auf, der "AccessDecisionManager" ** (* 1) ** implementiert.
  2. "Affirmative Based" ruft den Abstimmungsprozess von "MyVoter" auf, der "AccessDecisionVoter" ** (* 2) ** implementiert. (In "Affirmative Based" ist es möglich, mehrere Implementierungsklassen von "AccessDecisionVoter" festzulegen und jeden Abstimmungsprozess aufzurufen. Diese Implementierung setzt jedoch nur "MyVoter".)
  3. "MyVoter" ruft die Rolle des Clients und die angeforderte URL ab und ruft den "AuthorizationService" auf, der die Zugriffsberechtigung bestimmt.
  4. "Authorization Service" ruft "Dao" auf.
  5. "Dao" fragt die Datenbank, ob die Kombination der Client-Rolle und der angeforderten URL vorhanden ist, und konvertiert sie, falls vorhanden, in eine Entitätsinstanz mit dem Namen "Zugriffsautorisierung" und gibt sie an den "Autorisierungsdienst" zurück.
  6. "Authorization Service" gibt die Zugriffsberechtigung ("true") zurück, wenn Zugriffsberechtigungsinformationen (Access Authorization) vorhanden sind, und die Zugriffsverweigerung ("false"), wenn sie nicht vorhanden ist, an "My Voter".
  7. "MyVoter" wird gewährt ("ACCESS_GRANTED"), wenn die Zugriffsberechtigung ("true") von "MyVoter" zurückgegeben wird, und verweigert ("ACCESS_DENIED"), wenn der Zugriff verweigert ("false") "Affirmative Based" zurückgegeben wird. Zurücksenden an. (Details ** (* 2) **)
  8. "Affirmative Based" gibt die Zugriffsberechtigung zurück, wenn das Abstimmungsergebnis von "MyVoter" gewährt wird ("ACCESS_GRANTED"), und löst eine Ausnahme von "AccessDeniedException" aus, wenn es verweigert wird ("ACCESS_DENIED"). (Details ** (* 1) **)
  9. "FilterSecurityInterceptor" ist "AffirmativeBased". Wenn der Zugriff zulässig ist, wird der Prozess an den nächsten Filter gesendet. Wenn der Zugriff verweigert wird, wird "AccessDeniedException" ausgelöst. AccessDeniedException wird von" ExceptionTranslationFilter "abgefangen.

(※1) AccessDecisionManager Eine Schnittstelle, die überprüft, ob Sie Zugriff auf die Ressource haben, auf die Sie zugreifen möchten. Es gibt drei Arten von Implementierungsklassen, die von Spring Security bereitgestellt werden. Alle rufen jedoch die Vote-Methode der Schnittstelle namens AccessDecisionVoter auf, um zu bestimmen, ob Zugriffsrechte gewährt werden sollen. "AccessDecisionVoter" stimmt für "Gewähren", "Verweigern" oder "Zurückziehen", und die Implementierungsklasse von "AccessDecisionManager" aggregiert die Abstimmungsergebnisse, um das endgültige Zugriffsrecht zu bestimmen. Wenn festgestellt wird, dass Sie keinen Zugriff haben, wird eine "AccessDeniedException" ausgelöst und der Zugriff verweigert. Die folgende Tabelle zeigt die von Spring Security bereitgestellten Implementierungsklassen.

Implementierungsklasse Erläuterung
AffirmativeBased AccessDecisionVoterUm abzustimmenGewähren Sie Zugriff, wenn ein "Zuschuss" gewählt wirdImplementierungsklasse.
デフォルトで使用されるImplementierungsklasse.
ConsensusBased AlleAccessDecisionVoterUm abzustimmenGeben Sie Zugriff, wenn die Anzahl der "Grant" -Stimmen groß istImplementierungsklasse.
UnanimousBased AccessDecisionVoterUm abzustimmenKein Zugriffsrecht, wenn eine "Verweigerung" gewählt wirdImplementierungsklasse.

(※2) AccessDecisionVoter AccessDecisionVoter ist eine Schnittstelle zum Abstimmen, ob der Zugriff gewährt werden soll, indem auf die Zugriffsrichtlinie verwiesen wird, die für die Ressource angegeben ist, auf die Sie zugreifen möchten. "AccessDecisionVoter" stimmt für "Gewähren", "Verweigern" oder "Zurückziehen". Die folgende Tabelle zeigt die wichtigsten Implementierungsklassen von Spring Security.

Implementierungsklasse Erläuterung
WebExpressionVoter Spring Expression Language (SpEL)Benutzeranmeldeinformationen mit(Authentication)Und Informationen anfordern(HttpServletRequest)Eine Implementierungsklasse, die referenziert und abstimmt.
RoleVoter Eine Implementierungsklasse, die sich auf die Rolle und die Stimmen des Benutzers bezieht.
RoleHierarchyVoter Eine Implementierungsklasse, die anhand der geschichteten Rolle des Benutzers abstimmt.
AuthenticatedVoter Eine Implementierungsklasse, die anhand des Authentifizierungsstatus abstimmt.

In dieser Implementierung erstellen wir eine neue Klasse mit dem Namen "MyVoter", die "AccessDecisionVoter" implementiert, und setzen nur diesen "MyVoter" auf den von "AffirmativeBased" aufgerufenen Abstimmungsprozess.

4-2-3. Projektstruktur der Berechtigungsfunktion für das tatsächliche Geschäft

Markieren Sie basierend auf der Implementierung in [3. Implementierung der einfachen Authentifizierungs- / Autorisierungsfunktion](Nr. 3 - Implementierung der einfachen Authentifizierungs- / Autorisierungsfunktion) die hinzuzufügenden Dateien in Rot und die Dateien zum Hinzufügen oder Ändern von Code in Blau. Ich bin.

image

4-2-4. H2-Datenbank (Code hinzugefügt)

** Code für "schema.sql" anzeigen **

schema.sql


/*Fügen Sie den folgenden Code am Ende der Datei hinzu*/
CREATE TABLE IF NOT EXISTS access_authorization (
  rolename VARCHAR(10) NOT NULL,
  uri VARCHAR(255) NOT NULL,
  PRIMARY KEY(rolename, uri)
);
** Code für "data.sql" anzeigen **

data.sql


/*Fügen Sie den folgenden Code am Ende der Datei hinzu*/
/*Alle Rollenberechtigungen*/
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-Rollenberechtigungen*/
INSERT INTO access_authorization(rolename, uri) VALUES ('ADMIN', '/home');
INSERT INTO access_authorization(rolename, uri) VALUES ('ADMIN', '/adminPage');

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

Ein Rollenname von "*" ist ein Platzhalter, der bedeutet, dass Zugriff auf alle Rollen </ font> gewährt.

4-2-5. Controller (Codekorrektur)

image
Kommentieren Sie unnötiges "@ PreAuthorize" aus, da die Berechtigung in der Datenbank festgelegt ist.
** Code für "AdminPageController.java" anzeigen **

AdminPageController.java


/*Kürzung*/
	@GetMapping("/adminPage")
	// @PreAuthorize("hasRole('ROLE_ADMIN')")Auskommentieren
	public String adminPage() {
/*Kürzung*/
** Code für "UserPageController.java" anzeigen **

UserPageController.java


/*Kürzung*/
	@GetMapping("/userPage")
	// @PreAuthorize("hasRole('ROLE_USER')")Auskommentieren
	public String userPage() {
/*Kürzung*/

4-2-6. Entität (Datei hinzufügen)

image
** Code für "AccessAuthorization.java" anzeigen **

AccessAuthorization.java


package com.example.demo.entity;

import java.io.Serializable;

public class AccessAuthorization implements Serializable {
	String roleName;	//Zugriff in H2DB_Von der Berechtigungstabelle"rolename"Feld zum Speichern
	String uri;			//Zugriff in H2DB_Von der Berechtigungstabelle"uri"Feld zum Speichern

	/**
	 * 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 (Datei hinzufügen)

Greifen Sie mit Spring JDBC auf die Datenbank zu.

image
** Code für "AccessAuthorizationDao.java" anzeigen **

AccessAuthorizationDao.java


package com.example.demo.repository;

import com.example.demo.entity.AccessAuthorization;

public interface AccessAuthorizationDao {
	AccessAuthorization find(String roleName, String uri);
}
** Code für "AccessAuthorizationDaoImpl.java" anzeigen **

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

	/**
	 *Führen Sie eine SELECT-Anweisung mit roleName und uri als Suchbedingungen aus, um zu suchen, ob sie in der Datenbank registriert ist.
	 * @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 = ?";

		//Holen Sie sich einen Benutzer
		Map<String, Object> result = jdbcTemplate.queryForMap(sql, roleName, uri);

		//Entitätsklasse(Benutzertyp)Umstellung auf
		AccessAuthorization auth = convMapToAccessAuthorization(result);

		return auth;
	}

	/**
	 *Ergebnis der Ausführung der SQL SELECT-Anweisung(Map<String, Object>)Um auf den Autorisierungstyp zuzugreifen
	 * @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 (Datei hinzufügen)

image
** Code für "AuthorizationService.java" anzeigen **

AuthorizationService.java


package com.example.demo.service;

public interface AuthorizationService {
	boolean isAuthorized(String roleName, String uri);
}
** Code für "AuthorizationServiceImpl.java" anzeigen **

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

    /**
     *Legt fest, ob die im Argument übergebene Kombination aus Rollenname und URI zulässig ist.
     * @param roleName
     * @param uri
     * @return boolean
     */
    @Override
    public boolean isAuthorized(String roleName, String uri) { // ---(1)Methode, um festzustellen, ob der Zugriff zulässig ist

        if (StringUtils.isEmpty(roleName)) {
            throw new IllegalArgumentException("Rollenname ist leer.");
        }

        if (StringUtils.isEmpty(uri)) {
            throw new IllegalArgumentException("Der URI ist leer.");
        }

        //Eine AccessAuthorization-Ausnahme erhalten tritt auf, wenn keine AccessAuthorization vorhanden ist
        try {
            AccessAuthorization auth = authDao.find(roleName, uri); // ---(2)Rufen Sie eine AccessAuthorization-Instanz ab

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

            } else {            // ---(4)Zugriff verweigert
                return false;

            }
        } catch (EmptyResultDataAccessException e) { // ---(5)Zugriff verweigert
            return false;
        }
    }
}
Artikel Erläuterung
(1) Benutzerrollenname und Zugriffspfad(URI)Ist eine Methode, die bestimmt, ob der Zugriff gewährt wird.
(2) Übergeben Sie den Rollennamen und den URI an Dao und seien Sie eine EntitätAccessAuthorizationHolen Sie sich eine Instanz von.
(3) AccessAuthorizationWenn Sie eine Instanz von erhalten können, stellen Sie fest, dass der Zugriff zulässig ist undtrueGib es zurück.
(4) Von DaoAccessAuthorizationInstanz von kann nicht abgerufen werdennullWenn zurückgegeben wird, wird beurteilt, dass der Zugriff verweigert und der Zugriff verweigert wird.falseGib es zurück.

4-2-9. Wähler (Datei hinzufügen)

image
** Code für "MyVoter.java" anzeigen **

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)Methode, um festzustellen, ob eine Abstimmung erforderlich ist oder nicht
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {				// ---(1)Methode, um festzustellen, ob eine Abstimmung erforderlich ist oder nicht
		return true;
    }

    @Override
    public int vote(Authentication authentication, FilterInvocation filterInvocation,
    		Collection<ConfigAttribute> attributes) { 		// ---(2)Eine Methode zur Abstimmung über die Gewährung des Zugriffs

    	HttpServletRequest request = filterInvocation.getHttpRequest();	// --- (3)Holen Sie sich HttpServletRequest
    	String uri = request.getRequestURI();							// --- (4)URI von Anfrage abrufen

    	if(authorizationService.isAuthorized("*", uri)) {	// --- (5)Stellen Sie fest, ob allen Rollen Zugriff gewährt wird
        	return ACCESS_GRANTED;
        }

		Object principal = authentication.getPrincipal();	// --- (6)Erfassung von Benutzeridentifikationsinformationen

		if (!principal.getClass().equals(AccountUserDetails.class)) { // ---(7)Stellen Sie fest, ob es sich bei den erfassten Identifikationsinformationen um Kontonutzerdetails handelt
			return ACCESS_DENIED;
		}

		String roleName = ((AccountUserDetails) principal).getUser().getRoleName(); // ---(8)Holen Sie sich die Rolle des Benutzers

		if(authorizationService.isAuthorized(roleName, uri)) { // ---(9)Stellen Sie fest, ob die erworbene Rolle autorisiert ist
        	return ACCESS_GRANTED;
        }

    	return ACCESS_DENIED;
    }
}
Artikelnummer Erläuterung
(1) Eine Methode, die sich auf den Wert des Arguments bezieht, um zu bestimmen, ob eine Abstimmung erforderlich ist oder nicht
(2) Eine Methode zur Abstimmung über die Gewährung des Zugriffs(Detail ist,4-2-2.Übersicht über die Berechtigungsfunktion für das tatsächlich umzusetzende Geschäft)
(3) HttpServletRequestBekommen.
FilterInvocationIstHttpServletRequestOderHttpServletRequestEnthält Objekte, die HTTP-Filtern zugeordnet sind, z.
(4) HttpServletRequestRufen Sie die URI ab, von der aus Sie zugreifen möchten.
(5) Stellen Sie fest, ob der erfasste URI Zugriff auf alle Rollen gewährt.
(6) Holen Sie sich die Identifikationsinformationen des zugreifenden Benutzers
(7) Die erfassten Identifikationsinformationen sindAccountUserDetailsBestimmen Sie, ob es sich um eine Klasse handelt.
Wenn sie nicht authentifiziert sind, werden die Identifikationsinformationen von der String-Klasse erfasst, sodass ein Fehler auftritt, sofern dieser Bestimmungsprozess nicht ausgeführt wird.
(8) AccountUserDetailsRuft die Benutzerrolle von einer Instanz von ab.
(9) Stellen Sie fest, ob die Rolle des Benutzers und der Pfad, auf den Sie zugreifen möchten, autorisiert sind.

4-2-10. JavaConfig (Codeänderung)

image

Stellen Sie den erstellten "MyVoter" so ein, dass er für Zugriffsrechte stimmt.

** Code für "WebSecurityConfig.java" anzeigen **

WebSecurityConfig.java


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

    @Autowired
    AccountUserDetailsService userDetailsService;

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

	public AccessDecisionManager createAccessDecisionManager() { //Nachtrag
		return new AffirmativeBased(Arrays.asList(myVoter)); //Nachtrag---(1)Affirmative Based wird für die Genehmigungsverarbeitung und MyVoter für die Abstimmungsverarbeitung verwendet.
	} //Nachtrag

/*Kürzung*/

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //Autorisierungseinstellungen
        http.exceptionHandling()
        		.accessDeniedPage("/accessDeniedPage")
        	.and()
        	.authorizeRequests()
				.antMatchers("/**").authenticated() //Fix
					.accessDecisionManager(createAccessDecisionManager()); //Nachtrag---(2)Wenden Sie den Autorisierungsprozess für alle Zugriffe an

/*Kürzung*/
Artikelnummer Erläuterung
(1) AccessDecisionManagerAls Implementierungsklasse vonAffirmativeBasedBewerben.
AccessDecisionVoterAls Implementierungsklasse vonMyVoterBewerben.
AffirmativeBasedZum Zeitpunkt der Instanziierung vonAccessDecisionVoterMehrere Abstimmungsprozesse können durch Übergeben von Instanzen von festgelegt werden.
(2) Autorisierungsverarbeitung für alle Zugriffe(MyVoterAbstimmungsprozess)Ist auf Ausführung eingestellt.

5. Zusammenfassung

Heutzutage werden Sicherheitsprobleme häufig in den Nachrichten behandelt. Stellen Sie daher sicher, dass Sie Sicherheitsmaßnahmen in Ihrer Webanwendung implementieren. Wenn Sie eine Webanwendung mit dem Spring Framework entwickeln, sollten Sie Spring Security für Sicherheitsmaßnahmen nutzen. Sie können einfache und robuste Sicherheitsmaßnahmen anstelle von selbst erstellten Sicherheitsmaßnahmen einführen. Auf der anderen Seite ist es aus praktischer Sicht erforderlich, eine teilweise Erweiterung von Spring Security zu implementieren, da beispielsweise im Autorisierungsprozess auf die Datenbank verwiesen werden muss. Ich denke jedoch, dass dieser Artikel ein wenig gezeigt hat, wie diese erweitert werden kann. .. Die Idee, dass "auf der Grundlage eines Standardprodukts zusätzliche Funktionen, die für das Geschäft und den Betrieb erforderlich sind, zusätzlich implementiert werden", ist im tatsächlichen Geschäft wichtig, und der Autor selbst kann durch Anhörungen und das Schreiben von Artikeln lernen. Ich tat. In diesem Artikel hat Spring Security die Funktionen "Authentifizierung" und "Autorisierung" implementiert, die die Grundfunktionen von Sicherheitsmaßnahmen darstellen. Weitere Sicherheitsfunktionen bietet Spring Security. Ich habe noch viele Dinge, die ich nicht weiß, also möchte ich lernen.

Bonus

Dies ist ein Artikel, der von einem Neuling (meine Synchronisation) im nächsten Abschnitt geschrieben wurde. Wenn Sie SSO (Single Sign-On) einführen möchten, lesen Sie bitte den folgenden Artikel. Erläutert das Verfahren zum Verknüpfen von Keycloak und Azure AD. Versuchen Sie eine externe ID-Verknüpfung mit Keycloak

Referenzmaterial

Recommended Posts