[JAVA] [Inverser] Spring Security (mis à jour de temps en temps)

Aperçu

Lors de l'utilisation de Spring Security, nous l'avons résumé sous la forme de reverse pull. La réponse ici n'est pas la bonne réponse, ce n'est qu'une méthode, donc j'apprécierais que vous puissiez vous y référer lorsque vous n'êtes pas sûr de la mise en œuvre.

La référence sera mise à jour de temps à autre.

environnement

article version
Java 8
Spring Boot 2.2.4

Préparation

Vous trouverez ci-dessous le code minimum requis.

Limiter toutes les demandes



@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated();
    }
}

Puisque nous ne nous connecterons pas à la base de données cette fois, nous utiliserons les informations d'authentification suivantes.

application.yml


spring:
    security:
        user:
            name: user
            password: pass
            roles: USER

Remarques

Résolution inverse

Édition commune

Je ne veux pas limiter certains chemins

SecurityConfig.java



@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/","/login") // 「/」「/"login" est accessible sans authentification
                .permitAll()
                .anyRequest()
                .authenticated();
    }
}

Je veux désactiver CSRF

CSRF est activé par défaut.

SecurityConfig.java



@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated();
        http.csrf().disable();
    }
}

Je souhaite accepter des demandes de l'extérieur (sites d'origines différentes)

SecurityConfig.java



@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated();
        http.cors().configurationSource(getCorsConfigurationSource());
    }

    private CorsConfigurationSource getCorsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //Autoriser toutes les méthodes
        corsConfiguration.addAllowedMethod(CorsConfiguration.ALL);
        //Autoriser tous les en-têtes
        corsConfiguration.addAllowedHeader(CorsConfiguration.ALL);
        //Autoriser toutes les origines
        corsConfiguration.addAllowedOrigin(CorsConfiguration.ALL);

        UrlBasedCorsConfigurationSource corsSource = new UrlBasedCorsConfigurationSource();
        //Peut être défini pour chaque chemin. Ici défini pour tous les chemins
        corsSource.registerCorsConfiguration("/**", corsConfiguration);

        return corsSource;
    }
}

Je souhaite personnaliser le processus d'authentification

authenticationproviderCréez une classe d'implémentation pour.

CustomeAuthenticationProvider.java


@Configuration
public class CustomeAuthenticationProvider implements AuthenticationProvider {

    //Processus d'authentification
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = (String) authentication.getPrincipal();
        String password = (String) authentication.getCredentials();
        if (!"user".equals(username) || !"password".equals(password)) {
            throw new BadCredentialsException("Informations de connexion incorrectes");
        }
        return new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

}

securityconfigSpécifiez le fournisseur créé ci-dessus dans.

SecurityConfig.java


@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomeAuthenticationProvider authenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated();

        http.formLogin();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //Spécifiez le fournisseur créé ci-dessus
        auth.authenticationProvider(authenticationProvider);
    }
}

Certification de formulaire

SecurityConfig.java



@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated();
        http.formLogin();
    }
}

/loginLorsque vous y accédez, l'écran de connexion préparé par spring security s'affiche.

Je souhaite utiliser mon propre écran de connexion

Préparez le HTML pour l'écran de connexion.

login.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>S'identifier</title>
</head>
<body>
    <form th:action="@{/login}" method="post">
        <input type="text" name="username">
        <input type="password" name="password">
        <button type="submit">S'identifier</button>
    </form>
</body>
</html>

SecurityConfig.java



@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("(A)") //ajouter à
                .permitAll()         //ajouter à
                .anyRequest()
                .authenticated()
        http.formLogin()
                .loginPage("(A)");  //ajouter à
    }
}

Je souhaite modifier les paramètres d'authentification

Par défaut, vous l'obtenez avec les noms de paramètres username et `` `password```.

SecurityConfig.java



@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/(A)") //① passer
                .permitAll()
                .anyRequest()
                .authenticated()
        http.formLogin()
                .loginPage("/(A)")
                .usernameParameter("(B)")  //ajouter à
                .passwordParameter("(C)"); //ajouter à
    }
}

login.html



<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>S'identifier</title>
</head>
<body>
    <form th:action="@{/login}" method="post">
        <input type="text" name="(B)">
        <input type="password" name="(C)">
        <button type="submit">S'identifier</button>
    </form>
</body>
</html>

Je veux changer l'écran qui transite lorsque l'authentification est réussie

Par défaut, il passe à "/".

SecurityConfig.java



