SpringSecurity实现多种登录方式,如邮件验证码、电话号码登录

简介: SpringSecurity实现多种登录方式,如邮件验证码、电话号码登录


你好丫,我是博主宁在春,一起加油吧!!!

不知道, 你在用Spring Security的时候,有没有想过,用它实现多种登录方式勒,这次我的小伙伴就给我提了一些登录方面的需求,需要在原有账号密码登录的基础上,另外实现电话验证码以及邮件验证码登录,以及在实现之后,让我能够做到实现第三方登录,如giteegithub等。

本文主要是讲解Security在实现账号密码的基础上,并且不改变原有业务情况下,实现邮件、电话验证码登录。

封面傍晚操场的看到的云

前言:

掌握这个登录流程,我们才能更好的做Security的定制操作。

我在写这篇文章之前,也看过很多博主的文章,写的非常好,有对源码方面的解析,也有对一些相关设计理念的理解的文章。

这对于已经学过一段时间,并且对Security已经有了解的小伙伴来说,还是比较合适的,但是对于我以及其他一些急于解决当下问题的小白,并不是那么友善。😂

一、理论知识

我们先思考一下这个流程大致是如何的?

  1. 填写邮件号码,获取验证码
  2. 输入获取到的验证码进行登录(登录的接口:/email/login,这里不能使用默认的/login,因为我们是扩展)
  3. 在自定义的过滤器EmailCodeAuthenticationFilter中获取发送过来的邮件号码及验证码,判断验证码是否正确,邮件账号是否为空等
  4. 封装成一个需要认证的Authentication,此处我们自定义实现为EmailCodeAuthenticationToken
  5. 将 Authentiction 传给 AuthenticationManager 接口中 authenticate 方法进行认证处理
  6. AuthenticationManager 默认是实现类为 ProviderManager ,ProviderManager 又委托给 AuthenticationProvider 进行处理
  7. 我们自定义一个EmailCodeAuthenticationProvider实现AuthenticationProvider,实现身份验证。
  8. 自定义的 EmailCodeAuthenticationFilter 继承了 AbstractAuthenticationProcessingFilter 抽象类,AbstractAuthenticationProcessingFiltersuccessfulAuthentication 方法中对登录成功进行了处理,通过 SecurityContextHolder.getContext().setAuthentication() 方法将 Authentication 认证信息对象绑定到 SecurityContext即安全上下文中。
  9. 其实对于身份验证通过后的处理,有两种方案,一种是直接在过滤器重写successfulAuthentication,另外一种就是实现AuthenticationSuccessHandler来处理身份验证通过。
  10. 身份验证失败也是一样,可重写unsuccessfulAuthentication方法,也可以实现 AuthenticationFailureHandler来对身份验证失败进行处理。

大致流程就是如此。从这个流程中我们可以知道,需要重写的组件有以下几个:

  1. EmailCodeAuthenticationFilter:邮件验证登录过滤器
  2. EmailCodeAuthenticationToken:身份验证令牌
  3. EmailCodeAuthenticationProvider:邮件身份认证处理
  4. AuthenticationSuccessHandler:处理登录成功操作
  5. AuthenticationFailureHandler:处理登录失败操作

接下来,我是模仿着源码写出我的代码,建议大家可以在使用的时候,多去看看,我这里去除了一些不是和这个相关的代码。

来吧!!

二、EmailCodeAuthenticationFilter

我们需要重写的EmailCodeAuthenticationFilter,实际继承了AbstractAuthenticationProcessingFilter抽象类,我们不会写,可以先看看它的默认实现UsernamePasswordAuthenticationFilter是怎么样的吗,抄作业这是大家的强项的哈。

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
  public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
  public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
  private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
      "POST");
  //从前台传过来的参数
  private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
  private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
  private boolean postOnly = true;
    //  初始化一个用户密码 认证过滤器  默认的登录uri 是 /login 请求方式是POST
  public UsernamePasswordAuthenticationFilter() {
    super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
  }
  public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
    super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
  }
    /**
    执行实际身份验证。实现应执行以下操作之一:
  1、为经过身份验证的用户返回填充的身份验证令牌,表示身份验证成功
  2、返回null,表示认证过程还在进行中。 在返回之前,实现应该执行完成流程所需的任何额外工作。
  3、如果身份验证过程失败,则抛出AuthenticationException
    */
  @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
      throws AuthenticationException {
    if (this.postOnly && !request.getMethod().equals("POST")) {
      throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    }
    String username = obtainUsername(request);
    username = (username != null) ? username : "";
    username = username.trim();
    String password = obtainPassword(request);
    password = (password != null) ? password : "";
        //生成 UsernamePasswordAuthenticationToken 稍后交由AuthenticationManager中的authenticate进行认证
    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
    // 可以放一些其他信息进去
    setDetails(request, authRequest);
    return this.getAuthenticationManager().authenticate(authRequest);
  }
  @Nullable
  protected String obtainPassword(HttpServletRequest request) {
    return request.getParameter(this.passwordParameter);
  }
  @Nullable
  protected String obtainUsername(HttpServletRequest request) {
    return request.getParameter(this.usernameParameter);
  }
  protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
    authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
  }
  //set、get方法
}

