【精】Springboot+SpringSecurity+JWT+短信登录认证

提示:请配合上篇文章更容易理解: 【精】 一分钟读懂Spring Security的基本原理_GeeLoong`s Blog-CSDN博客

继springboot+springsecurity+JWT文章,实现短信登录
springboot+springsecurity+JWT
在这里插入图片描述
短信登录代码在SendSMs中,SendSmsSecurityConfig类为配置类,需引入SecurityConfig类中,也可直接在SecurityConfig类配置
短信验证流程:
在这里插入图片描述
文中把短信信息保存到redis中进行校验
在controller写获取短信验证码的类

/**
 * 发送短信登录
 * 
 * 根据实际获取短信验证码方式进行编写
 */
@RestController
@RequestMapping("/")
public class SendSmsController {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    //获取验证码
    @RequestMapping("/SendSms")
    public String sendSms(@RequestParam("/telephone") String telephone) {

        //随机生成4位数字验证码
        String code = String.valueOf((int) ((Math.random() * 9 + 1) * 1000));


        //把验证码存储到redis中过期时间为120秒
        stringRedisTemplate.opsForValue().set(telephone, code, 120, TimeUnit.SECONDS);
        return null;

    }
}

为了方便在test中进行测试无需在controller发送短信

@SpringBootTest
@RunWith(SpringRunner.class)
public class testRedis {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Test
    public void testredis(){
        stringRedisTemplate.opsForValue().set("1","1234",1200, TimeUnit.SECONDS);
        Long expire = stringRedisTemplate.getExpire("1", TimeUnit.SECONDS);
        System.out.println(expire);
        String s = stringRedisTemplate.opsForValue().get("1");
        System.out.println(s);
    }

    @Test
    public void testredis1(){
        //把key,value值保存到redis中,时间为1200
        stringRedisTemplate.opsForValue().set("123","1234",1200, TimeUnit.SECONDS);
        //根据key值查询剩余时间
        Long expire = stringRedisTemplate.getExpire("123", TimeUnit.SECONDS);
        System.out.println(expire);
        //根据key值输出value
        String s = stringRedisTemplate.opsForValue().get("123");
        System.out.println(s);
    }
}

SendSmsFilter 类

配置验证码校验的过滤器
继承OncePerRequestFilter类,每一次运行都会优先执行SendSmsFilter类

/**
 * 进行验证码校验的过滤器
 */
@Component
public class SendSmsFilter extends OncePerRequestFilter {


    @Autowired
    StringRedisTemplate stringRedisTemplate;

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

        //请求路径为Smslogin,并且为post类型
        if (StringUtils.equals("/Smslogin",request.getRequestURI())
                && StringUtils.equalsIgnoreCase(request.getMethod(),"post")){
            //获取手机号
            String username = request.getParameter("telephone");
            //获取验证码
            String password = request.getParameter("word");
            //获取redis中保存的验证码
            String code = stringRedisTemplate.opsForValue().get(username);
            if (code != null && password != null && password.equals(code)) {
 				//删除redis中保存的验证码
                stringRedisTemplate.delete(username);
                System.out.println("验证码输入正确");
            }else {
                throw new InternalAuthenticationServiceException("验证码错误");
            }

        }
        chain.doFilter(request, response);
    }
}

“/Smslogin”,"telephone"是在SendSmsAuthenticationFilter类中自定义的。

SendSmsAuthenticationFilter类

SendSmsAuthenticationFilter相当于图片中的SmsAuthenticationFilter
该类模拟 UsernamePasswordAuthenticationFilter类使用手机号进行身份验证的一个过滤器

/**
 *
 * UsernamePasswordAuthenticationFilter是AbstractAuthenticationProcessingFilter针对使用用户名和密码进行身份验证而定制化的一个过滤器。
 *
 *
 * 模拟 UsernamePasswordAuthenticationFilter类使用手机号进行身份验证的一个过滤器
 */
public class SendSmsAuthenticationFilter extends
        AbstractAuthenticationProcessingFilter {


    public static final String SPRING_SECURITY_FORM_TELEPHONE_KEY = "telephone";

    //验证短信验证码,传递手机号的参数的名称【telephone】
    private String telephoneParameter = SPRING_SECURITY_FORM_TELEPHONE_KEY;
    //指定请求时post形式
    private boolean postOnly = true;


    public SendSmsAuthenticationFilter() {
        //拦截访问路径,访问请求方式
        super(new AntPathRequestMatcher("/Smslogin", "POST"));
    }


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }

        String telephone = obtainTelephone(request);

        if (telephone == null) {
            telephone = "";
        }


        telephone = telephone.trim();


        //自定义SendSmsAuthenticationToken类实现其方法
        SendSmsAuthenticationToken authRequest = new SendSmsAuthenticationToken(
                telephone);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);
    }

    protected String obtainTelephone(HttpServletRequest request) {
        return request.getParameter(telephoneParameter);
    }

    /*
     * 功能描述:提供身份验证请求的详细属性
     * 入参:[request 为此创建身份验证请求, authRequest 详细信息集的身份验证请求对象]
     */
    protected void setDetails(HttpServletRequest request,
                              SendSmsAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }


    /*
     * 功能描述:设置用于获取用户名的参数名的登录请求。
     * 入参:[telephoneParameter 默认为“用户名”。]
     */
    public void setTelephoneParameter(String telephoneParameter) {
        Assert.hasText(telephoneParameter, "Telephone parameter must not be empty or null");
        this.telephoneParameter = telephoneParameter;
    }


    /**
     * 功能描述:定义此筛选器是否只允许HTTP POST请求。如果设置为true,则接收不到POST请求将立即引发异常并不再继续身份认证
     */
    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

    public final String getTelephoneParameter() {
        return telephoneParameter;
    }

}

SendSmsAuthenticationToken类

SendSmsAuthenticationToken相当于图片中的SmsAuthenticationToken
该类模拟 UsernamePasswordAuthenticationToken类

/**
 *
 * 模拟 UsernamePasswordAuthenticationToken类
 *
 */
public class SendSmsAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = 510L;

    //存放认证信息,认证之前放的是手机号,认证之后UserDetails
    private final Object principal;


    /**
     * 功能描述:创建用户身份验证令牌需要用到此构造函数
     * 返回值:通过身份验证的代码返回false
     */
    public SendSmsAuthenticationToken(Object principal) {
        super((Collection)null);
        this.principal = principal;
        this.setAuthenticated(false);
    }

    /**
     * 功能描述:产生身份验证令牌
     * @param principal
     * @param authorities
     */
    public SendSmsAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        super.setAuthenticated(true);
    }


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

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }
    @Override
    public Object getCredentials() {
        return null;
    }
}

SendSmsAuthenticationProvider类

SendSmsAuthenticationProvider相当于图片中的SmsAuthenticationProvider
该类模拟DaoAuthenticationProvider类的父类AbstractUserDetailsAuthenticationProvider类中实现的AuthenticationProvider类

/**
 * 模拟:DaoAuthenticationProvider类的父类AbstractUserDetailsAuthenticationProvider类中实现的AuthenticationProvider类
 */
public class SendSmsAuthenticationProvider implements AuthenticationProvider {


    private UserDetailsService userDetailsService;



    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    protected UserDetailsService getUserDetailsService() {
        return this.userDetailsService;
    }


    /**
     *实现AuthenticationProvider类中的两个方法
     *
     * 1,授权
     */

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        SendSmsAuthenticationToken authenticationToken = (SendSmsAuthenticationToken)authentication;
        UserDetails userDetails = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());
        if (userDetails == null){
            throw new InternalAuthenticationServiceException("无法根据手机号获取用户信息");
        }
        SendSmsAuthenticationToken authenticationResult = new SendSmsAuthenticationToken(userDetails,userDetails.getAuthorities());
        authenticationResult.setDetails(authenticationToken.getDetails());
        return authenticationResult;
    }

    /**
     * 2,通过不同的Token进行不同的Provider验证
     */
    @Override
    public boolean supports(Class<?> aClass) {
        return SendSmsAuthenticationToken.class.isAssignableFrom(aClass);
    }
}

SendSmsSecurityConfig类

配置SendSmsSecurityConfig类用于短信认证的

@Component
    public class SendSmsSecurityConfig
        extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    @Autowired
    MyUserDetailsService myUserDetailsService;

    @Autowired
    SendSmsFilter sendSmsFilter;
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //认证授权
        SendSmsAuthenticationFilter sendSmsAuthenticationFilter = new SendSmsAuthenticationFilter();
        sendSmsAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        sendSmsAuthenticationFilter.setAuthenticationSuccessHandler(new LoginSuccessHandler());
        sendSmsAuthenticationFilter.setAuthenticationFailureHandler(new LoginFailureHandler());

        SendSmsAuthenticationProvider sendSmsAuthenticationProvider = new SendSmsAuthenticationProvider();
        sendSmsAuthenticationProvider.setUserDetailsService(myUserDetailsService);

        //sendSmsFilter在UsernamePasswordAuthenticationFilter前面执行
        http.addFilterBefore(sendSmsFilter, UsernamePasswordAuthenticationFilter.class);
        http.authenticationProvider(sendSmsAuthenticationProvider)
                .addFilterAfter(sendSmsAuthenticationFilter,UsernamePasswordAuthenticationFilter.class);
    }
}

在SecurityConfig类中声明引入
在这里插入图片描述在这里插入图片描述在Postman中进行测试
telephone为在SendSmsAuthenticationFilter类自定义的参数
在这里插入图片描述执行成功后会在LoginSuccessHandler类(登陆成功处理)中生成token ——参考springboot-springsecurity-JWT
在Headers中会生成tokentoken在验证时,在Key中存放 Authorization ,在Value中把生成的token保存进去,然后会在MyJwtTokenFilter类中进行令牌校验
成功示例:
在这里插入图片描述
继springboot+springsecurity+JWT文章,实现短信登录
springboot+springsecurity+JWT https://blog.csdn.net/weixin_45498999/article/details/105973865
本文参考课程内容介绍 · Spring Security-JWT-OAuth2一本通(基于SpringBoot2.0) · 看云
视频链接:
https://www.bilibili.com/video/av76522147/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值