SpringSecurity自定义短信验证码认证(基于filter认证方式,不推荐)

在这里插入图片描述
如何自定义一个全新的认证方式。首先你需要知道整个流程是什么样的。
在这里插入图片描述

1. 实现 SmsAuthenticationToken

package com.zzh.springsecurityjson.security.sms;

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

import java.util.Collection;

/**
 * 自定义短信验证码 AuthenticationToken
 *   为什么不将 验证码也构造进来?验证码可以在 filter中进行验证
 * @date 2022/2/1
 * @since 1.0
 */
public class SmsAuthenticationToken extends AbstractAuthenticationToken {

    /**
     * 在未认证前,存放的是电话号码
     * 认证成功后: UserDetails 对象
     */
    private final Object principal;

    public SmsAuthenticationToken(Object principal) {
        super(null);
        this.principal = principal;
        this.setAuthenticated(false);
    }

    public SmsAuthenticationToken(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;
    }
}

2. 实现 SmsAuthenticationProvider

package com.example.security_demo.sms;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;

/**
 * 自定义 认证 Provider
 *
 * @author zzh
 * @date 2022/4/26
 * @since 1.0
 */
public class SmsAuthenticationProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(SmsAuthenticationToken.class, authentication,
                "Only SmsAuthenticationToken is supported");
        SmsAuthenticationToken token = ((SmsAuthenticationToken) authentication);
        String smsCode = token.getSmsCode();
        String  mobile = ((String) token.getPrincipal());
        // 在这里对验证码以及手机号进行验证
        if (!checkCode(smsCode)) {
            throw new UsernameNotFoundException("验证码错误");
        }
        // 如果验证码正确便可以通过电话号码到数据库或缓存中获取用户的信息,在这里需要引入一个新的概念
        // UserDetails 和 权限的概念
        System.out.println(mobile);
        return null;
    }

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

    /**
     * 我在这里做了简化的处理,正常情况下应该是在session 或者  redis 等缓存中获取比对
     * 同时一定要注意: 如果是通过session,一定要把验证流程放到过滤器中,因为在provider对象中
     * 注入 request对象会比较难以处理,可以放到 SmsAuthenticationFilter中验证也可以重新定义一个
     * filter.
     * @param smsCode 短信验证码
     * @return 如果验证码正确返回true 反之返回false
     */
    private boolean checkCode(String smsCode) {
        return smsCode != null && smsCode.length() > 0;
    }
}

3. 实现SmsAuthenticationFilter

package com.example.security_demo.sms;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 自定义 手机验证码认证过滤器
 *
 * @author zzh
 * @date 2022/4/23
 * @since 1.0
 */
public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private final ObjectMapper objectMapper = new ObjectMapper();

    private final static String SMS_MOBILE_KEY = "mobile";

    private final static String SMS_CODE_KEY = "smsCode";

    private final static AntPathRequestMatcher ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/sms/login");

    private final static String POST_REQUEST_METHOD = "POST";


    public SmsAuthenticationFilter() {
        super(ANT_PATH_REQUEST_MATCHER);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equalsIgnoreCase(POST_REQUEST_METHOD)) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        // 从请求体中获取 电话号码和验证码 request
        if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) {
            try {
                JsonNode jsonBody = objectMapper.readTree(request.getInputStream());
                String mobile = jsonBody.get(SMS_MOBILE_KEY).textValue();
                String smsCode = jsonBody.get(SMS_CODE_KEY).textValue();
                // 调用 AuthenticationManager 进行认证, 
                // 所以需要注入 AuthenticationManager 对象 通过父类的setAuthenticationManager 进行注入
                return this.getAuthenticationManager().authenticate(new SmsAuthenticationToken( mobile, smsCode));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
	// 此处调用其他逻辑 不如调用form表单登录即前后端不分离的逻辑(参考UsernamePasswordAuthenticaitonFilter),此处做简化
        return null;
    }
}

4. 在 SpringSecurity 配置文件中进行配置

此处一定要注意将系统的AuthenticationManager以Bean的方式进行暴露,这样会很方便我们进行认证。同时我们可以在任何地方进行注入,而不是局限于于在filter中进行验证。不如我们完全可以在controller中调用AuthenticationManager的认证方法进行认证。
在这里插入图片描述

package com.example.security_demo.config;

import com.example.security_demo.sms.SmsAuthenticationFilter;
import com.example.security_demo.sms.SmsAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * spring security configuration
 *  如何禁用
 * 需要理解 spring security 配置文件理解
 *  默认的配置是什么
 * @author zzh
 * @date 2022/4/15
 * @since 1.0
 */
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/sms/login").permitAll().anyRequest().authenticated()
                .and().csrf().disable();

        // 配置 过滤器
        http.addFilterBefore(smsAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        // 配置 AuthenticationProvider, 将自定义AuthenticationProvider注入到系统AuthenticationManager中
        http.authenticationProvider(new SmsAuthenticationProvider());


    }

    /**
     *  暴露  spring security中的 AuthenticationManager 对象,使得的用户可以在需要时进行对象注入
     *  例如:
     *  
     * 通过这种方法进行注入,然后调用
     *      @autowired
     *      AuthenticationManager authenticationManager;
     *      
     * @return AuthenticationManager
     * @throws Exception 异常
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 配置 filer, 此处要注意: 一定要注入AuthenticationManager
     * @return
     * @throws Exception
     */
    @Bean
    public SmsAuthenticationFilter smsAuthenticationFilter() throws Exception {
        SmsAuthenticationFilter smsAuthenticationFilter = new SmsAuthenticationFilter();
        smsAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
        return smsAuthenticationFilter;
    }


    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (request, response, exception) -> {

            response.setContentType("application/json;utf-8");
            response.getWriter().write("Exception Handler");
        };
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值