[JAVA] Essayez d'implémenter une fonction de connexion avec Spring-Boot

Préface et environnement

J'ai implémenté une fonction de connexion simple à l'aide de Spring-Boot. J'ai fait quelque chose de similaire avec PHP une fois, et quand j'ai pensé que je pouvais me le permettre, j'ai beaucoup lutté, alors je vais le résumer sous forme de mémorandum. Si vous écrivez tout de manière exhaustive, vous manquerez de puissance, alors j'écrirai en gros en me concentrant sur la partie obstruée. Je me trompe ici! Si vous avez des questions, merci de bien vouloir me le faire savoir (╹◡╹)

environnement

Flux de processus

Le déroulement de ce processus est le suivant. Il y a "hello.html" et "login.html" sur l'écran, et hello.html est un écran qui ne peut pas être vu sans se connecter. ① Traitement des autorisations Si vous essayez d'accéder à hello.html sans vous connecter, il passera à login.html

② Processus d'authentification Comparez les valeurs de «nom d'utilisateur» et de «mot de passe» saisies dans le formulaire avec les valeurs enregistrées dans le DB. S'il y a une correspondance, l'authentification est réussie. S'il n'existe pas, il passe à login.html et affiche un message d'erreur.

③ Gestion de session Si l'authentification réussit, les informations de session sont stockées dans la base de données. Vous permet d'obtenir le nom d'utilisateur à partir des informations de session si nécessaire.

④ Supprimer la session Lorsque vous appuyez sur le bouton de déconnexion, les informations de session sont supprimées et l'authentification est à nouveau requise.

La figure ci-dessous résume ces processus.

フロー図.png

Si vous les implémentez tous en même temps, votre tête sera perforée, donc cette fois nous les avons implémentés en 3 étapes.

Ci-dessous, nous examinerons les étapes de mise en œuvre pour chaque étape.

Dépendances

Avant cela, je décrirai les dépendances lors de la création d'un projet avec Spring-Boot. Voir pom.xml pour plus de détails. dependencies.png

Étape 1. Entrez "utilisateur" et "mot de passe" et cela passera!

Nous commencerons par faire la fondation. Voici ce que vous ferez dans cette étape:

Commençons par tous les fichiers de configuration de base. Mais avant ça ...

Problème de fichier de configuration difficile

Il semble difficile de bricoler le fichier de configuration ... Donc au début, j'ai pensé que Spring-Security était difficile à comprendre. Cependant, étant donné que Spring-Security vous permet d'écrire des paramètres sous la forme de classes Java, je pense qu'une fois que vous les écrivez, vous pouvez vous en faire une idée. Je comprends intuitivement qu'il est possible de définir en ajoutant des valeurs (paramètres pour le système) aux propriétés de la classe qui gère les paramètres, car on a l'impression de jouer avec le volume et la qualité d'image dans les jeux. Je pense que c'est facile.

MvcConfig est particulièrement facile à comprendre, donc si vous êtes nouveau dans Config, vous voudrez peut-être l'essayer à partir de là. Quand j'ai vu le fichier de configuration pour la première fois, il avait l'air complètement différent du code que j'écris habituellement, donc je l'ai écrit avant de regarder le code.

WebSecurityConfig.java


package login.app.config;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import login.app.service.UserDetailsServiceImpl;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	
	
	@Autowired
	private UserDetailsServiceImpl userDetailsService;
	
	//Étant donné que le mot de passe obtenu à partir de la base de données à comparer avec la valeur du formulaire est chiffré, il est également utilisé pour chiffrer la valeur du formulaire.
	@Bean
	public BCryptPasswordEncoder passwordEncoder() {
		BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
		return bCryptPasswordEncoder;
	}
	
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
                            "/images/**",
                            "/css/**",
                            "/javascript/**"
                            );
    }

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
		    .authorizeRequests()
		        .anyRequest().authenticated()
		        .and()
		    .formLogin()
		        .loginPage("/login") //Étant donné que la page de connexion ne passe pas par le contrôleur, il est nécessaire de la lier à ViewName.
		        .loginProcessingUrl("/sign_in") //Soumettre l'URL du formulaire, le processus d'authentification est exécuté lorsqu'une demande est envoyée à cette URL
		        .usernameParameter("username") //Attribut de nom explicite du paramètre de demande
		        .passwordParameter("password")
		        .successForwardUrl("/hello")
		        .failureUrl("/login?error")
		        .permitAll()
		        .and()
		    .logout()
		        .logoutUrl("/logout")
		        .logoutSuccessUrl("/login?logout")
		        .permitAll();
	}
	
	@Autowired
	public void configure(AuthenticationManagerBuilder auth) throws Exception{
		auth
		    .inMemoryAuthentication()
		        .withUser("user").password("{noop}password").roles("USER");
	}

}

