English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

spring security自定义认证登录的全过程记录

Catégorie d'utilisation de Spring Security :

Comment utiliser Spring Security, ceux qui ont cherché sur Baidu savent, il y a quatre méthodes en tout, de la plus simple à la plus complexe :

1, sans utiliser de base de données, toutes les données sont écrites dans le fichier de configuration, c'est aussi un exemple de demo dans le document officiel;

2, utiliser la base de données, concevoir la base de données en fonction du code source par défaut de Spring Security, c'est-à-dire que la base de données est déjà fixée, cette méthode n'est pas flexible et la conception de la base de données est très primitive, son utilité est mauvaise;

3, Spring Security et Acegi sont différents, il ne peut pas modifier le filtre par défaut, mais il supporte l'insertion de filtres, donc selon cela, nous pouvons insérer notre propre filtre pour une utilisation flexible;

4, méthodes de force brute, modification du code source, ce que nous avons dit de la modification du filtre par défaut consiste à remplacer le fichier de configuration pour le filtre, mais cela modifie directement le code source, ce qui n'est pas conforme aux principes de conception OO et n'est pas pratique.

Ce document présente principalement les informations sur la personnalisation de l'authentification de Spring Security, et est partagé pour que tout le monde puisse l'utiliser comme référence pour l'apprentissage. Il n'y a pas grand-chose à dire, regardons donc l'introduction détaillée.

1.Résumé

1.1.Introduction

Spring Security est un cadre de sécurité basé sur Spring AOP et Servlet Filter, utilisé pour gérer l'autorisation et l'authentification.

1.2.Procédure personnalisée de sécurité Spring

1) Processus d'authentification

Générer un AuthenticationToken non authentifié                 

 ↑(obtenir des informations)  (allouer le provider en fonction de AuthenticationToken)     
 AuthenticationFilter -> AuthenticationManager -> AuthenticationProvider
        ↓(authentification)
       UserDetails (généralement récupéré de la base de données)
        ↓(par)
        Générer un AuthenticationToken de succès d'authentification
         ↓(contient)
        SecurityContextHolder

2Ajouter AuthenticationFilter à la chaîne de filtrage de sécurité (configuré dans le serveur de ressources), par exemple :

http.addFilterBefore(AuthenticationFilter, AbstractPreAuthenticatedProcessingFilter.class)

ou :

http.addFilterAfter(AuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

2.Exemple de connexion par SMS avec le numéro de téléphone

2.1.Environnement de développement

  • SpringBoot
  • Spring security
  • Redis

2.2.Analyse du code principal

2.2.1.Procédure de connexion personnalisée

2.2.1.1.Token de connexion personnalisé

/**
 * Token de connexion mobile
 *
 * @author : CatalpaFlat
 */
public class MobileLoginAuthenticationToken extends AbstractAuthenticationToken {
 private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationToken.class.getName());
 private final Object principal;
 public MobileLoginAuthenticationToken(String mobile) {
 super(null);
 this.principal = mobile;
 this.setAuthenticated(false);
 logger.info("MobileLoginAuthenticationToken setAuthenticated ->false loading ...);
 }
 public MobileLoginAuthenticationToken(Object principal,
      Collection<? extends GrantedAuthority> authorities) {
 super(authorities);
 this.principal = principal;
 // must use super, as we override
 super.setAuthenticated(true);
 logger.info("MobileLoginAuthenticationToken setAuthenticated ->true loading ...);
 }
 @Override
 public void setAuthenticated(boolean authenticated) {}}
 if (authenticated) {
  lancer une new IllegalArgumentException(
   "Ce jeton ne peut pas être défini comme de confiance" - utilisez le constructeur qui prend une liste de GrantedAuthority au lieu de cela");
 }
 super.setAuthenticated(false);
 }
 @Override
 public Object getCredentials() {
 return null;
 }
 @Override
 public Object getPrincipal() {
 return this.principal;
 }
 @Override
 public void eraseCredentials() {
 super.eraseCredentials();
 }
}

Remarque :

setAuthenticated() : juger si il est authentifié

  • Lors du filtrage, un AuthenticationToken non authentifié est généré, à ce moment-là, la méthode appelée est setAuthenticated() du token personnalisé, et à ce moment-là, il est réglé sur false -> Non authentifié
  • Lors de la fourniture, un AuthenticationToken authentifié est généré, à ce moment-là, la méthode appelée est setAuthenticated() de la classe parente, et à ce moment-là, il est réglé sur true -> Authentifié

2.2.1.1.Définir un filtre d'authentification personnalisé

/**
 * Filtre de connexion par SMS mobile
 *
 * @author : CatalpaFlat
 */
public class MobileLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
 private boolean postOnly = true;
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationFilter.class.getName());
 @Getter
 @Setter
 private String mobileParameterName;
 public MobileLoginAuthenticationFilter(String mobileLoginUrl, String mobileParameterName,
      String httpMethod) {}}
 super(new AntPathRequestMatcher(mobileLoginUrl, httpMethod));
 this.mobileParameterName = mobileParameterName;
 logger.info("MobileLoginAuthenticationFilter en cours de chargement ...");
 }
 @Override
 public Authentication attemptAuthentication(HttpServletRequest request,      HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
 if (postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {
  lancer une nouvelle AuthenticationServiceException("Méthode d'authentification non prise en charge : ") + request.getMethod());
 }
 //Obtenir le mobile
 String mobile = obtainMobile(request);
 //Assemblez le jeton
 MobileLoginAuthenticationToken authRequest = new MobileLoginAuthenticationToken(mobile);
 // Permettre aux sous-classes de définir la propriété "details"
 setDetails(request, authRequest);
 return this.getAuthenticationManager().authenticate(authRequest);
 }
 /**
 * Définir les détails de l'authentification
 */
 private void setDetails(HttpServletRequest request, MobileLoginAuthenticationToken authRequest) {
 authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
 }
 /**
 * 获取手机号
 */
 private String obtainMobile(HttpServletRequest request) {
 return request.getParameter(mobileParameterName);
 }
 public void setPostOnly(boolean postOnly) {
 this.postOnly = postOnly;
 }
}

