七、基于SpringSecurity的手机短信验证码登陆

基于对SpringSecurity中用户名密码登陆的流程分析,要实现手机号加验证码登陆需要自定义下面这几个类

SmsCodeAuthenticationFilter
package com.liaoxiang.smsCodeLogin;

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.util.Assert;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @auther Mr.Liao
 * @date 2019/8/22 17:14
 */
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
    private String mobileParameter = "mobile";
    private boolean postOnly = true;

    //设置拦截的路径为手机登陆
    public SmsCodeAuthenticationFilter() {
        super(new AntPathRequestMatcher("/mobile/login", "POST"));
    }
    //获取授权对象:Authentication的实例
    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());
        } else {
            String mobile = this.obtainMobile(request);
            if (mobile == null) {
                mobile = "";
            }
            mobile = mobile.trim();
            //拿到手机号生成SmsCodeAuthenticationToken对象给AuthenticationManager挑出来的provider去认证
            SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

    /**
     * 获取手机号的方法
     * @param request
     * @return
     */
    protected String obtainMobile(HttpServletRequest request) {
        return request.getParameter(this.mobileParameter);
    }

    protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }

    public void setMobileParameter(String mobileParameter) {
        Assert.hasText(mobileParameter, "mobile parameter must not be empty or null");
        this.mobileParameter = mobileParameter;
    }

    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

    public final String mobileParameter() {
        return this.mobileParameter;
    }

}

SmsCodeAuthenticationToken
package com.liaoxiang.smsCodeLogin;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;

/**
 * @auther Mr.Liao
 * @date 2019/8/22 17:08
 */
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {

    //用来存手机号
    private final Object principal;

    // ~ Constructors
    // ===================================================================================================

    /**
     * This constructor can be safely used by any code that wishes to create a
     * <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()}
     * will return <code>false</code>.
     *
     */
    public SmsCodeAuthenticationToken(String mobile) {
        super(null);
        this.principal = mobile;
        setAuthenticated(false);
    }

    /**
     * This constructor should only be used by <code>AuthenticationManager</code> or
     * <code>AuthenticationProvider</code> implementations that are satisfied with
     * producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
     * authentication token.
     *
     * @param principal
     * @param authorities
     */
    public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true); // must use super, as we override
    }

    // ~ Methods
    // ========================================================================================================

    @Override
    public Object getCredentials() {
        return null;
    }

    public Object getPrincipal() {
        return this.principal;
    }

    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();
    }
}

SmsCodeAuthenticationProvider
package com.liaoxiang.smsCodeLogin;

import lombok.Data;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;

/**
 * @auther Mr.Liao
 * @date 2019/8/22 17:56
 */
@Data
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
    private UserDetailsService userDetailsService;
    //
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken)authentication;
        //根据手机号去查用户信息
        UserDetails userDetails = userDetailsService.loadUserByUsername((String) authentication.getPrincipal());
        if (userDetails == null){
            throw new InternalAuthenticationServiceException("无法获取用户信息");
        }
        SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(userDetails,userDetails.getAuthorities());
        authenticationResult.setDetails(authenticationToken.getDetails());
        return authenticationResult;
    }

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

在用户表中增加手机号字段,添加根据手机号查询用户的Service方法

@Service
public class MobileLoginService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
        //如果User为null,就会抛出异常信息:UsernameNotFoundException
        User user = userMapper.findUserByMobile(mobile);
        if (user == null){
            throw new UsernameNotFoundException("该手机号还未注册");
        }
        return user;
    }
}

配置上面的几个类,再将此配置类注入到MySecurityConfig这个主配置类中,并应用到主配置中 .apply(smsCodeAuthenticationSecurityConfig),完成这些后,就能根据用户的一个手机号来完成授权

@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    @Autowired
    private FailureHandler failureHandler;
    @Autowired
    private SuccessHandler successHandler;
    @Autowired
    private MobileLoginService mobileLoginService;
    @Override
    public void configure(HttpSecurity http) {
        SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
        smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        smsCodeAuthenticationFilter.setAuthenticationFailureHandler(failureHandler);
        smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(successHandler);

        SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
        smsCodeAuthenticationProvider.setUserDetailsService(mobileLoginService);

        http.authenticationProvider(smsCodeAuthenticationProvider)
                .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

下面是获取验证码

@RestController
public class ValidateCodeController {
    public static final String IMAGE_SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
    public static final String SMS_SESSION_KEY = "SESSION_KEY_SMS_CODE";
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
    //注入图片验证码生成器的实现类对象
    @Autowired
    private ValidateCodeGenerator imageCodeGenerator;
    @Autowired
    private ValidateCodeGenerator smsCodeGenerator;
    @Autowired
    private SmsCodeSender smsCodeSender;

    @GetMapping("/code/image")
    public void createImageCode(HttpServletResponse response, HttpServletRequest request) throws IOException {
        ServletWebRequest servletWebRequest = new ServletWebRequest(request);
        //使用图形验证码生成器,生成图形验证码
        ImageCode imageCode = (ImageCode) imageCodeGenerator.generate(servletWebRequest);
        //将验证码存到session中,有效期5分钟
        sessionStrategy.setAttribute(servletWebRequest, IMAGE_SESSION_KEY, imageCode);
        // 把生成的图片以JPEG的格式写到响应的输出流里面
        ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
    }

    @GetMapping("/code/sms")
    public void createSmsCode(HttpServletResponse response, HttpServletRequest request) throws IOException {
        ServletWebRequest servletWebRequest = new ServletWebRequest(request);
        //使用图形验证码生成器,生成图形验证码
        ValidateCode smsCode = smsCodeGenerator.generate(servletWebRequest);
        //将验证码存到session中,有效期5分钟
        sessionStrategy.setAttribute(servletWebRequest, SMS_SESSION_KEY, smsCode);
        String mobile = "";
        //获取手机号
        try {
            mobile = ServletRequestUtils.getRequiredStringParameter(request, "mobile");
        } catch (ServletRequestBindingException e) {
            System.out.println(e.getMessage());
        }
        //把验证码通过短信提供商发送到手机上
        smsCodeSender.send(mobile, smsCode.getCode());
    }
}

页面

<h2>手机验证码登陆</h2>
<form action="/mobile/login" method="post">
    <table>
        <tr>
            <td>手机号:</td>
            <td><input type="text" name="mobile" value="18312345678"></td>
        </tr>
        <tr>
            <td>短信验证码:</td>
            <td>
                <input type="password" name="smsCode">
                <a href="/code/sms?mobile=18312345678" onclick="return pop();">发送短信验证码</a>
            </td>
        </tr>
        <tr>
            <td colspan="2"><button type="submit">登录</button></td>
        </tr>
    </table>
</form>

登陆时,先通过手机获取验证码,提交到后台,先判断验证码是否正确,在根据手机号查询用户是否存在,完成授权过程

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值