La partie de base est soigneusement résumée par Officiel, donc si vous ne savez pas ce que vous dites, veuillez vous y référer. Je pense qu'il vaut mieux le faire.

Jetons maintenant un coup d'œil à chaque processus.

Annotation

Lisez correctement l'annotation @Configuration car cette classe est un fichier de configuration! C'est pour raconter Spring. L'annotation @EnableWebSecurity qui l'accompagne sert à utiliser ceci et celui de Spring-Security.

Classe d'héritage

Cette classe hérite de la classe "WebSecurityConfigurerAdapter" et elle sera utilisée en remplaçant certaines méthodes utilisées dans les paramètres. Cette fois, nous remplacerons la méthode configure pour définir le processus d'authentification / autorisation.

configure, méthode (WebSecurity)

Ici, les paramètres sont définis pour que la requête lors de l'utilisation d'un fichier statique (image, css, javascript) ne soit pas lue.

configure, méthode (HttpSecurity)

C'est une méthode pour décrire les paramètres de la partie liée à la requête http dans le processus d'authentification / autorisation. Cela semble difficile à première vue car il y a beaucoup de points alignés, mais je ne fais que définir des paramètres individuels, donc si je suis à la recherche de tutoriels, je suis sûr que ce sera le cas. Voici une liste approximative de ce qui est défini.

configure, méthode (AuthenticationManagerBuilder)

Ici, inMemoryAuthentication () est utilisé pour indiquer clairement que l'authentification en mémoire est effectuée, et le nom d'utilisateur et le mot de passe utilisés pour l'authentification sont définis directement. Je pense qu'il est peu probable que les applications modernes n'utilisent pas DB, je voudrais donc revoir le processus d'authentification dans la partie qui utilise DB.

Avec ou sans câblage automatique

Si vous regardez les didacticiels, vous verrez souvent le nom de la méthode configureGlobal et l'annotation @Autowired. En parlant de @Autowired, vous le voyez souvent utilisé pour les champs, mais vous pouvez également l'attacher à des méthodes. En ce qui concerne la classe de configuration cette fois, le nom peut être défini pour configurer pour la méthode sans annotation @Autowired, et n'importe quel nom peut être défini pour la méthode avec l'annotation @Autowired. Référence


Ensuite, regardons le fichier de paramètres lié à ViewName.

MvcConfig.java


package login.app.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class MvcConfig implements WebMvcConfigurer {
	
	/**
	 * 「/connectez-vous depuis l'URL "login".Appeler html
	 */
	public void addViewControllers(ViewControllerRegistry registry) {
		registry.addViewController("/login").setViewName("login");
	}

}

Normalement, dans Spring-Boot, un contrôleur existe, récupère une demande avec l'annotation @RequesMapping, effectue un traitement, renvoie le nom de la vue de l'écran (html) et affiche l'écran. Cependant, comme le professeur Spring-Security effectue le processus de connexion, je pense qu'il n'y a pas de problème même si vous n'implémentez pas LoginController ou quelque chose comme ça. Cependant, comme il est nécessaire de séparer l'association entre le nom de la vue et l'URL, nous la décrirons dans ce fichier de paramètres. Ici, lorsqu'une requête est accompagnée de l'URL "/ login", le processus est décrit en affichant "login" avec le nom de la vue (login.html). Je serais très heureux si vous pouviez me dire s'il existe une meilleure façon de le décrire.


Je vais omettre hello.html car ce n'est qu'un message d'accueil, mais j'écrirai à propos de login.html car c'est un peu addictif.

login.html


<form th:action="@{/sign_in}" method="post">
	    <input type="text" name="username">user_name
	    <br/>
	    <input type="password" name="password">password
	    
	    <br/>
	    
	    <input type="submit" value="Login">
	</form>

Le point à noter ici est que la valeur de l'attribut name doit correspondre à celle décrite dans le fichier de configuration et que la destination du formulaire doit être spécifiée par l'attribut th: action. Cependant, si vous écrivez "/ sign_in", vous vous fâcherez qu'il n'y ait pas une telle URL. En effet, les mesures CSRF Spring-Security sont activées par défaut. Si vous passez l'URL simple à acrtion sans penser à quoi que ce soit dans cet état, vous vous fâcherez parce que le jeton pour les contre-mesures CSRF n'est pas placé. Il existe différentes manières de résoudre ce problème, mais cette fois, en définissant "th: action = @ {URL}" dans Thymeleaf, un jeton sera généré et placé sur l'URL. Référence