@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
        http.formLogin()
                .defaultSuccessUrl("/home"); // 「/Transition vers la «maison»
    }
}

Je souhaite m'authentifier avec 3 paramètres ou plus

Normalement, vous êtes authentifié avec deux paramètres, username et `` `password```. Il existe plusieurs méthodes, mais voici la méthode d'authentification par formulaire.

Que créer

Préparez un écran de connexion pour entrer les trois paramètres.

login.html



<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>S'identifier</title>
</head>
<body>
    <form th:action="@{/login}" method="post">
        <input type="text" name="(A)">
        <input type="text" name="username">
        <input type="password" name="password">
        <button type="submit">S'identifier</button>
    </form>
</body>
</html>

Créez une classe qui hérite de UsernamePasswordAuthenticationToken qui contient les informations d'identification.

MultiParamAuthenticationToken


public class MultiParamAuthenticationToken extends UsernamePasswordAuthenticationToken {

    private static final long serialVersionUID = 1L;

    private Object tenant; //Paramètres supplémentaires

    public MultiParamAuthenticationToken(Object principal, Object credentials, Object tenant) {
        super(principal, credentials, Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
        this.tenant = tenant; 
    }

    public Object getTenant() {
        return this.tenant;
    }
}

Créez une classe de fournisseur d'authentification.

MultiParamAuthenticationProvider



@Configuration
public class MultiParamAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = (String) authentication.getPrincipal();
        String password = (String) authentication.getCredentials();
        String tenant = null;
        if (authentication instanceof MultiParamAuthenticationToken) {
            tenant = (String) ((MultiParamAuthenticationToken) authentication).getTenant();
        }
        if (!"user".equals(username) || !"pass".equals(password) || !"multi".equals(tenant)) {
            throw new BadCredentialsException("aaa");
        }
        return new MultiParamAuthenticationToken(username, password, tenant);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return MultiParamAuthenticationToken.class.isAssignableFrom(authentication);
    }

}

Préparez une classe de filtre pour obtenir le troisième paramètre.

MultiParamAuthenticationFilter


public class MultiParamAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {

        String username = obtainUsername(request);
        String password = obtainPassword(request);
        String tenant = obtainTenant(request);

        MultiParamAuthenticationToken authRequest = new MultiParamAuthenticationToken(
                username, password, tenant);

        return getAuthenticationManager().authenticate(authRequest);
    }

    private String obtainTenant(HttpServletRequest request) {
        return (String) request.getParameter("tenant");
    }
}

SecurityConfig.java



@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MultiParamAuthenticationProvider authenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login")
                .permitAll()
                .anyRequest()
                .authenticated();

        //Définissez votre propre filtre sans utiliser formLogin
        http.addFilter(getMultiParamAuthenticationFilter());
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
    }

    private MultiParamAuthenticationFilter getMultiParamAuthenticationFilter() throws Exception {
        MultiParamAuthenticationFilter filter = new MultiParamAuthenticationFilter();
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }
}

Authentification OAuth

Pour effectuer l'authentification OAuth, il est nécessaire que le fournisseur d'authentification émette un ID client et un secret. Ici, l'authentification OAuth est effectuée par Google.

Implémentation minimale requise

Ajoutez la dépendance suivante à pom.xml.

pom.xml


<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-jose</artifactId>
</dependency>

Définissez les informations OAuth dans application.yml.

application.yml


spring:
  security:
    oauth2:
      client:
        registration:
          google:
            clientId: <identité du client>
            clientSecret: <Secret du client>

SpringSecurity.java


@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated();

        http.oauth2Login(); //ajouter à
    }

Je souhaite utiliser les informations au moment de l'authentification

LoginController.java


@Controller
public class LoginController {

    @Autowired
    private OAuth2AuthorizedClientService authorizedClientService;

    @GetMapping
    public String index(OAuth2AuthenticationToken authentication, Model model) {

        OAuth2AuthorizedClient authorizedClient = this.authorizedClientService.loadAuthorizedClient(
                authentication.getAuthorizedClientRegistrationId(),
                authentication.getName());

        model.addAttribute("name", authorizedClient.getPrincipalName());
        model.addAttribute("accessToken", authorizedClient.getAccessToken().getTokenValue());

        return "index";
    }

oauth2authenticationtokenVous pouvez obtenir les informations d'authentification avec.

Ensuite, créez un écran pour l'affichage.

index.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>domicile</title>
</head>
<body>
    <p th:text="${accessToken}"></p>
    <p th:text="${name}"></p>
</body>
</html>

Je souhaite effectuer l'authentification OAuth sur mon propre écran de connexion

Créez un écran de connexion.

login.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>S'identifier</title>
</head>
<body>
    <a href="/oauth2/authorization/google">Authentification Google</a>
</body>
</html>

SecurityConfig.java


@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("(A)")
                .permitAll()
                .anyRequest()
                .authenticated();

