SpringOauth初级开发五 自定义认证页面中增加短信验证码认证登录(上)

SpringOauth初级开发五 自定义认证页面中增加短信验证码认证登录(上)_第1张图片
image.png

在QIQIHAL-SECURITY-CORE\src\main\java\com\qiqihal\validatecode路径下创建smsvalidatecode文件,在该文件夹中创建SmsValidateCode.java、SmsValidateCodeAuthenticationConfig.java、SmsValidateCodeAuthenticationFilter.java、
SmsValidateCodeAuthenticationProvider.java.java、
SmsValidateCodeAuthenticationToken.java、SmsValidateCodeFilter.java、SmsValidateCodeGenerator.java、这七个文件

SpringOauth初级开发五 自定义认证页面中增加短信验证码认证登录(上)_第2张图片
image.png

SmsValidateCode.java文件继承ValidateCode类,ValidateCode类的作用是保存短信验证码validateCode、过期时间expireTime,这两个值

QIQIHAL-SECURITY-CORE\src\main\java\com\qiqihal\validatecode\smsvalidatecode\SmsValidateCode.java文件内容如下

package com.qiqihal.validatecode.smsvalidatecode;

import com.qiqihal.validatecode.ValidateCode;

public class SmsValidateCode extends ValidateCode{

    public SmsValidateCode(String validateCode, int expireSecond) {
        super(validateCode, expireSecond);
    }
}

SmsValidateCodeAuthenticationFilter.java文件的作用是对手机号进行认证过,以为我们在配置SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器类的SmsValidateCodeAuthenticationConfig类的configure方法中使用addFilterAfter将SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器类,加入到SpringSecurity过滤器链中UsernamePasswordAuthenticationFilter过滤器之后,所以username、password已经在SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器认证手机号之前就已经认证通过,SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器只需要认证手机号

QIQIHAL-SECURITY-CORE\src\main\java\com\qiqihal\validatecode\smsvalidatecode\SmsValidateCodeAuthenticationFilter.java内容如下

package com.qiqihal.validatecode.smsvalidatecode;

import com.qiqihal.properties.SecurityProperties;
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;

/*UsernamePasswordAuthenticationFilter类的attemptAuthentication方法,主要获取request传递的参数,
UsernamePasswordAuthenticationToken类,主要用于增加认证所用到的额外参数,
DaoAuthenticationProvider类的retrieveUser方法,主要把UsernamePasswordAuthenticationToken扩展类的额外参数传递给UserDetailsService,
UserDetailsService实现类的loadUserByUsername方法,主用使用额外参数及账号、密码等多个参数进行验证,
UsernamePasswordAuthenticationFilter过滤器处理请求流程:
UsernamePasswordAuthenticationFilter→UsernamePasswordAuthenticationToken→AuthenticationManager→DaoAuthenticationProvider→DaoAuthenticationService→UserDetails→Authentication
SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器类处理请求流程:
SmsValidateCodeAuthenticationFilter→SmsValidateCodeAuthenticationToken→AuthenticationManager→SmsValidateCodeAuthenticationProvider→UserDetailsService→UserDetails→Authentication

在SmsValidateCodeAuthenticationConfig类的configure方法中,
使用addFilterAfter将SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器类,
加入到SpringSecurity过滤器链中UsernamePasswordAuthenticationFilter过滤器之后,
在QIQIHAL-SECURITY-BROWSER\src\main\java\com\qiqihal\config\securityconfig\QiqihalSecurityConfig.java的configure方法中,
将SmsValidateCodeFilter自定义校验短信验证码过滤器放在,
SpringSecurity过滤器链中UsernamePasswordAuthenticationFilter过滤器前,
在该类的configure方法中使用apply将SmsValidateCodeAuthenticationConfig配置类加入到SpringSecurity的配置中
*/
public class SmsValidateCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter{

    public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";

    private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
    /*SmsValidateCodeFilter自定义手机号认证过滤器类是否只处理post请求*/
    private boolean postOnly = true;