De plus, par défaut, l'URL de destination est une URL longue qui effectue le traitement d'authentification, mais je pense qu'il est préférable de la spécifier dans le fichier de configuration.


Si vous l'exécutez avec l'implémentation jusqu'à présent, l'écran de connexion sera affiché, donc si vous entrez "utilisateur" et "mot de passe", vous pourrez passer à hello.html. Comme j'ai oublié de capturer le résultat de l'exécution à chaque fois, l'écran d'exécution ne s'affichera qu'à l'étape 3, mais pardonnez-moi.


Étape 2. Si vous trouvez un utilisateur dans la base de données, laissez-le passer!

Nous avons implémenté une fonctionnalité qui permet uniquement à l'utilisateur connecté à l'étape 1 de visualiser la page. Cependant, il n'est pas possible d'identifier «qui s'est connecté» si tout le monde utilise un nom d'utilisateur et un mot de passe communs. En premier lieu, la sécurité est trop ridicule. Dans cette étape, nous ajouterons une fonction à laquelle seuls les utilisateurs enregistrés dans la base de données peuvent se connecter. Les éléments suivants sont obligatoires.

Étant donné que le but ici est de créer une fonction de connexion, j'omettrai comment utiliser Entity et EntityManager, mais je voudrais également résumer cette zone. Jetons donc un coup d'œil à la classe UserDetailsServiceImpl.

UserDetailsServiceImpl.java


package login.app.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import login.app.dao.LoginUserDao;
import login.app.entity.LoginUser;

@Service
public class UserDetailsServiceImpl implements UserDetailsService{
	
	//Une classe qui implémente une méthode pour rechercher des informations utilisateur à partir de DB
	@Autowired
	private LoginUserDao userDao;
	
	@Override
	public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
		
		LoginUser user = userDao.findUser(userName);
		
		if (user == null) {
			throw new UsernameNotFoundException("User" + userName + "was not found in the database");
		}
		//Liste des permissions
		//Il y a Admin, User, etc., mais comme nous ne les utiliserons pas cette fois, seul USER est temporairement défini.
		//Pour utiliser les autorisations, il est nécessaire de créer et de gérer des tables d'autorisations et des tables d'autorisations utilisateur sur la base de données.
		List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();
		GrantedAuthority authority = new SimpleGrantedAuthority("USER");
		grantList.add(authority);
		
		//Le mot de passe RawData ne peut pas être transmis, donc le chiffrement
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		
		//Puisque UserDetails est une interface, transtypez l'objet utilisateur créé par le constructeur de la classe User.
		UserDetails userDetails = (UserDetails)new User(user.getUserName(), encoder.encode(user.getPassword()),grantList);
		
		return userDetails;
	}

}

Le flux de traitement de la méthode loadUserByUsername qui acquiert les informations pour effectuer le traitement d'authentification est le suivant.

Je voudrais jeter un coup d'œil à chacun d'eux.

Informations d'autorisation

Spring-Security exige que vous transmettiez également des autorisations (administrateur, utilisateur, etc.) à vos informations d'identification. Cependant, comme nous n'avons besoin que d'un utilisateur ici, nous générerons temporairement des informations d'autorisation à partir de SimpleGrantedAuthority et les placerons dans la liste de GrantedAuthority. Si vous souhaitez également définir des autorisations, vous devrez créer une table d'autorisations ou une table d'autorisations utilisateur dans la base de données ou augmenter le nombre d'entités, mais cette fois, c'est OK si vous pouvez vous connecter, je vais donc l'omettre.

chiffrement

Dans Spring-Security, les mots de passe sont essentiellement chiffrés. Comme je n'ai aucune connaissance du cryptage, je pense que j'utilise des endroits célèbres pour le moment, mais lors de la création d'une application réelle, je connais chaque méthode et je crypte le mot de passe au stade du stockage des informations utilisateur dans la base de données. Je pense que ce sera nécessaire.

Objet UserDetails

Dans Spring-Security, au lieu de transmettre les informations utilisateur en tant qu'entité utilisateur, un nom d'utilisateur appelé UserDetails, un mot de passe chiffré et un objet de type UserDetails créé par des informations d'autorisation sont transmis au processus d'authentification. Étant donné que la classe User est une classe d'implémentation de l'interface UserDetails, elle peut être utilisée comme valeur de retour telle quelle, mais je pense qu'il est préférable de la convertir en fonction du type de méthode, puis de la renvoyer.


