Spring Security实现短信登录

本文详细介绍了如何使用Spring Security实现短信验证码登录。首先解释了短信验证码登录的理论流程,接着通过代码实战展示了SmsAuthenticationToken、SmsAuthenticationFilter和SmsAuthenticationProvider的创建,以及如何处理登录的成功和失败情况。最后,将这些组件整合到Spring Security配置中,实现了一个简洁的短信验证码登录系统。
摘要由CSDN通过智能技术生成


为了省去与本篇主题无关的代码,短信验证码只是一个模拟。如果你需要具体的实际例子,在下面的源码链接中除了包括每一章的代码外,还包括从头到尾的完整整合代码,方便大家参考学习。

一、理论说明

在开始编码前,先理解下短信验证码的实现流程。如果你能对《Spring Security认证过程》这篇文章有一定的了解的话,那么这篇文章的学习你会轻松许多。

1.1 用户名密码登录逻辑

废话不多说,在上一篇文章中,以标准的用户名密码登录为例,讲解了整个认证流程。大致流程如下:

先进入 UsernamePasswordAuthenticationFilter 中,根据输入的用户名和密码信息,构造出一个暂时没有鉴权的 UsernamePasswordAuthenticationToken,并将 UsernamePasswordAuthenticationToken交给 AuthenticationManager 处理。
AuthenticationManager本身并不做验证处理,他通过 for-each 遍历找到符合当前登录方式的一个 AuthenticationProvider,并交给它进行验证处理,对于用户名密码登录方式,这个 Provider 就是 DaoAuthenticationProvider
在这个 Provider中进行一系列的验证处理,如果验证通过,就会重新构造一个添加了鉴权的 UsernamePasswordAuthenticationToken,并将这个 token 传回到 UsernamePasswordAuthenticationFilter 中。
在该 Filter 的父类 AbstractAuthenticationProcessingFilter 中,会根据上一步验证的结果,跳转到 successHandler或者是failureHandler

在这里插入图片描述

1.2 短信验证码登录逻辑

我们可以仿照用户名密码登录的逻辑,来实现短信验证码的登陆逻辑。

用户名密码登录有个 UsernamePasswordAuthenticationFilter,我们搞一个 SmsAuthenticationFilter,代码粘过来改一改。
用户名密码登录需要 UsernamePasswordAuthenticationToken,我们搞一个 SmsAuthenticationToken,代码粘过来改一改。
用户名密码登录需要 DaoAuthenticationProvider,我们模仿它也 implenments AuthenticationProvider,叫做 SmsAuthenticationProvider
在这里插入图片描述
我们自己搞了上面三个类以后,想要实现的效果如上图所示。当我们使用短信验证码登录的时候:

  1. 先经过SmsAuthenticationFilter ,构造一个没有鉴权的 SmsAuthenticationToken,然后交给
    AuthenticationManager处理。
  2. AuthenticationManager通过 for-each 挑选出一个合适的 provider进行处理,当然我们希望这个
    provider要是 SmsAuthenticationProvider
  3. 验证通过后,重新构造一个有鉴权的 SmsAuthenticationToken,并返回给
    SmsAuthenticationFilter
    4.filter 根据上一步的验证结果,跳转到成功或者失败的处理逻辑。

二、代码实战

2.1 SmsAuthenticationToken

首先我们编写 SmsAuthenticationToken,这里直接参考 UsernamePasswordAuthenticationToken源码,直接粘过来,改一改

步骤:

  1. principal原本代表用户名,这里改成mobile,代表了手机号码。
  2. credentials 原本代码密码,短信登录用不到,直接删掉。
  3. SmsCodeAuthenticationToken() 两个构造方法一个是构造没有鉴权的,一个是构造有鉴权的。
  4. 剩下的几个方法去除无用属性即可。
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;

import java.util.Collection;

/**
 * 短信登录 AuthenticationToken,模仿 UsernamePasswordAuthenticationToken 实现
 */
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
   

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    /**
     * 在 UsernamePasswordAuthenticationToken 中该字段代表登录的用户名,
     * 在这里就代表登录的手机号码
     */
    private final Object principal; //存放认证信息。

    /**
     * 构建一个没有鉴权的 SmsCodeAuthenticationToken
     */
     //mobile:表示手机号。
    public SmsCodeAuthenticationToken(String mobile) {
   
        super(null);
        this.principal = mobile;
        setAuthenticated(false);
    }

    /**
     * 构建拥有鉴权的 SmsCodeAuthenticationToken
     */
    public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
   
        super(authorities);
        this.principal = principal;
        // must use super, as we override
        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");
        }

        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
   
        super.eraseCredentials();
    }
}