接下来我们就抄个作业哈:

package com.crush.security.auth.email_code;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.ArrayList;
/**
 * @Author: crush
 * @Date: 2021-09-08 21:13
 * version 1.0
 */
public class EmailCodeAuthenticationFilter  extends AbstractAuthenticationProcessingFilter {
    /**
     * 前端传来的 参数名 - 用于request.getParameter 获取
     */
    private final String DEFAULT_EMAIL_NAME="email";
    private final String DEFAULT_EMAIL_CODE="e_code";
    @Autowired
    @Override
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }
    /**
     * 是否 仅仅post方式
     */
    private boolean postOnly = true;
    /**
     * 通过 传入的 参数 创建 匹配器
     * 即 Filter过滤的url
     */
    public EmailCodeAuthenticationFilter() {
        super(new AntPathRequestMatcher("/email/login","POST"));
    }
    /**
     * filter 获得 用户名(邮箱) 和 密码(验证码) 装配到 token 上 ,
     * 然后把token 交给 provider 进行授权
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if(postOnly && !request.getMethod().equals("POST") ){
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }else{
            String email = getEmail(request);
            if(email == null){
                email = "";
            }
            email = email.trim();
            //如果 验证码不相等 故意让token出错 然后走springsecurity 错误的流程
            boolean flag = checkCode(request);
            //封装 token
            EmailCodeAuthenticationToken token = new EmailCodeAuthenticationToken(email,new ArrayList<>());
            this.setDetails(request,token);
            //交给 manager 发证
            return this.getAuthenticationManager().authenticate(token);
        }
    }
    /**
     * 获取 头部信息 让合适的provider 来验证他
     */
    public void setDetails(HttpServletRequest request , EmailCodeAuthenticationToken token ){
        token.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }
    /**
     * 获取 传来 的Email信息
     */
    public String getEmail(HttpServletRequest request ){
        String result=  request.getParameter(DEFAULT_EMAIL_NAME);
        return result;
    }
    /**
     * 判断 传来的 验证码信息 以及 session 中的验证码信息
     */
    public boolean checkCode(HttpServletRequest request ){
        String code1 = request.getParameter(DEFAULT_EMAIL_CODE);
        System.out.println("code1**********"+code1);
        // TODO 另外再写一个链接 生成 验证码 那个验证码 在生成的时候  存进redis 中去
        //TODO  这里的验证码 写在Redis中, 到时候取出来判断即可 验证之后 删除验证码
        if(code1.equals("123456")){
            return true;
        }
        return false;
    }
  // set、get方法...
}

三、EmailCodeAuthenticationToken

我们EmailCodeAuthenticationToken是继承AbstractAuthenticationToken的,按照同样的方式,我们接着去看看AbstractAuthenticationToken的默认实现是什么样的就行了。

/**
 */
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
  private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
    // 这里指的账号密码哈
  private final Object principal;
  private Object credentials;
  /**
  没经过身份验证时,初始化权限为空,setAuthenticated(false)设置为不可信令牌
   */
  public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
    super(null);
    this.principal = principal;
    this.credentials = credentials;
    setAuthenticated(false);
  }
  /**
  经过身份验证后,将权限放进去,setAuthenticated(true)设置为可信令牌
   */
  public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
      Collection<? extends GrantedAuthority> authorities) {
    super(authorities);
    this.principal = principal;
    this.credentials = credentials;
    super.setAuthenticated(true); // must use super, as we override
  }
  @Override
  public Object getCredentials() {
    return this.credentials;
  }
  @Override
  public Object getPrincipal() {
    return this.principal;
  }
  @Override
  public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
    Assert.isTrue(!isAuthenticated,
        "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
    super.setAuthenticated(false);
  }
  @Override
  public void eraseCredentials() {
    super.eraseCredentials();
    this.credentials = null;
  }
}

日常抄作业哈:

/**
 * @Author: crush
 * @Date: 2021-09-08 21:13
 * version 1.0
 */