    /*过滤请求*/
    public SmsValidateCodeAuthenticationFilter(SecurityProperties securityProperties) {
        /*请求匹配器,匹配请求路径为/authentication/html的POST请求*/
        super(new AntPathRequestMatcher(securityProperties.getValidateCode().getSmsValidateCode().getAuthenticationUrl(), "POST"));
    }

    /*认证流程*/
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        /*如果不是POST请求则抛出异常*/
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    /*认证方法不支持该请求*/
                    request.getMethod() + "不支持非POST方式的请求!");
        }

        /*从HttpServletRequest获取请求中请求参数名为mobile的值*/
        String mobile = obtainMobile(request);

        /*如果mobile的值为空,则传空字符串*/
        if (mobile == null) {
            mobile = "";
        }

        /*如果mobile的值为空,则去掉空格*/
        mobile = mobile.trim();

        /*创建SmsValidateCodeAuthenticationToken自定义校验验证码认证Token*/
        SmsValidateCodeAuthenticationToken authRequest = new SmsValidateCodeAuthenticationToken(
                mobile);

        /*将请求信息保存到SmsValidateCodeAuthenticationToken验证码认证Token中*/
        setDetails(request, authRequest);

        /*最后调用AuthenticationManager接口的实现类的authenticate方法,
          将AbstractAuthenticationToken接口的实现类作为入参,
          AuthenticationManager接口的实现类会检索SpringSecurity中所有AuthenticationProvider接口的实现类,
          当AuthenticationManager接口的实现类的authenticate方法的入参的类型,
          与AuthenticationProvider接口的实现类的supports方法入参类型一致,
          则将AuthenticationManager接口的实现类则调用该AuthenticationProvider接口的实现类的supports方法,
          并将AuthenticationManager接口的实现类的authenticate方法传入的AbstractAuthenticationToken接口的实现类作为supports方法的入参*/
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    /*从HttpServletRequest获取请求中请求参数名为mobile的值*/
    protected String obtainMobile(HttpServletRequest request) {
        return request.getParameter(mobileParameter);
    }

    protected void setDetails(HttpServletRequest request,
                              SmsValidateCodeAuthenticationToken authRequest) {
        authRequest.setDetails(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 getMobileParameter() {
        return mobileParameter;
    }

}

SmsValidateCodeAuthenticationToken.java文件的作用是用于增加认证所用到的额外参数,封装认证后的用户信息权限等

QIQIHAL-SECURITY-CORE\src\main\java\com\qiqihal\validatecode\smsvalidatecode\SmsValidateCodeAuthenticationToken.java文件内容如下

package com.qiqihal.validatecode.smsvalidatecode;

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;

/* SmsValidateCodeAuthenticationFilter自定义短信验证码认证过滤器处理请求流程:
SmsValidateCodeAuthenticationFilter→SmsValidateCodeAuthenticationToken→AuthenticationManager→SmsValidateCodeAuthenticationProvider→UserDetailsService→UserDetails→Authentication
我们在自定义AuthenticationProvider接口实现类中验证手机号是否在数据库中存在,把Authentication接口的实现类扩展的额外参数传递给UserDetailsService,
并将保存用户信息的UserDetails的实体、用户权限封装到Authentication的实现类中,
最终返回给SmsValidateCodeFilter自定义短信验证码认证过滤器的attemptAuthentication方法中AuthenticationManager接口的实现类调用的authenticate方法的作为返回值

我们在SmsValidateCodeAuthenticationProvider类中除了可以验证手机号码,也可以校验短信验证码,
但为了让更多功能也能使用短信验证码验证功能,我们将短信验证验证码功能单独封装到自定义过滤器中,
这样将自定义短信验证码验证过滤器加在其他功能之前,
任何功能都可以使用短信验证码进行认证*/
public class SmsValidateCodeAuthenticationProvider implements AuthenticationProvider {


    private UserDetailsService userDetailsService;

    public UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

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

    @Override
    /*组装UserDetails,传入未认证手机号之前的SmsValidateCodeAuthenticationToken*/
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        SmsValidateCodeAuthenticationToken authenticationToken = (SmsValidateCodeAuthenticationToken)authentication;

        /*最终调用UserDetailsService的实现类QIQIHAL-SECURITY-BROWSER\src\main\java\com\qiqihal\config\securityconfig\QiqihalUserDetailsService.java,
          的loadUserByUsername方法对SmsValidateCodeAuthenticationToken保存的手机号进行认证*/
        UserDetails user = userDetailsService.loadUserByUsername(
                /*从SmsValidateCodeAuthenticationToken类中取出principal属性保存手机号*/
                (String)authenticationToken.getPrincipal());

        /*如果得不到用户信息,则抛出异常*/
        if(user == null){
            throw new InternalAuthenticationServiceException("无法获取用户信息");
        }

        /*如果得到用户信息,则重新创建SmsValidateCodeAuthenticationToken对象,
        使用SmsValidateCodeAuthenticationToken的第二个构造方法,传入UserDetail,user.getAuthorities()用户权限*/
        SmsValidateCodeAuthenticationToken authenticationResult = new SmsValidateCodeAuthenticationToken(user,user.getAuthorities());

        /*将未认证手机号之前创建的SmsValidateCodeAuthenticationToken中的Userdetails保存到,
          已认证手机号之后创建SmsValidateCodeAuthenticationToken中,
          最终返回给SmsValidateCodeFilter自定义短信验证码认证过滤器的attemptAuthentication方法中AuthenticationManager接口的实现类调用的authenticate方法的返回值*/
        authenticationResult.setDetails(authentication.getDetails());
        return authenticationResult;
    }

    @Override
    /*是否是SmsValidateCodeAuthenticationProvider支持的Token,靠该方法判断*/
    public boolean supports(Class authentication) {
        /*判断传入的Token是否是SmsValidateCodeAuthenticationToken类型,
        isAssignableFrom  是用来判断一个类Class1和另一个类Class2是否相同或者Class1类是不是Class2的父类,
        Class1.isAssignableFrom(Class2)*/
        return SmsValidateCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

SmsValidateCodeAuthenticationProvider.java文件的作用是验证手机号是否在数据库中存在,把Authentication接口的实现类扩展的额外参数传递给UserDetailsService,并将保存用户信息的UserDetails的实体、用户权限封装到Authentication的实现类中,最终返回给SmsValidateCodeFilter自定义短信验证码认证过滤器的attemptAuthentication方法中AuthenticationManager接口的实现类调用的authenticate方法的作为返回值
我们在SmsValidateCodeAuthenticationProvider类中除了可以验证手机号码,也可以校验短信验证码,但为了让更多功能也能使用短信验证码验证功能,我们将短信验证码验证功能单独封装到自定义过滤器中,这样将自定义短信验证码验证过滤器加在其他功能之前,任何功能都可以使用短信验证码进行认证

QIQIHAL-SECURITY-CORE\src\main\java\com\qiqihal\validatecode\smsvalidatecode\SmsValidateCodeAuthenticationProvider.java文件内容如下

package com.qiqihal.validatecode.smsvalidatecode;

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;

/* SmsValidateCodeAuthenticationFilter自定义短信验证码认证过滤器处理请求流程:
SmsValidateCodeAuthenticationFilter→SmsValidateCodeAuthenticationToken→AuthenticationManager→SmsValidateCodeAuthenticationProvider→UserDetailsService→UserDetails→Authentication
我们在自定义AuthenticationProvider接口实现类中验证手机号是否在数据库中存在,并将保存用户信息的UserDetails的实体保存到Authentication的实现类中,
最终返回给SmsValidateCodeAuthenticationFilter自定义短信验证码认证过滤器的attemptAuthentication方法中AuthenticationManager接口的实现类调用的authenticate方法的作为返回值

我们在SmsValidateCodeAuthenticationProvider类中除了可以验证手机号码,也可以校验短信验证码,
但为了让更多功能也能使用短信验证码认证功能,我们将短信验证码验证功能单独封装到自定义过滤器中,
这样将自定义短信验证码验证过滤器加在其他功能之前,
任何功能都可以使用短信验证码进行验证*/
public class SmsValidateCodeAuthenticationProvider implements AuthenticationProvider {


    private UserDetailsService userDetailsService;

    public UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

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

    @Override
    /*组装UserDetails,传入未认证手机号之前的SmsValidateCodeAuthenticationToken*/
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        SmsValidateCodeAuthenticationToken authenticationToken = (SmsValidateCodeAuthenticationToken)authentication;

        /*最终调用UserDetailsService的实现类QIQIHAL-SECURITY-BROWSER\src\main\java\com\qiqihal\config\securityconfig\QiqihalUserDetailsService.java,
          的loadUserByUsername方法对SmsValidateCodeAuthenticationToken保存的手机号进行认证*/
        UserDetails user = userDetailsService.loadUserByUsername(
                /*从SmsValidateCodeAuthenticationToken类中取出principal属性保存手机号*/
                (String)authenticationToken.getPrincipal());

        /*如果得不到用户信息,则抛出异常*/
        if(user == null){
            throw new InternalAuthenticationServiceException("无法获取用户信息");
        }

        /*如果得到用户信息,则重新创建SmsValidateCodeAuthenticationToken对象,
        使用SmsValidateCodeAuthenticationToken的第二个构造方法,传入UserDetail,user.getAuthorities()用户权限*/
        SmsValidateCodeAuthenticationToken authenticationResult = new SmsValidateCodeAuthenticationToken(user,user.getAuthorities());

        /*将未认证手机号之前创建的SmsValidateCodeAuthenticationToken中的Userdetails保存到,
          已认证手机号之后创建SmsValidateCodeAuthenticationToken中,
          最终返回给SmsValidateCodeAuthenticationFilter自定义短信验证码认证过滤器的attemptAuthentication方法中AuthenticationManager接口的实现类调用的authenticate方法的返回值*/
        authenticationResult.setDetails(authentication.getDetails());
        return authenticationResult;
    }

    @Override
    /*是否是SmsValidateCodeAuthenticationProvider支持的Token,靠该方法判断*/
    public boolean supports(Class authentication) {
        /*判断传入的Token是否是SmsValidateCodeAuthenticationToken类型,
        isAssignableFrom  是用来判断一个类Class1和另一个类Class2是否相同或者Class1类是不是Class2的父类,
        Class1.isAssignableFrom(Class2)*/
        return SmsValidateCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

SmsValidateCodeAuthenticationConfig文件的作用是向SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器类继承的父类AbstractAuthenticationProcessingFilter的接口属性赋该接口属性的实现类,并将SmsValidateCodeAuthenticationFilter类、SmsValidateCodeAuthenticationProvider类注入到Spring容器中,最后将我们的SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器类,加入到SpringSecurity过滤器链中UsernamePasswordAuthenticationFilter过滤器之后,username、password已经在SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器认证手机号之前就已经认证通过,SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器只需要认证手机号,同时还要在在QIQIHAL-SECURITY-BROWSER\src\main\java\com\qiqihal\config\securityconfig\QiqihalSecurityConfig.java的configure方法中,使用apply将SmsValidateCodeAuthenticationConfig配置类加入到SpringSecurity的配置中,我们的SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器类和SmsValidateCodeAuthenticationProvider类才能生效

package com.qiqihal.validatecode.smsvalidatecode;

import com.qiqihal.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

@Component
/*继承SecurityConfigurerAdapter,
  将SmsValidateCodeAuthenticationFilter类、SmsValidateCodeAuthenticationProvider类注入到Spring容器中*/
public class SmsValidateCodeAuthenticationConfig extends SecurityConfigurerAdapter {

    @Autowired
    private SecurityProperties securityProperties;
    @Autowired
    /*从Spring容器中引入类型为接口,名字为实现类的实体*/
    private AuthenticationSuccessHandler qiqihalAuthenticationSuccessHandler;
    @Autowired
    private AuthenticationFailureHandler qiqihalAuthenticationFailureHandler;
    @Autowired
    private UserDetailsService qiqihalUserDetailsService;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        /*创建SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器类,
        向该类继承的父类AbstractAuthenticationProcessingFilter的接口属性赋该接口属性的实现类*/
        SmsValidateCodeAuthenticationFilter smsValidateCodeAuthenticationFilter = new SmsValidateCodeAuthenticationFilter(securityProperties);
        /*向AuthenticationManager接口属性赋值*/
        smsValidateCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        /*向successHandler接口属性赋QiqihalAuthenticationSuccessHandler认证成功后的自定义处理类*/
        smsValidateCodeAuthenticationFilter.setAuthenticationSuccessHandler(qiqihalAuthenticationSuccessHandler);
        /*向failureHandler接口属性赋QiqihalAuthenticationFailureHandler认证成功后的自定义处理类*/
        smsValidateCodeAuthenticationFilter.setAuthenticationFailureHandler(qiqihalAuthenticationFailureHandler);

        /*创建SmsValidateCodeAuthenticationProvider自定义AuthenticationProvider接口实现类,
        向该类的接口属性赋该接口属性的实现类*/
        SmsValidateCodeAuthenticationProvider smsValidateCodeAuthenticationProvider = new SmsValidateCodeAuthenticationProvider();
        smsValidateCodeAuthenticationProvider.setUserDetailsService(qiqihalUserDetailsService);

        /*将SmsValidateCodeAuthenticationProvider自定义AuthenticationProvider接口实现类,
        加入到AuthenticationManager接口实现类管理的AuthenticationProvider接口实现类集合中去*/
        http.authenticationProvider(smsValidateCodeAuthenticationProvider)
             /*最后将我们的SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器类,
               加入到SpringSecurity过滤器链中UsernamePasswordAuthenticationFilter过滤器之后,
               username、password已经在SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器认证手机号之前就已经认证通过,
               SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器只需要认证手机号,

               在QIQIHAL-SECURITY-BROWSER\src\main\java\com\qiqihal\config\securityconfig\QiqihalSecurityConfig.java的configure方法中,
               将SmsValidateCodeFilter自定义校验短信验证码过滤器放在,
               SpringSecurity过滤器链中UsernamePasswordAuthenticationFilter过滤器前,
               同时还要在该类的configure方法中使用apply将SmsValidateCodeAuthenticationConfig配置类加入到SpringSecurity的配置中,
               我们的SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器类和SmsValidateCodeAuthenticationProvider类才能生效*/
            .addFilterAfter(smsValidateCodeAuthenticationFilter,UsernamePasswordAuthenticationFilter.class);
    }
}

SmsValidateCodeFilter.java文件的作用短信验证码验证
QIQIHAL-SECURITY-CORE\src\main\java\com\qiqihal\validatecode\smsvalidatecode\SmsValidateCodeFilter.java文件内容如下

package com.qiqihal.validatecode.smsvalidatecode;

import com.qiqihal.validatecode.ValidateCodeController;
import com.qiqihal.validatecode.ValidateCodeException;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/*SmsValidateCodeFilter自定义校验短信验证码过滤器,OncePerRequestFilter过滤器Spring提供的过滤器,只执行一次,
SmsValidateCodeFilter自定义校验短信验证码过滤器,用来过滤从配置文件中读取的提交username、password、mobile、validateCode的自定义认证请求,
但只从自定义认证请求中读取validateCode验证码的值,与从Session中取出的保存的ValidateCode实体中的code验证码进行对比
如果不匹配则抛出将ValidateCodeException异常传入,
将ValidateCodeException异常传入实现AuthenticationFailureHandler接口的认证失败后的自定义处理类QiqihalAuthenticationFailureHandler的onAuthenticationFailure方法中,
认证失败后的自定义处理类,将认证失败信息,和自定义认证页面请求路径,封装成Json数据返回前端SpringSecurity过滤链就会终止,
不会进入,包括SpringSecurity过滤链上的过滤器
因为在QIQIHAL-SECURITY-BROWSER\src\main\java\com\qiqihal\config\securityconfig\QiqihalSecurityConfig类的configure方法中,
添加.addFilterBefore(ValidateCodeFilter, UsernamePasswordAuthenticationFilter.class),
将SmsValidateCodeFilter自定义校验短信验证码过滤器放在,SpringSecurity过滤器链中UsernamePasswordAuthenticationFilter过滤器之前,
所以如果匹配,则继续将提交username、password、mobile、validateCode的自定义认证请求,
传入SpringSecurity过滤器链中SmsValidateCodeFilter自定义校验短信验证码过滤器后面的UsernamePasswordAuthenticationFilter过滤器中,
从提交username、password、mobile、validateCode的自定义认证请求读取username、password的值保存到UsernamePasswordAuthenticationToken类中,
DaoAuthenticationProvider类中从UsernamePasswordAuthenticationToken类取出username传入UserDetailsService接口的实现类QIQIHAL-SECURITY-BROWSER\src\main\java\com\qiqihal\config\securityconfig\AuthenticationUsernamePasswordUserDetailsService类的loadUserByUsername方法中,
在该方法中根据username从数据库中查询出包含加密的password的记录并封装到实现UserDetails接口的User类中,
最后执行DaoAuthenticationProvider类的additionalAuthenticationChecks方法,
该方法将"保存数据库中加密password字段值的User类"和"保存从自定义认证请求提交的username、password值的UsernamePasswordAuthenticationToken类"作为参数,
然后从UsernamePasswordAuthenticationToken类中取出password值也进行加密,
加密后的结果与User类加密的password值进行对比以此判断自定义认证请求提交的username、password是否正确,
如果正确则继续执行SpringSecurity过滤器连,将提交username、password、mobile、validateCode的自定义认证请求,
传入SpringSecurity过滤器链中UsernamePasswordAuthenticationFilter过滤器中后面的SmsValidateCodeAuthenticationFilter自定义手机号认证过滤器,
从提交username、password、mobile、validateCode的自定义认证请求读取mobile的值保存到SmsValidateCodeAuthenticationToken类中,
SmsValidateCodeAuthenticationProvider类中从SmsValidateCodeAuthenticationToken类取出mobile传入UserDetailsService接口的实现类QIQIHAL-SECURITY-BROWSER\src\main\java\com\qiqihal\config\securityconfig\AuthenticationUsernamePasswordUserDetailsService类的loadUserByUsername方法中,
在该方法中根据mobile从数据库中查询出包含加密的password的记录并封装到实现UserDetails接口的User类中,
最后执行DaoAuthenticationProvider类的additionalAuthenticationChecks方法,
该方法将"保存数据库中加密password字段值的User类"和"保存从自定义认证请求提交的username、password值的UsernamePasswordAuthenticationToken类"作为参数,
然后从UsernamePasswordAuthenticationToken类中取出password值也进行加密,
加密后的结果与User类加密的password值进行对比以此判断自定义认证请求提交的username、password是否正确*/
public class SmsValidateCodeFilter extends OncePerRequestFilter{

    public static final String SPRING_SECURITY_VALIDATE_CODE_KEY = "validateCode";

    private String validateCodeParameter = SPRING_SECURITY_VALIDATE_CODE_KEY;

    /*ValidateCodeFilter自定义校验验证码认证过滤器是否只处理post请求*/
    private boolean postOnly = true;

    /*提交username、password、validateCode的认证请求路径*/
    private String authenticationUrl;

    /*是否开启图片认证*/
    private Boolean isOpenValidateCode;

    private AuthenticationFailureHandler authenticationFailureHandler;

    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    public SmsValidateCodeFilter(String authenticationUrl, Boolean isOpenValidateCode, AuthenticationFailureHandler authenticationFailureHandler) {
        this.authenticationUrl = authenticationUrl;
        this.isOpenValidateCode = isOpenValidateCode;
        this.authenticationFailureHandler = authenticationFailureHandler;
    }

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

        if (postOnly && request.getMethod().equalsIgnoreCase("POST") && authenticationUrl.equals(request.getServletPath())) {
            if (isOpenValidateCode) {
                try {
                    //将表单提交的图像验证码和session中保存的图像验证码进行校验
                    validate(new ServletWebRequest(request));
                } catch (ValidateCodeException e) {
                    /*AuthenticationException是SpringSecurity中的抽象异常,是所有认证过程中抛出异常的基类,
                      继承AuthenticationException必须实现该类中的一个构造方法,
                      ValidateCodeException只有继承AuthenticationException类,
                      才能作为AuthenticationFailureHandler接口的onAuthenticationFailure方法的参数,
                      在QIQIHAL-SECURITY-BROWSER\src\main\java\com\qiqihal\config\securityconfig\QiqihalSecurityConfig类的configure方法中,
                      将ValidateCodeFilter过滤器加入到SpringSecurity过滤链中并放在UsernamePasswordAuthenticationFilter过滤器前面时,
                      传入实现AuthenticationFailureHandler接口的认证失败后的自定义处理类,
                      一旦validate方法抛出异常,
                      将ValidateCodeException异常传入实现AuthenticationFailureHandler接口的认证失败后的自定义处理类QiqihalAuthenticationFailureHandler的onAuthenticationFailure方法中,
                      证失败后的自定义处理类,将认证失败信息,
                      和自定义认证页面请求路径,封装成Json数据返回前端,
                      SpringSecurity过滤链就会终止,不会进入,包括SpringSecurity过滤链上的过滤器*/
                    authenticationFailureHandler.onAuthenticationFailure(request, response, e);
                    /*执行完实现AuthenticationFailureHandler接口的认证失败后的自定义处理类后,
                      执行return null终止SpringSecurity过滤链中执行下一个过滤器,
                      避免多个错误用户名错误、密码错误和验证码错误等认证失败后都进入认证失败后的自定义处理类,
                      返回两个结果*/
                    return;
                }
            }
        }
        //继续执行其他过滤器,包括SpringSecurity过滤链上的过滤器
        filterChain.doFilter(request,response);
    }

    private void validate(ServletWebRequest request) throws ServletRequestBindingException {
        //从session中取出验证码
        SmsValidateCode codeInSession = (SmsValidateCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY + "SMS");
        //从request请求中根据表单提交的参数"validateCode"取出的值就是提交的验证码,
        String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),validateCodeParameter);

        if (StringUtils.isBlank(codeInRequest)){
            throw new ValidateCodeException("验证码不能为空");
        }
        if (codeInSession == null){
            throw new ValidateCodeException("验证码不存在");
        }
        if (codeInSession.isExpried()){
            //过期时间expireTime,是在ValidateCodeGenerator类中设置的,验证码已过期,把session中将保存短信验证码和过期时间的SmsValidateCode实体删除
            sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY + "SMS");
            throw new ValidateCodeException("验证码已过期");
        }
        //校验表单提交的验证码和session中取出的验证码
        if (!StringUtils.equals(codeInSession.getValidateCode(),codeInRequest)){
            throw new ValidateCodeException("验证码不匹配");
        }
        //验证码校验完毕,则把session中将保存短信验证码和过期时间的SmsValidateCode实体删除
        sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY + "SMS");
    }
}