2.2 SmsAuthenticationFilter

然后编写 SmsAuthenticationFilter,参考 UsernamePasswordAuthenticationFilter的源码,直接粘过来,改一改。

步骤:

  1. 认证请求的方法必须为POST
  2. 从request中获取手机号
  3. 封装成自己的Authenticaiton的实现类SmsCodeAuthenticationToken(未认证)
  4. 调用 AuthenticationManager</
  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Spring Security 支持多种认证方式,其中包括短信验证码登录。下面是一个简单的实现步骤: 1. 添加依赖 在 `pom.xml` 文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </dependency> ``` 2. 配置 Security 在 `application.yml` 文件中添加以下配置: ```yaml spring: security: user: name: admin password: password sms: login: url: /login/sms parameter: phone: phone code: code ``` 其中,`user` 节点用于配置默认的用户名和密码,`sms.login` 节点用于配置短信验证码登录的 URL 和参数名。 3. 实现短信验证码登录逻辑 创建一个实现 `UserDetailsService` 接口的类,用于通过手机号查询用户信息。在该类中实现 `loadUserByUsername` 方法,根据手机号查询用户信息并返回 `UserDetails` 对象。 ```java @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserService userService; @Override public UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException { User user = userService.getByPhone(phone); if (user == null) { throw new UsernameNotFoundException("手机号不存在"); } return new User(user.getUsername(), user.getPassword(), user.getAuthorities()); } } ``` 创建一个实现 `AuthenticationProvider` 接口的类,用于处理短信验证码登录。在该类中实现 `authenticate` 方法,根据手机号和验证码验证用户信息并返回 `Authentication` 对象。 ```java @Component public class SmsAuthenticationProvider implements AuthenticationProvider { @Autowired private UserService userService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String phone = authentication.getName(); String code = authentication.getCredentials().toString(); User user = userService.getByPhone(phone); if (user == null) { throw new UsernameNotFoundException("手机号不存在"); } // 验证短信验证码 if (!"123456".equals(code)) { throw new BadCredentialsException("短信验证码错误"); } return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); } @Override public boolean supports(Class<?> authentication) { return SmsAuthenticationToken.class.isAssignableFrom(authentication); } } ``` 其中,`SmsAuthenticationToken` 是自定义的认证对象,用于封装手机号和验证码信息。 4. 配置 Security 登录流程 在 Security 配置类中添加以下配置: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsServiceImpl userDetailsService; @Autowired private SmsAuthenticationProvider smsAuthenticationProvider; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/login/sms").permitAll() .anyRequest().authenticated() .and() .formLogin().disable() .logout().disable() .authenticationProvider(smsAuthenticationProvider) .addFilterAfter(smsAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } @Bean public SmsAuthenticationFilter smsAuthenticationFilter() throws Exception { SmsAuthenticationFilter filter = new SmsAuthenticationFilter(); filter.setAuthenticationManager(authenticationManagerBean()); filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/")); filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/login?error")); return filter; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); auth.authenticationProvider(smsAuthenticationProvider); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } ``` 其中,`SmsAuthenticationFilter` 是自定义的过滤器,用于处理短信验证码登录请求。在该过滤器中,获取手机号和验证码信息,封装为 `SmsAuthenticationToken` 对象并调用 `AuthenticationManager` 进行认证。 ```java public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private String phoneParameter = "phone"; private String codeParameter = "code"; public SmsAuthenticationFilter() { super(new AntPathRequestMatcher("/login/sms", "POST")); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { String phone = obtainPhone(request); String code = obtainCode(request); if (phone == null) { phone = ""; } if (code == null) { code = ""; } phone = phone.trim(); SmsAuthenticationToken authRequest = new SmsAuthenticationToken(phone, code); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } private String obtainPhone(HttpServletRequest request) { return request.getParameter(phoneParameter); } private String obtainCode(HttpServletRequest request) { return request.getParameter(codeParameter); } private void setDetails(HttpServletRequest request, SmsAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); } public void setPhoneParameter(String phoneParameter) { this.phoneParameter = phoneParameter; } public void setCodeParameter(String codeParameter) { this.codeParameter = codeParameter; } } ``` 至此,短信验证码登录已经实现。在登录页面中,用户输入手机号和验证码后,发送 POST 请求到 `/login/sms`,即可完成短信验证码登录
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值