public class EmailCodeAuthenticationToken extends AbstractAuthenticationToken {
    /**
     * 这里的 principal 指的是 email 地址(未认证的时候)
     */
    private final Object principal;
    public EmailCodeAuthenticationToken(Object principal) {
        super((Collection) null);
        this.principal = principal;
        setAuthenticated(false);
    }
    public EmailCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true);
    }
    @Override
    public Object getCredentials() {
        return null;
    }
    @Override
    public Object getPrincipal() {
        return this.principal;
    }
    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        } else {
            super.setAuthenticated(false);
        }
    }
}

这个很简单的哈。👨‍💻

四、EmailCodeAuthenticationProvider

自定义的EmailCodeAuthenticationProvider是实现了AuthenticationProvider接口,抄作业就得学会看看源码。我们接着来。

4.1、先看看AbstractUserDetailsAuthenticationProvider,我们再来模仿

AuthenticationProvider接口有很多实现类,不一一说明了,直接看我们需要看的AbstractUserDetailsAuthenticationProvider, 该类旨在响应UsernamePasswordAuthenticationToken身份验证请求。但是它是一个抽象类,但其实就一个步骤在它的实现类中实现的,很简单,稍后会讲到。

在这个源码中我把和检查相关的一些操作都给删除,只留下几个重点,我们一起来看一看哈。

//该类旨在响应UsernamePasswordAuthenticationToken身份验证请求。
public abstract class AbstractUserDetailsAuthenticationProvider
    implements AuthenticationProvider, InitializingBean, MessageSourceAware {
  protected final Log logger = LogFactory.getLog(getClass());
  private UserCache userCache = new NullUserCache();
  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
        () -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
            "Only UsernamePasswordAuthenticationToken is supported"));
        //获取用户名
    String username = determineUsername(authentication);
    //判断缓存中是否存在
        boolean cacheWasUsed = true;
    UserDetails user = this.userCache.getUserFromCache(username);
    if (user == null) {
      cacheWasUsed = false;
      try {
                // 缓存中没有 通过字类实现的retrieveUser 从数据库进行检索,返回一个 UserDetails 对象
        user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
      }
      catch (UsernameNotFoundException ex) {
        this.logger.debug("Failed to find user '" + username + "'");
        if (!this.hideUserNotFoundExceptions) {
          throw ex;
        }
        throw new BadCredentialsException(this.messages
            .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
      }
      Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
    }
    try {
            //进行相关检查  因为可能是从缓存中取出来的 并非是最新的
      this.preAuthenticationChecks.check(user);
      additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
    }
    catch (AuthenticationException ex) {
      if (!cacheWasUsed) {
        throw ex;
      }
            // 没有通过检查, 重新检索最新的数据
      cacheWasUsed = false;
      user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
      this.preAuthenticationChecks.check(user);
      additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
    }
        // 再次进行检查
    this.postAuthenticationChecks.check(user);
    // 存进缓存中去
        if (!cacheWasUsed) {
      this.userCache.putUserInCache(user);
    }
    Object principalToReturn = user;
    if (this.forcePrincipalAsString) {
      principalToReturn = user.getUsername();
    }
        //创建一个可信的身份令牌返回
    return createSuccessAuthentication(principalToReturn, authentication, user);
  }
  private String determineUsername(Authentication authentication) {
    return (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
  }
  /**
简而言之就是创建了一个通过身份验证的UsernamePasswordAuthenticationToken
   */
  protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
      UserDetails user) {
    UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
        authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
    result.setDetails(authentication.getDetails());
    this.logger.debug("Authenticated user");
    return result;
  }
  /**
允许子类从特定于实现的位置实际检索UserDetails ,如果提供的凭据不正确,则可以选择立即抛出AuthenticationException (如果需要以用户身份绑定到资源以获得或生成一个UserDetails )
   */
  protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
      throws AuthenticationException;
  //...
    //简而言之:当然有时候我们有多个不同的 `AuthenticationProvider`,它们分别支持不同的 `Authentication`对象,那么当一个具体的 `AuthenticationProvier`传进入 `ProviderManager`的内部时,就会在 `AuthenticationProvider`列表中挑选其对应支持的provider对相应的 Authentication对象进行验证
  @Override
  public boolean supports(Class<?> authentication) {
    return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
  }
}

关于protected abstract UserDetails retrieveUser的实现,AbstractUserDetailsAuthenticationProvider实现是DaoAuthenticationProvider.