改变QIQIHAL-SECURITY-CORE\src\main\java\com\qiqihal\validatecode\ValidateCodeGenerator.java文件,新增smsValidateCodeGenerate方法作用是生成随机短信验证码

package com.qiqihal.validatecode;

import com.qiqihal.validatecode.imagevalidatecode.ImageValidateCode;
import com.qiqihal.validatecode.smsvalidatecode.SmsValidateCode;
import org.springframework.web.context.request.ServletWebRequest;

/*为了使验证码生成逻辑可配置,将生成验证码的方法抽象在接口中*/
public interface ValidateCodeGenerator {

    /*ServletWebRequest封装了HttpServletRequest和HttpServletResponse,
    这样无需每次都传请求和响应两个参数,只传递他们两个的封装ServletWebRequest这一个参数即可,
    想要获取HttpServletRequest可以使用getRequest方法,
    想要获取HttpServletResponse可以使用getResponse方法,

    成验图像证码的方法*/
    public ImageValidateCode imageValidateCodeGenerate(ServletWebRequest request);

    /*生成短信验证码方法*/
    public SmsValidateCode smsValidateCodeGenerate(ServletWebRequest request);
}

修改QIQIHAL-SECURITY-CORE\src\main\java\com\qiqihal\validatecode\AbstractValidateCodeGenerator.java文件,实现新增smsValidateCodeGenerate方法

