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 (╹◡╹)
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.
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.
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.
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 ...
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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é.
"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.
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é.
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.
Écran de connexion Erreur d'authentification Connexion réussie Se déconnecterLe 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