Remarque :attemptAuthentication()方法:

  • 过滤指定的url、httpMethod
  • 获取所需请求参数数据封装生成一个未认证的AuthenticationToken
  • 传递给AuthenticationManager认证

2.2.1.1.自定义认证登录提供者

/**
 * 手机短信登录认证提供者
 *
 * @author : CatalpaFlat
 */
public class MobileLoginAuthenticationProvider implements AuthenticationProvider {
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationProvider.class.getName());
 @Getter
 @Setter
 private UserDetailsService customUserDetailsService;
 public MobileLoginAuthenticationProvider() {
 logger.info("MobileLoginAuthenticationProvider loading ...");
 }
 /**
 * 认证
 */
 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 //Obtenir des informations sur le token encapsulé par le filtre
 MobileLoginAuthenticationToken authenticationToken = (MobileLoginAuthenticationToken) authentication;
 //获取用户信息(数据库认证)
 UserDetails userDetails = customUserDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());
 //Échec
 if (userDetails == null) {
  lancer une nouvelle InternalAuthenticationServiceException("Impossible d'obtenir des informations utilisateur");
 }
 //Succès
 MobileLoginAuthenticationToken authenticationResult = new MobileLoginAuthenticationToken(userDetails, userDetails.getAuthorities());
 authenticationResult.setDetails(authenticationToken.getDetails());
 return authenticationResult;
 }
 /**
 * Selon le type de token, décidez quel fournisseur utiliser
 */
 @Override
 public boolean supports(Class<63;> authentication) {
 return MobileLoginAuthenticationToken.class.isAssignableFrom(authentication);
 }
}

Remarque :méthode authenticate()

  • Obtenir des informations sur le token encapsulé par le filtre
  • Appeler UserDetailsService pour obtenir des informations utilisateur (authentification de base de données)->Jugement de succès ou de échec
  • Si le test est réussi, encapsulez un nouveau AuthenticationToken et retournez

2.2.1.1.Configuration personnalisée pour l'authentification de connexion mobile

@Configuration(SpringBeanNameConstant.DEFAULT_CUSTOM_MOBILE_LOGIN_AUTHENTICATION_SECURITY_CONFIG_BN)
public class MobileLoginAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationSecurityConfig.class.getName());
 @Value("${login.mobile.url}")
 private String defaultMobileLoginUrl;
 @Value("${login.mobile.parameter}")
 private String defaultMobileLoginParameter;
 @Value("${login.mobile.httpMethod}")
 private String defaultMobileLoginHttpMethod;
 @Autowired
 private CustomYmlConfig customYmlConfig;
 @Autowired
 private UserDetailsService customUserDetailsService;
 @Autowired
 private AuthenticationSuccessHandler customAuthenticationSuccessHandler;
 @Autowired
 private AuthenticationFailureHandler customAuthenticationFailureHandler;
 public MobileLoginAuthenticationSecurityConfig() {
 logger.info("MobileLoginAuthenticationSecurityConfig loading ...");
 }
 @Override
 public void configure(HttpSecurity http) throws Exception {
 MobilePOJO mobile = customYmlConfig.getLogins().getMobile();
 String url = mobile.getUrl();
 String parameter = mobile.getParameter().getMobile();
 String httpMethod = mobile.getHttpMethod();
 MobileLoginAuthenticationFilter mobileLoginAuthenticationFilter = new MobileLoginAuthenticationFilter(StringUtils.isBlank(url) ? urlParDefautMobileLogin : url,
  StringUtils.isBlank(parametre) ? urlParDefautMobileLogin : parametre, StringUtils.isBlank(httpMethod) ? methodeHttpParDefautMobileLogin : httpMethod); mobileLoginAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); mobileLoginAuthenticationFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler); mobileLoginAuthenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);
 MobileLoginAuthenticationProvider mobileLoginAuthenticationProvider = new MobileLoginAuthenticationProvider(); mobileLoginAuthenticationProvider.setCustomUserDetailsService(customUserDetailsService);
 http.authenticationProvider(mobileLoginAuthenticationProvider)
  .ajouterFiltreApres(mobileLoginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
 }
}