Ensuite, puisque les informations d'authentification ont été modifiées de la mémoire à la base de données, je voudrais jeter un coup d'œil à la partie modifiée du fichier de configuration.

WebSecurityConfig.java(Partie de traitement d'authentification)


public void configure(AuthenticationManagerBuilder auth) throws Exception{
		auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
	}

Ici, si vous spécifiez que userDetailsService est utilisé pour l'authentification et transmettez la classe d'implémentation créée précédemment en tant qu'argument, une classe appelée DaoAuthenticationProvider sera appelée et la méthode loadUserByUsername implémentée dans l'authentification sera appelée. .. Spécifiez la même méthode de cryptage que celle de DB pour passwordEncoder et utilisez-la pour comparer la valeur d'entrée du formulaire avec le mot de passe de DB et effectuer le processus d'authentification.


Si vous soumettez à nouveau le formulaire avec login.html avec l'implémentation jusqu'à présent, il recherchera dans la base de données et seuls les utilisateurs existants dans la base de données passeront l'authentification. Tu l'as fait.


Étape 3. Voyez qui est connecté à la session!

Enfin, il gère les informations de session et réalise les fonctions suivantes.

Il existe différentes méthodes de gestion de session, mais cette fois je vais la mettre sur MySQL. Cependant, il ne vous reste plus qu'à préparer une [requête de génération de table de session](https://github.com/spring-projects/spring-session/tree/master/spring-session-jdbc/src/main/ Exécutez simplement resources / org / springframework / session / jdbc) et dérangez-vous avec application.properties et dépendances. Spring-Security s'occupera du contenu.


La gestion de session peut ne pas pouvoir être écrite ici, comme l'utilisation de Redis avec NoSQL ou ce qu'il faut faire avec l'ID de session, donc cette fois je n'écrirai qu'une méthode d'utilisation approximative pour créer une fonction de connexion et un contenu détaillé J'aimerais en regarder un autre.

Créer et définir une table de stockage de session

La création elle-même est OK si vous exécutez l'instruction CREATE pour chaque SQL que vous utilisez sur le lien ci-dessus. Vous pouvez voir comment la session se comporte dans l'application en sélectionnant séquentiellement le contenu de ce tableau. J'ai besoin de bien connaître ce domaine, alors j'aimerais l'organiser un peu plus avant d'écrire.

Ensuite, c'est un paramètre pour utiliser la table, mais c'est OK juste en ajoutant un peu à application.properties et pom.xml. Je vous remercie.

application.properties


spring.session.store-type=jdbc

pom.xml(Partie utilisation de la session)


       <dependency>
		<groupId>org.springframework.session</groupId>
		<artifactId>spring-session-jdbc</artifactId>
	    </dependency>

Je ne pense pas que pom.xml soit dans la liste des dépendances au stade de la génération du projet, vous devez donc l'ajouter à la main. Je n'ai pas compris cela et c'était assez chargé.

Lecture des informations de session

"Bonjour, nom d'utilisateur san!" Dans hello.html, pour voir quelque chose comme, qui vous êtes connecté maintenant, vous devez avoir des informations. Cela sera implémenté par la méthode init de HelloController, qui transite lorsque la connexion est réussie.

HelloController.java(Méthode appelée lors de la transition d'écran)


@RequestMapping("/hello")
	private String init(Model model) {
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		//Obtenir les informations de connexion du principal
		String userName = auth.getName();
		model.addAttribute("userName", userName);
		return "hello";
		
	}

Le point ici est le SecurityContextHolder. Cela stocke les informations de l'utilisateur actuellement connecté, mais il s'agit en fait d'une session HttpSession. En d'autres termes, les informations utilisateur contenues dans les informations de session définies sur Spring-Security sont utilisées comme clé pour faire référence à la base de données qui stocke les informations de session, et l'état de connexion de l'utilisateur correspondant est défini. C'est compliqué. Je vais organiser cette zone correctement et la résumer dans un autre article (o_o) S'il s'agit de l'application d'origine, nous allons récupérer la zone d'ID utilisateur à partir du nom d'utilisateur acquis dans la base de données et lire davantage les informations de chaque utilisateur ... mais cette fois, nous mettons simplement le nom d'utilisateur dans la portée de la requête. Je vais le garder. En passant à hello.html dans cet état, le nom de l'utilisateur connecté peut être affiché à l'écran.

Se déconnecter

Au moment de la déconnexion, il y a diverses choses telles que la suppression des informations de session, mais si vous envoyez une demande à "/ déconnexion" du professeur Spring-Security, il le fera. Vous pouvez implémenter le processus de déconnexion vous-même, mais il semble que CSRF fera tout cela bien, donc je pense qu'il est prudent de nous le laisser. Cependant, je pense qu'il est nécessaire de garder à l'esprit ce qui est requis dans le processus et comment les informations de base de données de la session changent. Organisez cette zone (en abrégé ci-dessous)

Il y a une mise en garde, il semble que lorsque vous passez à "/ logout" sur hello.html, vous devez toujours effectuer la transition avec la méthode "POST". Il semble que ce soit pour faire ceci et cela du CSRF, mais comme la compréhension est floue, je voudrais faire de mon mieux pour étudier la sécurité.

Résultat d'exécution

Avec cela, nous avons implémenté un ensemble de fonctions. Enfin, je voudrais mettre le résultat de l'exécution d'une série de processus.

login.png Écran de connexion invalid.png Erreur d'authentification success.png Connexion réussie logout.png Se déconnecter

Le code source de cette série d'implémentations est répertorié sur GitHub. Il y a aussi un fichier sql dans le dossier sql pour faire ceci et cela avec une seule commande, donc si cela ne vous dérange pas.

Recommended Posts

Essayez d'implémenter une fonction de connexion avec Spring-Boot
Essayez d'implémenter la fonction de connexion avec Spring Boot
Comment implémenter TextInputLayout avec la fonction de validation
Essayez d'implémenter TCP / IP + NIO avec JAVA
Java pour jouer avec Function
Fonction de connexion avec Spring Security
Implémentons une fonction pour limiter le nombre d'accès à l'API avec SpringBoot + Redis
[Ruby on Rails] Implémenter la fonction de connexion par add_token_to_users avec l'API
Implémentez la fonction de connexion simplement avec le nom et le mot de passe dans Rails (3)
Implémenter le lien texte avec Springboot + Thymeleaf
Essayez d'implémenter Yuma dans Ruby
Implémenter une fonction de connexion simple dans Rails
[Java] Essayez de mettre en œuvre à l'aide de génériques
Essayez d'implémenter Yuma en Java
Avec Spring Boot, hachez le mot de passe et utilisez l'enregistrement des membres et la sécurité Spring pour implémenter la fonction de connexion.
Fonction de connexion
Essayez d'implémenter l'ajout n-aire en Java
Créez quand même une fonction de connexion avec Rails
Implémenter la fonction de pagination avec Spring Boot + Thymeleaf
"Professeur, je souhaite implémenter une fonction de connexion au printemps" ① Hello World
Implémentez la connexion avec Twitter dans Spring-Boot, Security, Social
J'ai essayé d'implémenter une fonction équivalente à Felica Lite avec HCE-F d'Android
J'ai essayé de gérer les informations de connexion avec JMX
Essayez d'intégrer Ruby et Java avec Dapr
Comment mettre en œuvre la fonction de chapelure avec Gretel
[Android] Implémentez rapidement la fonction pour afficher le mot de passe
[Pour les débutants] Comment implémenter la fonction de suppression
Flux pour implémenter la fonction de publication d'images à l'aide d'ActiveStorage
Créez une fonction de connexion / déconnexion avec Spring Security selon le guide officiel de Spring [pour les débutants]
Essayez d'obtenir la clé API de redmine avec ruby
Mettre en œuvre la fonction de recherche avec l'API Rakuten Books (DVD)
Essayez d'automatiser la migration avec Spring Boot Flyway
Comment sortir un fichier de ressources avec spring-boot
Essayez d'afficher Hello World avec Spring + Gradle
Essayez de résumer la disposition commune avec des rails
[Introduction à Spring Boot] Fonction d'authentification avec Spring Security
Comment rediriger après la connexion de l'utilisateur avec Spring-security
Suite ・ Flux pour implémenter la fonction de publication d'image à l'aide d'ActiveStorage
J'ai essayé ce que je voulais essayer avec Stream doucement.
Comment implémenter UICollectionView avec du code uniquement dans Swift
J'ai essayé d'implémenter le téléchargement de fichiers avec Spring MVC
Comment implémenter la connexion invité en 5 minutes sur le portefeuille de rails
J'ai essayé d'implémenter TCP / IP + BIO avec JAVA
[Procédure de mise en œuvre] Implémentez la fonction de téléchargement d'images avec Active Storage
Comment créer une fonction de messagerie LINE avec Ruby