Histoire de base et systématique Histoire Remember-Me Histoire CSRF Histoire de gestion de session L'histoire de l'en-tête de la réponse Histoire de la sécurité de la méthode Histoire CORS L'histoire de Run-As Histoire ACL Test story Parlez de la coopération avec MVC et Boot
Edition supplémentaire Ce que Spring Security peut et ne peut pas faire
ʻUserDetailsService` est responsable de la récupération des informations utilisateur.
Spring Security fournit plusieurs classes qui implémentent ʻUserDetailsService`.
Implémentation qui enregistre les informations utilisateur en mémoire. La classe spécifique est ʻInMemoryUserDetailsManager`.
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
...>
...
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user name="hoge" password="HOGE" authorities="ROLE_USER" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
Java Configuration
MySpringSecurityConfig.java
package sample.spring.security;
import org.springframework.beans.factory.annotation.Autowired;
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;
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
...
}
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("hoge").password("HOGE").roles("USER");
}
}
--Déclarez une méthode dans la classe de configuration qui reçoit ʻAuthenticationManagerBuilder et annotez avec
@ Autowired. --ʻInMemoryAuthentication ()
Définit les informations de définition avec la méthode.
JDBC
Une implémentation qui récupère les informations utilisateur de la base de données.
La classe réelle sera JdbcUserDetailsManager
.
** Commun **
build.gradle (dépendance supplémentaire)
compile 'org.springframework:spring-jdbc:4.3.6.RELEASE'
compile 'com.h2database:h2:1.4.193'
--Pour la vérification, utilisez H2 pour DB en mode intégré.
spring-jdbc
est ajouté à la dépendance pour créer DataSource
.src/main/resources/sql/create_database.sql
CREATE TABLE USERS (
USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,
PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,
ENABLED BOOLEAN NOT NULL
);
CREATE TABLE AUTHORITIES (
USERNAME VARCHAR_IGNORECASE(50) NOT NULL,
AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,
CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY (USERNAME) REFERENCES USERS(USERNAME),
CONSTRAINT UK_AUTHORITIES UNIQUE (USERNAME, AUTHORITY)
);
INSERT INTO USERS VALUES ('fuga', 'FUGA', true);
INSERT INTO AUTHORITIES VALUES ('fuga', 'USER');
--Par défaut, si vous déclarez une colonne de table comme décrit ci-dessus, les informations utilisateur seront recherchées automatiquement. --Il peut être modifié par réglage.
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
...
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
...
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:/sql/create_database.sql" />
</jdbc:embedded-database>
<sec:authentication-manager>
<sec:authentication-provider>
<sec:jdbc-user-service data-source-ref="dataSource" />
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
--Utilisez la balise <jdbc: script>
.
Java Configuration
MySpringSecurityConfig.java
package sample.spring.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
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 javax.sql.DataSource;
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
...
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.H2)
.setScriptEncoding("UTF-8")
.addScript("/sql/create_database.sql")
.build();
}
@Autowired
public void configure(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
}
}
--Utilisez la méthode jdbcAuthentication ()
de ʻAuthenticationManagerBuilder`.
CREATE TABLE USERS (
LOGIN_ID VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,
PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,
ENABLED BOOLEAN NOT NULL
);
CREATE TABLE AUTHORITIES (
LOGIN_ID VARCHAR_IGNORECASE(50) NOT NULL,
ROLE VARCHAR_IGNORECASE(50) NOT NULL,
CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY (LOGIN_ID) REFERENCES USERS(LOGIN_ID),
CONSTRAINT UK_AUTHORITIES UNIQUE (LOGIN_ID, ROLE)
);
Essayez de changer ʻUSERNAME en
LOGIN_ID et ʻAUTHORITY
en ROLE
.
Si vous souhaitez modifier le nom de la table ou le nom de la colonne comme vous le souhaitez, ajustez le SQL lors de la recherche d'informations utilisateur. Le nom de la colonne peut être n'importe quoi tant que l'ordre des éléments au moment de la recherche correspond.
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
...>
...
<sec:authentication-manager>
<sec:authentication-provider>
<sec:jdbc-user-service
data-source-ref="dataSource"
users-by-username-query="SELECT LOGIN_ID, PASSWORD, ENABLED FROM USERS WHERE LOGIN_ID=?"
authorities-by-username-query="SELECT LOGIN_ID, ROLE FROM AUTHORITIES WHERE LOGIN_ID=?" />
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
--Dans l'attribut ʻusers-by-username-query, définissez une requête pour rechercher ʻUSERNAME
, PASSWORD
, ʻENABLED. --Dans l'attribut ʻauthorities-by-username-query
, définissez une requête pour rechercher ʻUSERNAME, ʻAUTHORITY
.
Java Configuration
MySpringSecurityConfig.java
package sample.spring.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
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 javax.sql.DataSource;
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
...
}
@Bean
public DataSource dataSource() {
...
}
@Autowired
public void configure(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("SELECT LOGIN_ID, PASSWORD, ENABLED FROM USERS WHERE LOGIN_ID=?")
.authoritiesByUsernameQuery("SELECT LOGIN_ID, ROLE FROM AUTHORITIES WHERE LOGIN_ID=?");
}
}
--Définissez une requête pour rechercher ʻUSERNAME,
PASSWORD, ʻENABLED
dans la méthode ʻusersByUsernameQuery (String). --Définissez une requête pour rechercher ʻUSERNAME
, ʻAUTHORITYdans la méthode ʻauthoritiesByUsernameQuery (String)
.
MyUserDetailsService.java
package sample.spring.security;
import org.springframework.security.core.authority.AuthorityUtils;
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;
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if ("hoge".equals(username)) {
return new User(username, "HOGE", AuthorityUtils.createAuthorityList("USER"));
}
throw new UsernameNotFoundException("not found : " + username);
}
}
--ʻCréez une classe qui implémente UserDetailsService. --ʻUserDetailsService
n'a qu'une seule méthode appelée loadUserByUsername (String)
.
--Une chaîne de caractères pour identifier l'utilisateur (généralement le nom d'utilisateur saisi sur l'écran de connexion, etc.) est transmise en tant qu'argument, de sorte que les informations utilisateur correspondant à cette chaîne de caractères d'identification sont renvoyées.
--Throw ʻUsernameNotFoundException` si les informations utilisateur correspondantes n'existent pas.
. --Une classe appelée ʻUser
est préparée en standard. dus à des circonstances adultes, vous pouvez créer votre propre classe qui implémente ʻUserDetails
.namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
...
<bean id="myUserDetailsService" class="sample.spring.security.MyUserDetailsService" />
<sec:authentication-manager>
<sec:authentication-provider user-service-ref="myUserDetailsService" />
</sec:authentication-manager>
</beans>
--Spécifiez ʻUserDetailsService créé avec l'attribut ʻuser-service-ref
de<authentication-provider>
.
Java Configuration
MySpringSecurityConfig.java
package sample.spring.security;
import org.springframework.beans.factory.annotation.Autowired;
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;
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
...
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new MyUserDetailsService());
}
}
avec la méthode ʻuserDetailsService ()
de ʻAuthenticationManagerBuilder`.Dans le cas peu probable où des données seraient divulguées, il est mal conditionné que le mot de passe soit stocké en texte brut.
Par conséquent, le mot de passe est généralement enregistré dans une base de données ou similaire sous une forme dans laquelle la chaîne de caractères d'origine ne peut pas être spécifiée. Si vous n'avez pas besoin de combiner les mots de passe, utilisez une fonction de hachage pour convertir le mot de passe.
BCrypt En parlant de fonctions de hachage, il existe MD5 et SHA, mais si vous souhaitez hacher les mots de passe, il semble préférable d'utiliser BCrypt.
Si vous appliquez simplement la fonction de hachage une seule fois, [attaque de mot de passe] telle qu'une attaque circulaire, une attaque par dictionnaire, une table arc-en-ciel, etc. (https://ja.wikipedia.org/wiki/%E3%83%91%E3% 82% B9% E3% 83% AF% E3% 83% BC% E3% 83% 89% E3% 82% AF% E3% 83% A9% E3% 83% 83% E3% 82% AF) devenir.
BCrypt rend difficile de deviner le mot de passe d'origine en appliquant un sel aléatoire, puis en appliquant le hachage plusieurs fois au lieu de simplement hacher l'entrée une fois.
Spring Security recommande également d'utiliser ce BCrypt.
Le hachage de mot de passe peut être réalisé en définissant PasswordEncoder
sur DaoAuthenticationProvider
.
Lorsque vous utilisez BCrypt, utilisez l'implémentation PasswordEncoder``` BCryptPasswordEncoder
.
En outre, la valeur de hachage est décrite à l'avance par BCrypt hash value calcul | tekboy.
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
...
<bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user
name="hoge"
password="$2a$08$CekzJRYhb5bzp5mx/eZmX.grG92fRXo267QVVyRs0IE.V.zeCIw8S"
authorities="ROLE_USER" />
</sec:user-service>
<sec:password-encoder ref="passwordEncoder" />
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
--Définissez BCryptPasswordEncoder
comme bean et spécifiez-le dans l'attribut ref
de<password-encoder>
.
--Définissez <password-encoder>
comme élément enfant de <authentication-provider>
.
Java Configuration
MySpringSecurityConfig.java
package sample.spring.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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 org.springframework.security.crypto.password.PasswordEncoder;
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
...
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
public void configure(AuthenticationManagerBuilder auth, PasswordEncoder passwordEncoder) throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(passwordEncoder)
.withUser("hoge")
.password("$2a$08$CekzJRYhb5bzp5mx/eZmX.grG92fRXo267QVVyRs0IE.V.zeCIw8S")
.roles("USER");
}
}
PasswordEncoder
avec la méthode passwordEncoder ()
de ʻAbstractDaoAuthenticationConfigurer. --ʻAbstractDaoAuthenticationConfigurer
est la classe parente de ʻInMemoryUserDetailsManagerConfigurer, qui est le type de retour de la méthode ʻinMemoryAuthentication ()
de ʻAuthenticationManagerBuilder`.Pour encoder n'importe quel mot de passe, saisissez simplement PasswordEncoder
dans le conteneur et utilisez la méthode ʻencode ()`.
EncodePasswordServlet.java
package sample.spring.security.servlet;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/encode-password")
public class EncodePasswordServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(req.getServletContext());
PasswordEncoder passwordEncoder = context.getBean(PasswordEncoder.class);
String password = req.getParameter("password");
String encode = passwordEncoder.encode(password);
System.out.println(encode);
}
}
BCryptPasswordEncoder
est enregistré en tant que bean)PasswordEncoder
est explicitement obtenu à partir du conteneur pour une implémentation simple pour vérifier l'opération, mais en réalité, la référence est obtenue par DI le PasswordEncoder
au bean de gestion du conteneur.Une fois connecté, accédez à http: // localhost: 8080 / xxxx / encode-password? Password = fuga
(remplacez le chemin de contexte selon le cas).
Sortie de la console du serveur
$2a$10$2qVDkAqxp8eDrxR8Be2ZpubYGOCVZ7Qy9uK/XzOIY1ZoxpChtrWDK
Authentication ʻAuthentication` existe dans l'une des classes importantes qui composent Spring Security. Cette classe stocke des informations sur les utilisateurs authentifiés (tels que les noms d'utilisateur et une liste des autorisations accordées).
Si l'authentification par ʻAuthenticationProvider réussit, ʻAuthenticationProvider
crée et retourne un objet ʻAuthenticationauthentifié (ʻisAuthenticated ()
retourne true
).
Après cela, cet objet authentifié «Authentification» sera référencé dans un traitement ultérieur tel que la vérification des autorisations.
SecurityContextHolderSampleServlet.java
package sample.spring.security.servlet;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static java.util.stream.Collectors.*;
@WebServlet("/authentication")
public class SecurityContextHolderSampleServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println("[authorities]");
System.out.println(" " + auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(joining("\n ")));
WebAuthenticationDetails details = (WebAuthenticationDetails) auth.getDetails();
System.out.println("[details]");
System.out.println(" IP Address : " + details.getRemoteAddress());
System.out.println(" Session ID : " + details.getSessionId());
UserDetails principal = (UserDetails) auth.getPrincipal();
System.out.println("[principal]");
System.out.println(" username : " + principal.getUsername());
System.out.println(" password : " + principal.getPassword());
System.out.println("[credentials]");
System.out.println(" " + auth.getCredentials());
}
}
Résultat d'exécution
[authorities]
USER
[details]
IP Address : 0:0:0:0:0:0:0:1
Session ID : 0225051BCDFE2C34D55DF4FA9D9685C2
[principal]
username : hoge
password : null
[credentials]
null
SecurityContextHolder.getContext ()
renvoie le SecurityContext
associé à la requête courante.
--ʻAuthentication.getAuthorities () peut être utilisé pour obtenir les privilèges accordés à l'utilisateur actuellement connecté (une collection de
GrantedAuthority). --ʻAuthentication.getDetails ()
retourne WebAuthenticationDetails
par défaut.de l'utilisateur connecté avec ʻAuthentication.getPrincipal ()
.
--ʻAuthentication.getCredentials () `renvoie les informations utilisées pour l'authentification de l'utilisateur (généralement le mot de passe de connexion) par conception.Pour des raisons de sécurité, les informations de mot de passe sont explicitement supprimées (définies sur null
) par ʻAuthenticationManager (
ProviderManager) après une connexion réussie. Il est également possible de ne pas l'effacer avec l'option (ʻeraseCredentialsAfterAuthentication
).
Le SecurityContext
qui contient ʻAuthentication est sauvegardé dans
HttpSession` par défaut une fois l'authentification terminée.
La prochaine fois qu'il est accédé dans la même session, le SecurityContext
stocké dans HttpSession
sera récupéré et stocké dans SecurityContextHolder
.
Ce SecurityContextHolder
utilise ThreadLocal
par défaut pour stocker SecurityContext
dans le champ static
.
Par conséquent, le SecurityContext
associé à la requête actuelle peut être obtenu globalement.
Strictement parlant, SecurityContextHolder
contient une instance de SecurityContextHolderStrategy
, et l'implémentation de cette interface est ThreadLocalSecurityContextHolderStrategy
, qui stocke SecurityContext
dans ThreadLocal
.
À propos, SecurityContextPersistenceFilter
effectue le chargement et le déchargement de SecurityContext
vers SecurityContextHolder
et l'effacement de ThreadLocal
pour chaque requête.
En plus de ThreadLocalSecurityContextHolderStrategy
, il existe deux classes restantes dans l'implémentation de SecurityContextHolderStrategy
.
GlobalSecurityContextHolderStrategy
Enregistrez un seul «SecurityContext» dans votre application.
Cette classe est utilisée, par exemple, dans une application autonome. Pour les applications autonomes, l'utilisateur en cours d'exécution peut être le seul utilisateur. Dans ce cas, même si vous créez plusieurs threads, vous devrez peut-être partager les mêmes informations d'identification pour tous.
InheritableThreadLocalSecurityContextHolderStrategy
Lorsqu'un nouveau thread est créé, il partage le SecurityContext
du thread parent.
Cette classe peut être utilisée par plusieurs utilisateurs, mais elle est utilisée lorsqu'un nouveau thread est créé dans le traitement de chaque utilisateur pour exécuter le traitement en arrière-plan. Dans ce cas, les threads sont séparés, mais les informations d'identification doivent hériter des informations utilisateur du thread parent.
La méthode de stockage utilisée peut être modifiée par l'une des méthodes suivantes.
** Spécifié dans les propriétés système **
Il peut être spécifié en passant la propriété système spring.security.strategy
aux options de démarrage de la JVM.
Lors de la spécification au démarrage de Tomcat
> set JAVA_OPTS=-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL
> cd %TOMCAT_HOME%\bin
> startup.bat
** Précisez avec la méthode statique **
python
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
SecurityContextHolder
fournit une méthode appelée setStrategyName (String)
.
Pour l'argument, spécifiez également la constante définie dans SecurityContextHolder
.
Si vous activez la connexion par formulaire mais ne spécifiez pas de page de connexion, la page de connexion simple générée par Spring Security est utilisée par défaut.
Ceci est juste un écran pour vérifier l'opération, et dans l'application réelle, il est nécessaire de le remplacer par la page de connexion créée par vous-même.
Modifiez la page de connexion comme suit.
my-login.jsp
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>My Login Page</title>
</head>
<body>
<h1>My Login Page</h1>
<c:url var="loginUrl" value="/login" />
<form action="${loginUrl}" method="post">
<div>
<label>Nom d'utilisateur: <input type="text" name="username" /></label>
</div>
<div>
<label>mot de passe: <input type="password" name="password" /></label>
</div>
<input type="submit" value="login" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
</body>
</html>
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
<sec:http>
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/my-login.jsp" access="permitAll" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<sec:form-login login-page="/my-login.jsp" />
<sec:logout />
</sec:http>
...
</beans>
Java Configuration
MySpringSecurityConfig.java
...
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/my-login.jsp").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/my-login.jsp");
}
...
}
Si vous essayez d'accéder à une page, vous serez redirigé vers votre propre page de connexion.
applicationContext.xml
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/my-login.jsp" access="permitAll" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<sec:form-login login-page="/my-login.jsp" />
permitAll
.
--Spécifiez le chemin de votre propre page de connexion dans l'attribut login-page
de la balise <form-login>
.
--Pour la configuration Java, la méthode .loginPage (String)
.my-login.jsp
<c:url var="loginUrl" value="/login" />
<form action="${loginUrl}" method="post">
<div>
<label>Nom d'utilisateur: <input type="text" name="username" /></label>
</div>
<div>
<label>mot de passe: <input type="password" name="password" /></label>
</div>
<input type="submit" value="login" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
--Une demande de connexion peut être faite à / login
avec la méthode POST.
login-processing-url
de <form-login>
.
--Pour la configuration Java, la méthode loginProcessingUrl (String)
.et le mot de passe doit être
password`. et
password-parameterde
--Dans la configuration Java, il peut être modifié avec ʻusernameParameter (String)
,passwordParameter (String)
Par défaut, la destination de la transition après la connexion est contrôlée comme suit.
/
) (cible par défaut)hello.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello</title>
</head>
<body>
<h1>Hello!!</h1>
</body>
</html>
Ajoutez un écran simple.
Accédez à / hello.html
.
Vous serez redirigé vers l'écran de connexion, alors connectez-vous.
Vous serez redirigé vers la page (/ hello.html
) à laquelle vous accédiez avant de vous connecter.
Ensuite, déconnectez-vous une fois, puis accédez directement à la page de connexion (/ login
).
s'identifier.
Il est ignoré à la racine de l'application (/
).
Vous pouvez changer la destination de la transition après avoir ouvert la page de connexion directement et vous être connecté avec default-target-url
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
<sec:http>
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<sec:form-login default-target-url="/hello.html" />
<sec:logout />
</sec:http>
...
</beans>
--Spécifié par default-target-url
Java Configuration
MySpringSecurityConfig.java
...
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().defaultSuccessUrl("/hello.html");
}
...
}
--Spécifié par defaultSuccessUrl (String)
Accédez directement à l'écran de connexion (/ login
).
s'identifier
Vous serez passé à l'URL (/ hello.html
) spécifiée par default-target-url
.
Même s'il y a une page à laquelle vous avez essayé d'accéder avant de vous connecter, modifiez-la pour qu'elle passe à la cible par défaut après la connexion.
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
<sec:http>
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<sec:form-login default-target-url="/hello.html"
always-use-default-target="true" />
<sec:logout />
</sec:http>
...
</beans>
--Spécifiez true
pour ʻalways-use-default-target`
Java Configuration
MySpringSecurityConfig
...
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().defaultSuccessUrl("/hello.html", true);
}
...
}
--Spécifiez true
comme deuxième argument de defaultSuccessUrl (String, boolean)
Accédez à l'URL appropriée.
Vous serez redirigé vers l'écran de connexion, alors connectez-vous.
Vous serez renvoyé à la page spécifiée par default-target-url
.
MyAuthenticationSuccessHandler.java
package sample.spring.security.handler;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
redirectStrategy.sendRedirect(request, response, "/hello.html");
}
}
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
<bean id="authenticationSuccessHandler"
class="sample.spring.security.handler.MyAuthenticationSuccessHandler" />
<sec:http>
...
<sec:form-login authentication-success-handler-ref="authenticationSuccessHandler" />
...
</sec:http>
...
</beans>
--Spécifiez le bean du ʻAuthenticationSuccessHandler créé dans l'attribut ʻauthentication-success-handler-ref
de la balise<form-login>
.
Java Configuration
MySpringSecurityConfig.java
...
import sample.spring.security.handler.MyAuthenticationSuccessHandler;
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
...
.and()
.formLogin().successHandler(new MyAuthenticationSuccessHandler());
}
...
}
--Spécifiez une instance de ʻAuthenticationSuccessHandler créée par la méthode
successHandler () `
L'état de fonctionnement est omis car il ne redirige que vers / hello.html
.
Si vous spécifiez la cible par défaut et ʻAuthenticationSuccessHandler` en même temps, cela fonctionne comme suit.
L'espace de noms est priorisé pour déterminer les paramètres, mais la configuration Java écrase les paramètres lorsque la méthode est appelée, donc cette différence est faite.
MyAuthenticationFailureHandler.java
package sample.spring.security.handler;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(
HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
redirectStrategy.sendRedirect(request, response, "/login");
}
}
--Créez une classe qui implémente ʻAuthenticationFailureHandler`
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
<bean id="authenticationFailureHandler"
class="sample.spring.security.handler.MyAuthenticationFailureHandler" />
<sec:http>
...
<sec:form-login authentication-failure-handler-ref="authenticationFailureHandler"/>
...
</sec:http>
...
</beans>
--Spécifiez le bean du ʻAuthenticationFailureHandler créé avec ʻauthentication-failure-handler-ref
de<form-login>
.
Java Configuration
MySpringSecurityConfig.java
...
import sample.spring.security.handler.MyAuthenticationFailureHandler;
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.failureHandler(new MyAuthenticationFailureHandler());
}
...
}
--Spécifiez un gestionnaire avec failureHandler ()
L'opération est omise car elle revient simplement à l'écran de connexion.
ʻOnAuthenticationFailure () de ʻAuthenticationFailureHandler
peut recevoir l'exception qui s'est produite lors de l'authentification à la fin de l'argument.
En examinant cette exception, la cause de l'échec de connexion peut être identifiée.
Si vous souhaitez ajuster le message d'erreur lorsque le compte est verrouillé, je pense que vous pouvez le contrôler ici.
exception | situation |
---|---|
BadCredentialsException |
L'utilisateur n'existe pas, le mot de passe est incorrect, etc. |
LockedException |
UserDetails.isAccountNonLocked() Maisfalse Revenu |
DisabledException |
UserDetails.isEnabled() Maisfalse Revenu |
AccountExpiredException |
UserDetails.isAccountNonExpired() Maisfalse Revenu |
CredentialsExpiredException |
UserDetails.isCredentialsNonExpired() Maisfalse Revenu |
SessionAuthenticationException |
Lorsque le nombre de sessions dépasse la limite supérieure (Détails à une date ultérieure) |
Hello World
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
...>
...
<sec:http>
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/**" access="isAuthenticated() and hasAuthority('USER')" />
<sec:form-login />
<sec:logout />
</sec:http>
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user
name="hoge"
password="hoge"
authorities="USER" />
<sec:user
name="fuga"
password="fuga"
authorities="" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
Java Configuration
MySpringSecurityConfig.java
package sample.spring.security;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.Collections;
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().access("isAuthenticated() and hasAuthority('USER')")
.and()
.formLogin();
}
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("hoge")
.password("hoge")
.authorities("USER")
.and()
.withUser("fuga")
.password("fuga")
.authorities(Collections.emptyList());
}
}
Connectez-vous en tant qu'utilisateur hoge
La première page peut être affichée.
Puis connectez-vous en tant qu'utilisateur fuga
403 J'obtiens une erreur.
applicationContext.xml
<sec:http>
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/**" access="isAuthenticated() and hasAuthority('USER')" />
<sec:form-login />
<sec:logout />
</sec:http>
--Définissez les conditions requises pour l'accès avec l'attribut ʻaccess de
hasAuthority ()
etc.) et des constantes (permitAll
) que Spring Security a étendues indépendamment.
--ʻIsAuthenticated () sera
true si authentifié et
hasAuthority ('USER') sera
true si l'utilisateur a l'autorité ʻUSER
(connectez-vous avec ʻet`) Les deux doivent être satisfaits).applicationContext.xml
<sec:user
name="hoge"
password="hoge"
authorities="USER" />
<sec:user
name="fuga"
password="fuga"
authorities="" />
--ʻLa chaîne de caractères spécifiée dans l'attribut des autoritésdevient l'autorité de l'utilisateur. --Si vous en spécifiez plusieurs, séparez-les par
, (exemple: ʻUTILISATEUR, ADMIN
).
Le processus d'autorisation basé sur l'URL est effectué à la fin de la série de processus "Filtre".
Cela est fait par ʻAccessDecisionManager dans le processus de
FilterSecurityInterceptor`.
L'implémentation de ʻAccessDecisionManager` fournie par Spring Security en standard détermine la disponibilité de l'accès par ** "vote" **.
La classe d'implémentation de ʻAccessDecisionManager peut avoir plusieurs classes de vote appelées ʻAccessDecisionVoter
.
Ensuite, chaque ʻAccessDecisionVoter est fait pour vérifier (voter) si l'utilisateur actuel a ou non les droits d'accès à la cible (objet sécurisé). Le résultat du vote sera l'un des suivants: "accorder (ʻACCESS_GRANTED
)", "rejet (ʻACCESS_DENIED)" et "retrait (ʻACCESS_ABSTAIN
)".
La classe d'implémentation de ʻAccessDecisionManager regroupe les résultats de vote de ʻAccessDecisionVoter
et décide de l'accès final.
Il existe trois classes d'implémentation de ʻAccessDecisionManager, ʻAffirmativeBased
, ConsensusBased
et ʻUnanimousBased`, selon la méthode d'agrégation.
AffirmativeBased
--S'il y a ne serait-ce qu'un vote pour "accorder", le droit d'accès est accordé.ConsensusBased
UnanimousBased
La valeur par défaut est ʻAffirmativeBased`.
«Les trois classes d'implémentation par défaut d'AccessDecisionManager» ne déterminent pas directement la présence ou l'absence de droits d'accès. C'est la classe d'électeurs qui implémente l'interface ʻAccessDecisionVoter` qui détermine en fait la présence ou l'absence de droits d'accès.
L'interface ʻAccessDecisionVoter` est définie comme suit (seulement certains).
AccessDecisionVoter
package org.springframework.security.access;
import java.util.Collection;
import org.springframework.security.core.Authentication;
public interface AccessDecisionVoter<S> {
int ACCESS_GRANTED = 1;
int ACCESS_ABSTAIN = 0;
int ACCESS_DENIED = -1;
...
int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);
}
Il existe une méthode pour voter «vote ()».
Cette méthode détermine l'existence d'un droit d'accès à partir des informations de l'argument reçu, et retourne l'une des constantes (ʻACCESS_GRANTED, ʻACCESS_ABSTAIN
, ʻACCESS_DENIED) également définies dans ʻAccessDecisionVoter
.
Chaque argument a la signification suivante.
authentication
object
Filter
, FilterInvocation
--Pour la sécurité des méthodes, MethodInvocation
attributes
) définie dans l'attribut ʻaccess
de la balise <intercept-url>
.La classe d'implémentation de ʻAccessDecisionVoter obtient les privilèges accordés à l'utilisateur à partir de ʻauthentication
.
Ensuite, on évalue si l'utilisateur a ou non les droits d'accès en comparant avec le paramètre de sécurité passé dans «attributs».
Si true
est spécifié dans l'attribut ʻuse-expressions de la balise
(la valeur par défaut est
true),
WebExpressionVoter est utilisé pour implémenter ʻAccessDecisionVoter
.
Cette classe est une expression, un électeur qui évalue une expression (SpEL) pour déterminer l'accès.
ʻAccessDecisionVoter a également
RoleVoteret
PreInvocationAuthorizationAdviceVoter` utilisés dans la sécurité des méthodes.
L'interface ʻAuthentication définit une méthode appelée
getAuthorities () . Cette méthode retourne une collection de
GrantedAuthority`.
GrantedAuthority
est aussi une interface, comme son nom l'indique " permissions accordées à l'utilisateur ".
GrantedAuthority
n'a qu'une seule méthode définie qui renvoie String
appeléegetAuthority ()
.
Cette méthode est conçue pour renvoyer "une représentation sous forme de chaîne des autorisations représentées par une instance de" GrantedAuthority "" [^ 2].
En bref, c'est une chaîne de caractères qui représente une telle autorité, telle que «ROLE_USER» ou «OP_REGISTER_ITEM».
[^ 2]: De par sa conception, si l'autorité ne peut pas être exprimée par une simple chaîne de caractères, «null» est renvoyé. Cependant, l'implémentation standard (telle que SimpleGrantedAuthority
) renvoie essentiellement une chaîne de caractères, donc l'explication de ce cas est omise ici.
Dans Hello World, la valeur définie dans l'attribut ʻauthorities de la balise
est
GrantedAuthority`.
applicationContext.xml
<sec:user
name="hoge"
password="hoge"
authorities="USER" />★ Ce
Les informations d'autorisation définies ici sont initialement stockées dans la classe qui implémente ʻUserDetets. Ensuite, lorsque le processus d'authentification est effectué par ʻAuthenticationProvider
, il est acquis par la méthodegetAuthorities ()
de ʻUserDetails et stocké dans l'instance ʻAuthentication
.
--FilterSecurityInterceptor
indique à ʻAccessDecisionManager (ʻAffirmativeBased
) de déterminer le droit d'accès.
--ʻAffirmativeBased délègue la détermination des droits d'accès à ʻAccessDecisionVoter
(WebExpressionVoter
).
--WebExpressionVoter
obtient une collection de GrantedAuthority
de ʻAuthentication. --
WebExpressionVoter exécute l'expression conditionnelle (SpEL) à partir des informations d'argument et des informations
GrantedAuthority`.
regroupe les résultats du vote des électeurs et détermine si vous avez ou non des droits d'accès. --S'il est déterminé que vous n'avez pas de droits d'accès, ʻAccessDeniedException
est levé.Si WebExpressionVoter
est utilisé pour implémenter ʻAccessDecisionVoter, le contrôle d'accès basé sur l'expression peut être défini dans l'attribut ʻaccess
de la balise <intercept-url>
.
Spring Expression Language (SpEL) est utilisé pour les expressions utilisées ici. Spring Security a été étendu pour vous permettre d'utiliser vos propres fonctions et variables afin de faciliter la définition des contrôles d'accès.
Écrivez cette expression pour qu'elle soit finalement évaluée à un seul booléen
.
Ensuite, si le résultat de l'évaluation de l'expression est «vrai», l'accès est autorisé, et s'il est «faux», l'accès est refusé.
Fonctions / variables | Vérifier le contenu / les valeurs | Exemple |
---|---|---|
hasRole(String) |
A le rôle spécifié | hasRole('USER') |
hasAnyRole(String...) |
A l'un des rôles spécifiés | hasAnyRole('FOO', 'ROLE_BAR') |
hasAuthority(String) |
Avoir l'autorité spécifiée | hasAuthority('CREATE_TICKET') |
hasAnyAuthority(String...) |
Avoir l'une des autorisations spécifiées | hasAnyAuthority('CREATE_TICKET', 'MANAGE_TICKET') |
isAnonymous() |
Être authentifié de manière anonyme | - |
isRememberMe() |
Remember-Moi certifié | - |
isAuthenticated() |
Doit être certifié | - |
isFullyAuthenticated() |
Soyez entièrement authentifié | - |
permitAll |
toujourstrue Est évalué comme |
- |
denyAll |
toujoursfalse Est évalué comme |
- |
La relation entre ʻisAnonymous () , ʻisRememberMe ()
, ʻisAuthenticated () , ʻisFullyAuthenticated ()
est la suivante.
Authentification anonyme | Remember-Certification moi | Certification complète[^3] | |
---|---|---|---|
isAnonymous() |
true | false | false |
isRememberMe() |
false | true | false |
isAuthenticated() |
false | true | true |
isFullyAuthenticated() |
false | false | true |
[^ 3]: Identifiants (mot de passe, etc.) vérifiés avec succès et authentifiés garantis (en bref, après vous être connecté normalement à Form)
hasRole ()
et hasAuthority ()
Parmi les fonctions qui peuvent être utilisées avec WebExpressionVoter
, il y a hasRole () ʻet
hasAuthority () ʻen tant que fonctions pour vérifier l'autorisation.
Les deux ne changent pas le point de vérification de l'existence de «GrantedAuthority» donné à l'utilisateur.
La différence est que «hasRole ()» complète le préfixe «ROLE_».
Droits des utilisateurs → | USER |
ROLE_USER |
---|---|---|
hasRole('USER') |
false | true |
hasRole('ROLE_USER') |
false | true |
hasAuthority('USER') |
true | false |
hasAuthority('ROLE_USER') |
false | true |
hasAuthority ()
vérifie que la représentation sous forme de chaîne de GrantedAuthority
correspond exactement.
D'un autre côté, hasRole ()
complète ROLE_
et le compare avec GrantedAuthority
si la chaîne passée en argument ne commence pas par ROLE_
.
(HasRole ('USER')
complète ROLE_
et se compare à ROLE_USER
)
Quelle est la différence entre le rôle et l'autorité? Rien ne change. La seule différence est de savoir s'il commence par «ROLE_» ou non, les deux étant en fait «GrantedAuthority».
La raison pour laquelle celui avec le préfixe ROLE_
est traité spécialement est que ** c'est ma supposition **, mais cela est dû au fait que Voter
appelé RoleVoter
a été utilisé dans le passé. Je ressens.
«WebExpressionVoter» a été ajouté à la version 3.0, et avant cela, il semble que la combinaison de ** peut-être ** «RoleVoter» et «AuthenticatedVoter» ait été utilisée.
Si vous utilisez un espace de noms, vous pouvez le faire en définissant ʻuse-expression dans
sur
false`.
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
...>
<sec:http use-expressions="false">
<sec:intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<sec:intercept-url pattern="/**" access="ROLE_ADMIN" />
<sec:form-login />
<sec:logout />
</sec:http>
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user
name="user"
password="user"
authorities="ROLE_USER" />
<sec:user
name="admin"
password="admin"
authorities="ROLE_ADMIN" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
SpEL ne peut pas être utilisé pour ʻaccès de
. Au lieu de cela, écrivez les paramètres pour ʻAuthenticatedVoter
et RoleVoter
.
ʻIS_AUTHENTICATED_ANONYMOUSLY est un paramètre pour ʻAuthenticatedVoter
pour vérifier l'authentification anonyme (c'est-à-dire que c'est OK même si vous ne vous êtes pas connecté).
De plus, ROLE_ADMIN
vérifie si le paramètre de RoleVoter
a l'autorité ROLE_ADMIN
.
Puisque pattern =" / login "
n'a que le paramètre pour ʻAuthenticatedVoter, la vérification par
RoleVoter est inutile. Cependant, ʻAccessDecisionManager
ne peut pas juger si c'est nécessaire ou non [^ 4], donc pour le moment, chaque ʻAccessDecisionVoter est invité à voter (ici, ʻAuthenticatedVoter
et RoleVoter
sont Voter).
[^ 4]: ʻAccessDecisionManager ne sait pas (ne sait pas) si la classe réelle derrière l'interface ʻAccessDecisionVoter
est RoleVoter
Que ce soit nécessaire ou non est déterminé par la méthode «supports (ConfigAttribute)» de chaque «électeur». Voici la méthode «supports ()» de «RoleVoter».
RoleVoter soutient()Méthode
public boolean supports(ConfigAttribute attribute) {
if ((attribute.getAttribute() != null)
&& attribute.getAttribute().startsWith(getRolePrefix())) {
return true;
}
else {
return false;
}
}
Pour ʻattribute, la valeur spécifiée par l'attribut ʻaccess
est passée (si plusieurs sont spécifiés séparés par des virgules, ils sont passés un par un).
getRolePrefix ()
retourne"ROLE_"
.
En d'autres termes, il vérifie si la valeur spécifiée par l'attribut ʻaccess commence par
ROLE_`, et si elle démarre, elle la prend en charge, sinon elle ne la prend pas en charge.
S'il n'est pas pris en charge, «RoleVoter» renverra un «retrait» à la suite du vote.
De cette façon, en faisant commencer le nom de l'autorisation par ROLE_
, RoleVoter
n'effectuera pas de traitement de vérification d'autorisation inutile.
De même, ʻAuthenticatedVoter ne traite pas à moins que la valeur spécifiée par ʻaccess
soit une chaîne de caractères spécifique. En d'autres termes, les valeurs de définition commençant par «ROLE_» ne sont pas prises en charge et ne sont pas traitées.
De cette manière, chaque «électeur» contrôle la présence ou l'absence de soutien afin qu'aucun jugement inutile ne soit émis. Le résultat est un préfixe spécial appelé «ROLE_».
Cependant, maintenant que «WebExpressionVoter» est apparu, il est devenu possible de définir de manière flexible avec SpEL, et je pense que le besoin de combiner plusieurs «Voter» a diminué.
(En fait, même si vous essayez de combiner WebExpressionVoter
et RoleVoter
, si vous écrivez ʻaccess =" ROLE_USER "`, une erreur se produira lors de l'analyse de l'expression SpEL, et vous ne pourrez pas les combiner en premier lieu.)
MyExpression.java
package sample.spring.security.expression;
import org.springframework.security.core.Authentication;
public class MyExpression {
public boolean check(Authentication authentication) {
String name = authentication.getName();
System.out.println("name = " + name);
return "hoge".equals(name);
}
}
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
<bean id="myExpression" class="sample.spring.security.expression.MyExpression" />
<sec:http>
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/**" access="@myExpression.check(authentication)" />
<sec:form-login />
<sec:logout />
</sec:http>
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user
name="hoge"
password="hoge"
authorities="" />
<sec:user
name="fuga"
password="fuga"
authorities="" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
Java Configuration
MySpringSecurityConfig.java
package sample.spring.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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 sample.spring.security.expression.MyExpression;
import java.util.Collections;
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().access("@myExpression.check(authentication)")
.and()
.formLogin();
}
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("hoge")
.password("hoge")
.authorities(Collections.emptyList())
.and()
.withUser("fuga")
.password("fuga")
.authorities(Collections.emptyList());
}
@Bean
public MyExpression myExpression() {
return new MyExpression();
}
}
Lors de l'accès par hoge
sur la gauche, lors de l'accès par fuga
sur la droite
Journal du serveur
name = hoge
name = fuga
MyExpression.java
package sample.spring.security.expression;
import org.springframework.security.core.Authentication;
public class MyExpression {
public boolean check(Authentication authentication) {
String name = authentication.getName();
System.out.println("name = " + name);
return "hoge".equals(name);
}
}
applicationContext.xml
<sec:http>
...
<sec:intercept-url pattern="/**" access="@myExpression.check(authentication)" />
...
</sec:http>
@bean name
Pour permitAll
et hasAuthority ()
, [SecurityExpressionRoot](http://docs.spring.io/autorepo/docs/spring-security/4.2.x-SNAPSHOT/apidocs/org/springframework/security/access/ Il est défini dans expression / SecurityExpressionRoot.html).
En outre, lorsque le contrôle d'accès à Filter
est traité, [WebSecurityExpressionRoot](http://docs.spring.io/autorepo/docs/spring-security/4.2.x-SNAPSHOT/apidocs/org/springframework/security/ web / access / expression / WebSecurityExpressionRoot.html) est utilisé.
AcceptFugaVoter.java
package sample.spring.security.voter;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class AcceptFugaVoter implements AccessDecisionVoter<Object> {
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
@Override
public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
Object principal = authentication.getPrincipal();
if (!(principal instanceof UserDetails)) {
return ACCESS_ABSTAIN;
}
String username = ((UserDetails)principal).getUsername();
return "fuga".equals(username) ? ACCESS_GRANTED : ACCESS_DENIED;
}
}
Un électeur bâclé qui autorise l'accès si le nom d'utilisateur est «fuga».
namespace
application.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
<bean id="accessDecisionManager"
class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg>
<list>
<bean class="org.springframework.security.web.access.expression.WebExpressionVoter" />
<bean class="sample.spring.security.voter.AcceptFugaVoter" />
</list>
</constructor-arg>
</bean>
<sec:http access-decision-manager-ref="accessDecisionManager">
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/**" access="hasAuthority('HOGE')" />
<sec:form-login />
<sec:logout />
</sec:http>
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user
name="hoge"
password="hoge"
authorities="HOGE" />
<sec:user
name="fuga"
password="fuga"
authorities="" />
<sec:user
name="piyo"
password="piyo"
authorities="" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
Java Configuration
python
package sample.spring.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.vote.AffirmativeBased;
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.web.access.expression.WebExpressionVoter;
import sample.spring.security.voter.AcceptFugaVoter;
import java.util.Arrays;
import java.util.Collections;
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.accessDecisionManager(this.createAccessDecisionManager())
.antMatchers("/login").permitAll()
.anyRequest().hasAuthority("HOGE")
.and()
.formLogin();
}
private AccessDecisionManager createAccessDecisionManager() {
return new AffirmativeBased(Arrays.asList(
new WebExpressionVoter(),
new AcceptFugaVoter()
));
}
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("hoge")
.password("hoge")
.authorities("HOGE")
.and()
.withUser("fuga")
.password("fuga")
.authorities(Collections.emptyList())
.and()
.withUser("piyo")
.password("piyo")
.authorities(Collections.emptyList());
}
}
De gauche à droite, l'état après la connexion en tant qu'utilisateur hoge
, fuga
, piyo
.
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
<bean id="accessDecisionManager"
class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg>
<list>
<bean class="org.springframework.security.web.access.expression.WebExpressionVoter" />
<bean class="sample.spring.security.voter.AcceptFugaVoter" />
</list>
</constructor-arg>
</bean>
<sec:http access-decision-manager-ref="accessDecisionManager">
...
</sec:http>
...
</beans>
avec la balise
ʻaccess-decision-manager-ref
.
--Pour la configuration Java, il peut être spécifié avec la méthode ʻaccessDecisionManager () . --Passez ʻAccessDecisionManager
avec votre propreVoter
défini ici.
--Spécifiez Voter
dans le constructeur de ʻAffirmativeBased`.ConsensusBased
et ʻUnanimousBased`.Le rôle a généralement une relation d'héritage.
Par exemple, s'il existe des rôles pour «utilisateur» et «administrateur», «administrateur» hérite généralement de «utilisateur». En d'autres termes, ce qu'un «utilisateur» peut faire peut également être fait par un «administrateur».
Cependant, si vous essayez de contrôler cela simplement avec ou sans rôle, les paramètres seront les suivants.
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
...>
<sec:http>
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/hierarchy/admin" access="hasRole('ADMIN')" />
<sec:intercept-url pattern="/hierarchy/user" access="hasRole('USER') or hasRole('ADMIN')" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<sec:form-login />
<sec:logout />
</sec:http>
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user
name="user"
password="user"
authorities="ROLE_USER" />
<sec:user
name="admin"
password="admin"
authorities="ROLE_ADMIN" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
/ hierarchy / admin
n'est accessible qu'à l '" administrateur ", et / hierarchy / user
est accessible à l'" utilisateur ".
Pour / hierarchy / admin
, simplement hasRole ('ADMIN')
.
Cependant, / hierarchy / user
est hasRole ('USER') ou hasRole ('ADMIN')
.
Si seul hasRole ('USER')
est utilisé, il se bloquera lors de l'accès par un utilisateur qui n'a que ROLE_ADMIN
, il s'agit donc d'un paramètre redondant.
De cette façon, si vous essayez de réaliser la relation d'héritage du rôle simplement avec ou sans rôle, définissez le rôle enfant (ROLE_ADMIN
) dans tous les endroits où le rôle parent ( ROLE_USER
) est spécifié. Tu vas devoir.
Il existe également un moyen de définir ʻaccess uniquement sur
hasRole ('USER') , et de définir ʻADMIN
sur ʻauthorities, ainsi que de définir ʻUSER
.
Cependant, dans tous les cas, il n'y a aucune différence en ce que la description est redondante.
Spring Security fournit un mécanisme pour définir les relations d'héritage des rôles.
Le paramètre précédent peut être réécrit comme suit en utilisant RoleHierarchy
.
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
...>
<bean id="roleHierarchy"
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
<property name="hierarchy">
<value>
ROLE_ADMIN > ROLE_USER
</value>
</property>
</bean>
<bean id="expressionHandler"
class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler">
<property name="roleHierarchy" ref="roleHierarchy" />
</bean>
<sec:http>
<sec:expression-handler ref="expressionHandler" />
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/hierarchy/admin" access="hasRole('ADMIN')" />
<sec:intercept-url pattern="/hierarchy/user" access="hasRole('USER')" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<sec:form-login />
<sec:logout />
</sec:http>
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user
name="user"
password="user"
authorities="ROLE_USER" />
<sec:user
name="admin"
password="admin"
authorities="ROLE_ADMIN" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
Java Configuration
MySpringSecurityConfig.java
package sample.spring.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
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.web.FilterInvocation;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.expressionHandler(this.createSecurityExpressionHandler())
.antMatchers("/login").permitAll()
.antMatchers("/hierarchy/user").hasRole("USER")
.antMatchers("/hierarchy/admin").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin();
}
private SecurityExpressionHandler<FilterInvocation> createSecurityExpressionHandler() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
return expressionHandler;
}
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("user")
.roles("USER")
.and()
.withUser("admin")
.password("admin")
.roles("ADMIN");
}
}
Connectez-vous en tant que ʻuser et ʻadmin
pour accéder respectivement à / hierarchy / user
et à / hierarchy / admin
.
** Lorsque vous êtes connecté en tant qu'utilisateur **
** Si vous vous connectez en tant qu'administrateur **
Définition de RoleHierarchyImpl
<bean id="roleHierarchy"
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
<property name="hierarchy">
<value>
ROLE_ADMIN > ROLE_USER
</value>
</property>
</bean>
RoleHierarchyImpl
comme classe d'implémentation de l'interface RoleHierarchy
.hierarchy
.nom du rôle enfant> nom du rôle parent
.Lors de la définition de plus d'un
ROLE_A > ROLE_B
ROLE_B > ROLE_C
Vous pouvez le faire comme ceci (il n'y a pas besoin de sauts de ligne, mais c'est plus facile à voir).
Définissez RoleHierarchyImpl à utiliser dans l'évaluation des expressions
<bean id="roleHierarchy"
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
...
</bean>
<bean id="expressionHandler"
class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler">
<property name="roleHierarchy" ref="roleHierarchy" />
</bean>
<sec:http>
<sec:expression-handler ref="expressionHandler" />
...
</sec:http>
Ensuite, définissez cette RoleHierarchyImpl
à utiliser lorsque la SpEL est évaluée.
Pour ce faire, enregistrez SecurityExpressionHandler
en tant que bean.
Utilisez DefaultWebSecurityExpressionHandler
comme classe d'implémentation et spécifiez le RoleHierarchyImpl
défini précédemment dans la propriété roleHierarchy
.
Ajoutez ensuite «
Utilisez RoleHierarchyVoter
sans utiliser WebExpressionVoter
.
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
...>
<bean id="roleHierarchy"
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
<property name="hierarchy">
<value>
ROLE_ADMIN > ROLE_USER
</value>
</property>
</bean>
<bean id="roleHierarchyVoter"
class="org.springframework.security.access.vote.RoleHierarchyVoter">
<constructor-arg ref="roleHierarchy" />
</bean>
<bean id="accessDecisionManager"
class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg>
<list>
<bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
<ref bean="roleHierarchyVoter" />
</list>
</constructor-arg>
</bean>
<sec:http use-expressions="false"
access-decision-manager-ref="accessDecisionManager">
<sec:intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<sec:intercept-url pattern="/hierarchy/admin" access="ROLE_ADMIN" />
<sec:intercept-url pattern="/hierarchy/user" access="ROLE_USER" />
<sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
<sec:form-login />
<sec:logout />
</sec:http>
<sec:authentication-manager>
<sec:authentication-provider>
<sec:user-service>
<sec:user
name="user"
password="user"
authorities="ROLE_USER" />
<sec:user
name="admin"
password="admin"
authorities="ROLE_ADMIN" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
</beans>
access-denied.html
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Erreur d'autorisation</title>
</head>
<body>
<h1>L'opération n'est pas autorisée!</h1>
</body>
</html>
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
<sec:http>
<sec:intercept-url pattern="/login" access="permitAll" />
<sec:intercept-url pattern="/test" access="hasAuthority('HOGE')" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
<sec:form-login />
<sec:logout />
<sec:access-denied-handler error-page="/access-denied.html" />
</sec:http>
...
</beans>
Java Configuration
MySpringSecurityConfig.java
...
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
...
.and()
.exceptionHandling().accessDeniedPage("/access-denied.html");
}
...
}
Après vous être connecté avec hoge
, accédez à / test
applicationContext.xml
<sec:access-denied-handler error-page="/access-denied.html" />
MySpringSecurityConfig.java
.exceptionHandling().accessDeniedPage("/access-denied.html");
--Pour l'espace de noms, ajoutez la balise <access-denied-handler>
et spécifiez-la avec l'attribut ʻerror-page. --Pour la configuration Java, spécifiez avec ʻexceptionHandler (). AccessDeniedPage (String)
MyAccessDeniedHandler.java
package sample.spring.security.handler;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
if (response.isCommitted()) {
return;
}
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
redirectStrategy.sendRedirect(request, response, "/access-denied.html");
}
}
--Créez une classe qui implémente AccessDeniedHandler
et implémentez le traitement au moment de l'erreur d'autorisation dans la méthode handle ()
.
/ access-refusé.html
namespace
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
...>
<bean id="accessDeniedHandler"
class="sample.spring.security.handler.MyAccessDeniedHandler" />
<sec:http>
...
<sec:access-denied-handler ref="accessDeniedHandler" />
</sec:http>
...
</beans>
--Spécifiez le bean de ʻAccessDeniedHandler dans l'attribut
refde
Java Configuration
MySpringSecurityConfig.java
...
import sample.spring.security.handler.MyAccessDeniedHandler;
@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
...
.and()
.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
}
...
}
avec ʻaccessDeniedHandler ()
.Fonctionne de la même manière que lorsque ʻerror-page` a été spécifié.
Vous pouvez maintenant voir que l'URL a changé et est maintenant une redirection.
Recommended Posts