        http.oauth2Login().loginPage("(A)"); //ajouter à
    }

Certification originale

Je veux m'authentifier avec JSON

usernamepasswordauthenticationfilterCréez une classe qui hérite de.

JsonAuthenticationFilter.java


public class JsonAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {

        try {
            //Obtenir les paramètres
            //Les informations de paramètre d'authentification sont demandées.getInputStream()Comme il est stocké, il est retiré à l'aide de Jackson
            Map<String, String> params = new ObjectMapper().readValue(request.getInputStream(),
                    new TypeReference<Map<String, String>>() {});

            //Génération d'informations de demande d'authentification
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                    params.get("(A)"), params.get("(B)"));

            //Authentification
            return getAuthenticationManager().authenticate(authRequest);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //Traiter lorsque l'authentification est réussie
    @Override
    protected void successfulAuthentication(HttpServletRequest req,
            HttpServletResponse res,
            FilterChain chain,
            Authentication auth) throws IOException, ServletException {
        //Enregistrer l'utilisateur authentifié
        //Si vous ne l'enregistrez pas, il sera traité comme non connecté
        SecurityContextHolder.getContext().setAuthentication(auth);
    }
}

L'écran de connexion enverra les informations d'identification via la communication Ajax.

login.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>S'identifier</title>
</head>
<body>
    <form>
        <input type="text" id="(A)" name="(A)">
        <input type="password" id="(B)" name="(B)">
        <button type="button" id="btnLogin" onclick="login()">S'identifier</button>
    </form>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
        function login() {
            //Obtenir les paramètres d'authentification
            const data = {
                email: document.getElementById('(A)').value,
                password: document.getElementById('(B)').value
            }
            //Authentification
            axios.post('(C)', data)
                .then(res => location.href = "/home") //Lorsque l'authentification est réussie, "/Transition vers la «maison»
        }
    </script>
</body>

</html>

Définissez le filtre créé ci-dessus avec SecuritConfig.

SecurityConfig.java


@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("(C)")
                .permitAll()
                .anyRequest()
                .authenticated();

        //La communication Ajax est effectuée, alors désactivez-la
        http.csrf().disable();

        //Définir le filtre d'authentification par Json
        http.addFilter(getJsonAuthenticationFilter());
    }

    private JsonAuthenticationFilter getJsonAuthenticationFilter() throws Exception {
        JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
        //Utiliser la méthode d'authentification par défaut
        filter.setAuthenticationManager(authenticationManager());
        //Spécifiez le chemin où le filtre sera exécuté et la méthode HTTP
        filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("(C)", "POST"));
        return filter;
    }
}

Version avancée

Je souhaite retourner JWT après authentification

Ajoutez la bibliothèque JWT à pom.xml.

pom.xml (supplémentaire uniquement)


<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>

Envoyez les informations d'identification dans JSON et renvoyez le JWT après le succès.

usernamepasswordauthenticationfilterCréez une classe qui hérite de.

JwtAuthenticationFilter.java


public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {

        try {
            //Obtenir les paramètres
            //Les informations de paramètre d'authentification sont demandées.getInputStream()Comme il est stocké, il est retiré à l'aide de Jackson
            Map<String, String> params = new ObjectMapper().readValue(request.getInputStream(),
                    new TypeReference<Map<String, String>>() {});

            //Génération d'informations de demande d'authentification
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                    params.get("(A)"), params.get("(B)"));

            //Authentification
            return getAuthenticationManager().authenticate(authRequest);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //Créez JWT lorsque l'authentification a réussi et définissez-le dans l'en-tête de réponse.
    @Override
    protected void successfulAuthentication(HttpServletRequest req,
            HttpServletResponse res,
            FilterChain chain,
            Authentication auth) throws IOException, ServletException {

        Date issuedAt = new Date();
        Date notBefore = new Date(issuedAt.getTime());
        Date expiresAt = new Date(issuedAt.getTime() + TimeUnit.MINUTES.toMillis(100L));

        //Génération JWT
        String token = JWT.create()
                .withIssuedAt(issuedAt)
                .withNotBefore(notBefore)
                .withExpiresAt(expiresAt)
                .withClaim("(C)", (String)auth.getPrincipal())
                .sign(Algorithm.HMAC512("secret"));

        //Définir JWT dans l'en-tête d'autorisation
        res.addHeader("Authorization", "Bearer " + token);
    }
}