Remarque :méthode configure()

Instancier AuthenticationFilter et AuthenticationProvider

Ajouter AuthenticationFilter et AuthenticationProvider au Spring Security.

2.2.2.Vérification de code personnalisé basé sur Redis

2.2.2.1.Filtre de validation de code personnalisé basé sur Redis

/**
 * Filtre de code de validation
 *
 * @author : CatalpaFlat
 */
@Component(SpringBeanNameConstant.DEFAULT_VALIDATE_CODE_FILTER_BN)
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
 private static final Logger logger = LoggerFactory.getLogger(ValidateCodeFilter.class.getName());
 @Autowired
 private CustomYmlConfig customYmlConfig;
 @Autowired
 private RedisTemplate<Object, Object> redisTemplate;
 /**
  * Classe utilitaire pour vérifier si l'URL de la requête correspond à l'URL configurée
  */
 private AntPathMatcher pathMatcher = new AntPathMatcher();
 public ValidateCodeFilter() {
  logger.info("Loading ValidateCodeFilter...");
 }
 @Override
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
         FilterChain filterChain) throws ServletException, IOException {
  String url = customYmlConfig.getLogins().getMobile().getUrl();
  si (pathMatcher.match(url, request.getRequestURI())) {}}
   String deviceId = request.getHeader("deviceId");
   si (StringUtils.isBlank(deviceId)) {
    lancer une nouvelle ExceptionCustome(HttpStatus.NOT_ACCEPTABLE.value(), "Pas de deviceId dans l'en-tête de la requête");
   }
   String codeParamName = customYmlConfig.getLogins().getMobile().getParameter().getCode();
   String code = request.getParameter(codeParamName);
   si (StringUtils.isBlank(code)) {
    lancer une nouvelle ExceptionCustome(HttpStatus.NOT_ACCEPTABLE.value(), "Pas de code dans les paramètres de la requête");
   }
   String key = SystemConstant.DEFAULT_MOBILE_KEY_PIX + deviceId;
   SmsCodePO smsCodePo = (SmsCodePO) redisTemplate.opsForValue().get(key);
   si (smsCodePo.isExpried()){
    lancer une nouvelle ExceptionCustome(HttpStatus.BAD_REQUEST.value(), "Le code de vérification a expiré");
   }
   String smsCode = smsCodePo.getCode();
   si (StringUtils.isBlank(smsCode)) {
    lancer une nouvelle ExceptionCustome(HttpStatus.BAD_REQUEST.value(), "Le code de vérification n'existe pas");
   }
   if (StringUtils.equals(code, smsCode)) {}}
    redisTemplate.delete(key);
    //Laissez-le aller
    filterChain.doFilter(request, response);
   }
    Lancer une nouvelle CustomException(HttpStatus.BAD_REQUEST.value(), "Le code de validation est incorrect");
   }
  }
   //Laissez-le aller
   filterChain.doFilter(request, response);
  }
 }
}

Remarque :doFilterInternal()

Validation personnalisée du code de validation

2.2.2.2.Ajouter le filtre de validation personnalisé au chaînage des filtres de sécurité de Spring

http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class)

Remarque :Ajouter avant le filtre de prétraitement d'authentification

3.Test des effets

Voici l'adresse de la source :https://gitee.com/CatalpaFlat/springSecurity.git  (Téléchargement local)

Résumé

Voici la totalité du contenu de cet article. J'espère que le contenu de cet article aura une certaine valeur de référence pour votre apprentissage ou travail. Si vous avez des doutes, vous pouvez laisser des messages pour échanger. Merci de votre soutien au tutoriel de cri.

Déclaration : Le contenu de cet article est extrait du réseau, propriété des auteurs originaux, contribué et téléversé par les utilisateurs d'Internet. Ce site n'est pas propriétaire des droits, n'a pas été édité par l'homme, et n'assume pas la responsabilité des responsabilités juridiques associées. Si vous trouvez du contenu présumé de violation de droits d'auteur, veuillez envoyer un e-mail à : notice#oldtoolbag.com (veuillez remplacer # par @ lors de l'envoi d'un e-mail pour faire une plainte, et fournir des preuves pertinentes. Une fois vérifié, ce site supprimera immédiatement le contenu présumé de violation de droits d'auteur.