DaoAuthenticationProvider主要操作是两个,第一个是从数据库中检索出相关信息,第二个是给检索出的用户信息进行密码的加密操作。

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
  private UserDetailsService userDetailsService;
  @Override
  protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
      throws AuthenticationException {
    prepareTimingAttackProtection();
    try {
            // 检索用户,一般我们都会实现 UserDetailsService接口,改为从数据库中检索用户信息 返回安全核心类 UserDetails
      UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
      if (loadedUser == null) {
        throw new InternalAuthenticationServiceException(
            "UserDetailsService returned null, which is an interface contract violation");
      }
      return loadedUser;
    }
    catch (UsernameNotFoundException ex) {
      mitigateAgainstTimingAttack(authentication);
      throw ex;
    }
    catch (InternalAuthenticationServiceException ex) {
      throw ex;
    }
    catch (Exception ex) {
      throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
    }
  }
  @Override
  protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
      UserDetails user) {
        // 判断是否用了密码加密 针对这个点 没有深入 大家好奇可以去查一查这个知识点
    boolean upgradeEncoding = this.userDetailsPasswordService != null
        && this.passwordEncoder.upgradeEncoding(user.getPassword());
    if (upgradeEncoding) {
      String presentedPassword = authentication.getCredentials().toString();
      String newPassword = this.passwordEncoder.encode(presentedPassword);
      user = this.userDetailsPasswordService.updatePassword(user, newPassword);
    }
    return super.createSuccessAuthentication(principal, authentication, user);
  }
}

4.2、抄作业啦

看完源码,其实我们如果要重写的话,主要要做到以下几个事情:

  1. 重写public boolean supports(Class<?> authentication)方法。
    有时候我们有多个不同的 AuthenticationProvider,它们分别支持不同的 Authentication对象,那么当一个具体的 AuthenticationProvier传进入 ProviderManager的内部时,就会在 AuthenticationProvider列表中挑选其对应支持的provider对相应的 Authentication对象进行验证
    简单说就是指定AuthenticationProvider验证哪个Authentication对象。如指定DaoAuthenticationProvider认证UsernamePasswordAuthenticationToken
    所以我们指定EmailCodeAuthenticationProvider认证EmailCodeAuthenticationToken
  2. 检索数据库,返回一个安全核心类UserDetail
  3. 创建一个经过身份验证的Authentication对象

了解要做什么事情了,我们就可以动手看看代码啦。

/**
 * @Author: crush
 * @Date: 2021-09-08 21:14
 * version 1.0
 */
@Slf4j
public class EmailCodeAuthenticationProvider implements AuthenticationProvider {
    ITbUserService userService;
    public EmailCodeAuthenticationProvider(ITbUserService userService) {
        this.userService = userService;
    }
    /**
     * 认证
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if (!supports(authentication.getClass())) {
            return null;
        }
        log.info("EmailCodeAuthentication authentication request: %s", authentication);
        EmailCodeAuthenticationToken token = (EmailCodeAuthenticationToken) authentication;
        UserDetails user = userService.getByEmail((String) token.getPrincipal());
        System.out.println(token.getPrincipal());
        if (user == null) {
            throw new InternalAuthenticationServiceException("无法获取用户信息");
        }
        System.out.println(user.getAuthorities());
        EmailCodeAuthenticationToken result =
                new EmailCodeAuthenticationToken(user, user.getAuthorities());
                /*
                Details 中包含了 ip地址、 sessionId 等等属性 也可以存储一些自己想要放进去的内容
                */
        result.setDetails(token.getDetails());
        return result;
    }
    @Override
    public boolean supports(Class<?> aClass) {
        return EmailCodeAuthenticationToken.class.isAssignableFrom(aClass);
    }
}

五、在配置类中进行配置

主要就是做下面几件事:

  1. 将过滤器、认证器注入到spring
  2. 将登录成功处理、登录失败处理器注入到Spring中,或者在自定义过滤器中对登录成功和失败进行处理。
  3. 添加到过滤链中