onceperrequestfilterCréez une classe qui valide jwt qui hérite de.

JwtAuthenticationFilter.java


public class JwtAuthorizationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws ServletException, IOException {

        String header = req.getHeader("Authorization");

        if (header == null || !header.startsWith("Bearer ")) {
            //Continue le traitement, mais ne définit pas les informations d'identification dans SecurityContext, ce qui entraîne le renvoi de 403
            chain.doFilter(req, res);
            return;
        }

        //S'il s'agit d'un préfixe de support dans l'en-tête d'autorisation
        UsernamePasswordAuthenticationToken authentication = getAuthentication(req);

        if (Objects.isNull(authentication)) {
            chain.doFilter(req, res);
            return;
        }

        SecurityContextHolder.getContext().setAuthentication(authentication);

        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {

        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            return null;
        }
        token = token.substring("Bearer ".length());

        if (Objects.isNull(token) || token.length() == 0) {
            return null;
        }

        JWTVerifier verifier = JWT.require(Algorithm.HMAC512("secret")).build();

        try {
            DecodedJWT jwt = verifier.verify(token);

            return new UsernamePasswordAuthenticationToken(
                    jwt.getClaim("(C)").asString(), null,
                    null);
        } catch (JWTDecodeException e) {
            return null;
        }
    }

}

SecurityConfig.java


@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login")
                .permitAll()
                .anyRequest()
                .authenticated();

        http.csrf().disable();

        http.addFilter(getJsonAuthenticationFilter());

    }

    private JsonAuthenticationFilter getJsonAuthenticationFilter() throws Exception {
        JsonAuthenticationFilter filter = new JsonAuthenticationFilter();
        //Utiliser la méthode d'authentification par défaut
        filter.setAuthenticationManager(authenticationManager());
        //Spécifiez le chemin où le filtre sera exécuté et la méthode HTTP
        filter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
        return filter;
    }
}

Recommended Posts

[Inverser] Spring Security (mis à jour de temps en temps)
Appuyez sur tous les "Guides" de Spring (mis à jour de temps en temps)
Memorandum Poem (mis à jour de temps en temps)
Collection de sites utiles SpringBoot (mise à jour de temps en temps)
[Mis à jour de temps à autre] Liens endettés
Introduction à la programmation pour les étudiants du collégial (mise à jour de temps à autre)
[Mis à jour de temps en temps] Ruby on Rails Méthode pratique
[Eclipse] Résumé des paramètres d'environnement * Mis à jour de temps en temps
Essayez Spring Boot de 0 à 100.
[Mémo personnel] Grammaire Java fréquemment utilisée mise à jour de temps en temps
Paramètres initiaux d'Intellij IDEA personnellement recommandés (mis à jour de temps en temps)
Mise à niveau de la botte à ressort de la série 1.5 à la série 2.0
Résumé des résultats de la recherche sur l'orientation des objets [mis à jour de temps à autre]
Convertir l'heure UTC Java en heure JST
L'histoire de la transition de Spring Boot 1.5 à 2.1
Modifications lors de la migration de Spring Boot 1.5 vers Spring Boot 2.0
Transition de Struts2 à Spring MVC (contrôleur)
Modifications lors de la migration de Spring Boot 2.0 vers Spring Boot 2.2
J'ai essayé de traduire la grammaire de R et Java [Mis à jour de temps en temps]
Points sur lesquels je suis tombé lors de la création d'une application Android [Mis à jour de temps en temps]
Commande Git sur laquelle les nouveaux ingénieurs devraient se pencher [Mis à jour de temps en temps]
Obtenez Enum en retirant du contenu
[Introduction à Spring Boot] Fonction d'authentification avec Spring Security
Spring Boot - Comment définir le délai d'expiration de la session
Pour se connecter de Spring à MySQL sur un serveur virtuel (non résolu)
Livraison push de l'application Spring vers Firebase Cloud Messaging
Essayez de travailler avec Keycloak en utilisant Spring Security SAML (Spring 5)
Un nouvel employé a tenté de créer une fonction d'authentification / autorisation à partir de zéro avec Spring Security