package com.qiqihal.validatecode;

import com.qiqihal.validatecode.imagevalidatecode.ImageValidateCode;
import com.qiqihal.validatecode.smsvalidatecode.SmsValidateCode;
import org.springframework.web.context.request.ServletWebRequest;

/*实现ValidateCodeGenerator接口,使用抽象类实现接口,
再使用其他的类继承抽象类,这样就可以选择性实现接口中的某一个方法*/
public abstract class AbstractValidateCodeGenerator implements ValidateCodeGenerator{

    @Override
    public ImageValidateCode imageValidateCodeGenerate(ServletWebRequest request) {
        return null;
    }

    @Override
    public SmsValidateCode smsValidateCodeGenerate(ServletWebRequest request){
        return null;
    }
}

SmsValidateCodeGenerator.java文件用来生成随机短信验证码
QIQIHAL-SECURITY-CORE\src\main\java\com\qiqihal\validatecode\smsvalidatecode\SmsValidateCodeGenerator.java文件内容如下
···java
package com.qiqihal.validatecode.smsvalidatecode;

import com.qiqihal.properties.SecurityProperties;
import com.qiqihal.validatecode.AbstractValidateCodeGenerator;
import org.apache.commons.lang.RandomStringUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;

/随机短信验证码/
public class SmsValidateCodeGenerator extends AbstractValidateCodeGenerator {

SecurityProperties securityProperties;

public SecurityProperties getSecurityProperties() {
    return securityProperties;
}

public void setSecurityProperties(SecurityProperties securityProperties) {
    this.securityProperties = securityProperties;
}

@Override
/*ServletWebRequest封装了HttpServletRequest和HttpServletResponse,
这样无需每次都传请求和响应两个参数,只传递他们两个的封装ServletWebRequest这一个参数即可,
想要获取HttpServletRequest可以使用getRequest方法,想要获取HttpServletResponse可以使用getResponse方法*/
public SmsValidateCode smsValidateCodeGenerate(ServletWebRequest request) {
    String code = RandomStringUtils.randomNumeric(ServletRequestUtils.getIntParameter(request.getRequest(),"validateCodelength",securityProperties.getValidateCode().getSmsValidateCode().getValidateCodelength()));
    return new SmsValidateCode(code,ServletRequestUtils.getIntParameter(request.getRequest(),"expireTime",securityProperties.getValidateCode().getSmsValidateCode().getExpireTime()));
}

}

···
在QIQIHAL-SECURITY-CORE\src\main\java\com\qiqihal\validatecode\imagevalidatecode路径下创建sms文件夹,在该文件夹下创建DefaultSmsCodeSender.java、SmsCodeSender.java这两个文件

SmsCodeSender.java文件的作用是抽象发送短信功能方法
QIQIHAL-SECURITY-CORE\src\main\java\com\qiqihal\validatecode\sms\SmsCodeSender.java文件内容如下

package com.qiqihal.validatecode.sms;

public interface SmsCodeSender {

    public void send(String mobile, String code);
}

DefaultSmsCodeSender.java文件的作用是调用短信厂家发送短信模板对手机号进行发送,这里简单模拟一个短信发送功能
QIQIHAL-SECURITY-CORE\src\main\java\com\qiqihal\validatecode\sms\DefaultSmsCodeSender.java文件内容如下

package com.qiqihal.validatecode.sms;

public class DefaultSmsCodeSender implements SmsCodeSender{
    @Override
    public void send(String mobile, String code) {
        System.out.println("向"+mobile+"发送短信验证码"+code);
    }
}

你可能感兴趣的:(SpringOauth初级开发五 自定义认证页面中增加短信验证码认证登录(上))