@Bean
    public EmailCodeAuthenticationFilter emailCodeAuthenticationFilter() {
        EmailCodeAuthenticationFilter emailCodeAuthenticationFilter = new EmailCodeAuthenticationFilter();
        emailCodeAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
        emailCodeAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
        return emailCodeAuthenticationFilter;
    }
    @Bean
    public EmailCodeAuthenticationProvider emailCodeAuthenticationProvider() {
        return new EmailCodeAuthenticationProvider(userService);
    }
    /**
     * 因为使用了BCryptPasswordEncoder来进行密码的加密,所以身份验证的时候也的用他来判断哈、,
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
        //authenticationProvider 根据传入的自定义AuthenticationProvider添加身份AuthenticationProvider 。
        auth.authenticationProvider(emailCodeAuthenticationProvider());
    }
.and()
    .authenticationProvider(emailCodeAuthenticationProvider())
    .addFilterBefore(emailCodeAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class)
    .authenticationProvider(mobileCodeAuthenticationProvider())
    .addFilterBefore(mobileCodeAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class)

六、测试及源代码

项目具体的配置、启动方式、环境等、都在githubgitee的文档上有详细说明。

源代码中包含sql文件、配置文件以及相关博客链接,源代码中也加了很多注释,尽最大程度让大家能够看明白。

在最大程度上保证大家都能正确的运行及测试。

源码:gitee-Security

七、自言自语

如果这篇存在不太懂的内容,可以先看我的另一篇文章:

SpringBoot集成Security实现安全控制,使用Jwt制作Token令牌。

之后再回过头来看这一篇文章,应该会更加容易理解。

我是真的想要教会大家,并非是胡乱写写,主要是想收到来自大家成功后的那种开心,会让人心情愉悦。

相关文章:

  1. SpringBoot集成SpringSecurity做安全框架
  2. Security的登录流程详解
  3. Security实现多种登录方式、邮件验证码、手机验证码登录。
  4. SpringSecurity权限命名ROLE_问题

今天的文章就到这里了。

你好,我是博主宁在春主页

如若在文章中遇到疑惑,请留言或私信,或者加主页联系方式,都会尽快回复。

如若发现文章中存在问题,望你能够指正,不胜感谢。

如果觉得对你有所帮助的话,请点个赞再走吧!

目录
相关文章
|
6月前
|
存储 JSON JavaScript
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)-1
前后端分离项目知识汇总(微信扫码登录,手机验证码登录,JWT)
185 0
|
6月前
|
前端开发 安全 Java
SpringBoot 实现登录验证码(附集成SpringSecurity)
SpringBoot 实现登录验证码(附集成SpringSecurity)
405 0
|
3月前
|
存储 NoSQL 数据库
认证服务---整合短信验证码,用户注册和登录 ,密码采用MD5加密存储 【二】
这篇文章讲述了在分布式微服务系统中添加用户注册和登录功能的过程,重点介绍了用户注册时通过远程服务调用第三方服务获取短信验证码、使用Redis进行验证码校验、对密码进行MD5加密后存储到数据库,以及用户登录时的远程服务调用和密码匹配校验的实现细节。
认证服务---整合短信验证码,用户注册和登录 ,密码采用MD5加密存储 【二】
|
1月前
|
Java
Java 登录输入的验证码
Java 登录输入的验证码
26 1
|
1月前
|
C#
C# 图形验证码实现登录校验代码
C# 图形验证码实现登录校验代码
76 2
|
2月前
|
存储 JSON 前端开发
node使用token来实现前端验证码和登录功能详细流程[供参考]=‘很值得‘
本文介绍了在Node.js中使用token实现前端验证码和登录功能的详细流程,包括生成验证码、账号密码验证以及token验证和过期处理。
49 0
node使用token来实现前端验证码和登录功能详细流程[供参考]=‘很值得‘
|
3月前
|
资源调度 JavaScript API
nest.js + sms 实现短信验证码登录
本文介绍了在Nest.js框架中集成短信验证码登录的实现方案,详细阐述了使用阿里云短信服务的配置流程、资质申请、短信模板设置,并提供了API调用示例和工程代码的运行步骤。
nest.js + sms 实现短信验证码登录
|
3月前
【Azure 环境】中国区Azure B2C 是否支持手机验证码登录呢?
【Azure 环境】中国区Azure B2C 是否支持手机验证码登录呢?
|
6月前
|
缓存 算法 NoSQL
短信验证码登录接口,如何防止恶意攻击
该文讨论了移动应用中常见的手机短信验证码登录(短验登录)的安全设计。后端通常需要提供获取短信验证码和手机短验登录两个API。为了增强机短验登录API的安全性,提出了几种无需依赖Redis等存储介质的方案:1)使用数字签名确保请求合法性;2)基于时间戳的验证,允许在一定时间范围内有效;3)应用TOTP算法生成动态码进行验证;4)利用JWTToken进行身份验证并设置有效期。文章强调了创新思考在解决安全问题中的重要性,并鼓励读者分享更多方案。
571 1
|
5月前
|
存储 NoSQL Java
Redis系列学习文章分享---第三篇(Redis快速入门之Java客户端--短信登录+session+验证码+拦截器+登录刷新)
Redis系列学习文章分享---第三篇(Redis快速入门之Java客户端--短信登录+session+验证码+拦截器+登录刷新)
100 0