Spring Boot整合Security系列步骤及问题排查(十)—— 短信验证码及代码优化

自定义Filter及Token等工具类准备:

/**
 * 仿写UsernamePasswordAuthenticationFilter
 *
 * @author zhaohaibin
 */
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public static final String DEMO_FORM_MOBILE_KEY = SecurityConstants.DEFAULT_PARAMETER_NAME_MOBILE;
    private String mobileParameter = DEMO_FORM_MOBILE_KEY;
    /**
     * 只处理post请求
     */
    private boolean postOnly = true;

    public SmsCodeAuthenticationFilter() {
        super(new AntPathRequestMatcher("/authentication/mobile", "POST"));
    }

    /**
     * 认证流程
     *
     * @param request
     * @param response
     * @return
     * @throws AuthenticationException
     */
    @Override
    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 authRequest = new SmsCodeAuthenticationToken(mobile);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

    /**
     * 获取手机号
     *
     * @param request
     * @return
     */
    @Nullable
    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 usernameParameter) {
        Assert.hasText(usernameParameter, "Mobile parameter must not be empty or null");
        this.mobileParameter = usernameParameter;
    }

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

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

}
/**
 * 仿写UsernamePasswordAuthenticationToken
 *
 * @author zhaohaibin
 */
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
    private final Object principal;

    public SmsCodeAuthenticationToken(String mobile) {
        super((Collection) null);
        this.principal = mobile;
        this.setAuthenticated(false);
    }

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

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

}
/**
 * @author zhaohaibin
 */
@Data
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {

    private UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;
        UserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());

        if (null == user) {

            throw new InternalAuthenticationServiceException("无法获取用户信息");

        }

        SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities());

        authenticationResult.setDetails(authenticationToken.getDetails());

        return authenticationResult;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return SmsCodeAuthenticationToken.class.isAssignableFrom(aClass);
    }
}
/**
 * 短信验证码认证配置
 *
 * @author zhaohaibin
 */
@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    @Autowired
    private AuthenticationSuccessHandler demoAuthenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler demoAuthenticationFailureHandler;

    @Autowired
    private ISystemUserService userDetailsService;

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {

        SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
        smsCodeAuthenticationFilter.setAuthenticationManager(httpSecurity.getSharedObject(AuthenticationManager.class));
        smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(demoAuthenticationSuccessHandler);
        smsCodeAuthenticationFilter.setAuthenticationFailureHandler(demoAuthenticationFailureHandler);

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

        httpSecurity.authenticationProvider(smsCodeAuthenticationProvider)
                .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

    }

}

1.新建短信验证码属性配置类:

/**
 * 短信验证码属性配置
 *
 * @author zhaohaibin
 */
@Data
public class SmsCodeProperties {

    private int length = 6;
    /**
     * 有效期
     */
    private int expireIn = 60;

    /**
     * 需要图片验证码的url
     */
    private String url;

}

2.更新图片验证码属性配置类ImageCodeProperties:

/**
 * 图片验证码属性配置
 *
 * @author zhaohaibin
 */
@Data
public class ImageCodeProperties extends SmsCodeProperties{

    public ImageCodeProperties(){
        setLength(4);
    }

    private int width = 67;
    private int height = 23;

}

3.新建短信验证码生成逻辑:

/**
 * 验证码生成逻辑实现类-短信验证码
 *
 * @author zhaohaibin
 */
@Data
@Component("smsValidateCodeGenerator")
public class SmsValidateCodeGenerator implements ValidateCodeGenerator {

    @Autowired
    private SecurityProperties securityProperties;

    @Override
    public ValidateCode generate(ServletWebRequest request) {
        String code = RandomStringUtils.randomNumeric(securityProperties.getCode().getSms().getLength());
        return new ValidateCode(code, securityProperties.getCode().getSms().getExpireIn());

    }


}

4.新建短信验证码发送接口及默认实现,更新ValidateCodeBeanConfig:

/**
 * 短信验证码发送接口
 *
 * @author zhaohaibin
 */
public interface SmsCodeSender {

    /**
     * 发送抽象方法
     * @param mobile
     * @param code
     */
    void send(String mobile, String code);
}
/**
 * 短信验证码发送默认实现
 *
 * @author zhaohaibin
 */
@Slf4j
public class DefaultSmsCodeSender implements SmsCodeSender {
    @Override
    public void send(String mobile, String code) {

        log.info("向手机【" + mobile + "】发送验证:" + code);

    }
}
/**
 * 生成器实现方式可配置
 * 效果同@Component,但是可以配置@ConditionalOnMissingBean,这样会优先寻找匹配name的生成实现方法
 *
 * @author zhaohaibin
 */
@Configuration
public class ValidateCodeBeanConfig {

    @Autowired
    private SecurityProperties securityProperties;

    /**
     * 图片验证码默认生成器
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(name = "imageValidateCodeGenerator")
    public ValidateCodeGenerator imageValidateCodeGenerator() {
        ImageValidateCodeGenerator codeGenerator = new ImageValidateCodeGenerator();
        codeGenerator.setSecurityProperties(securityProperties);
        return codeGenerator;
    }

    /**
     * 短信验证码默认发送实现
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(SmsCodeSender.class)
    public SmsCodeSender smsCodeSender() {
        return new DefaultSmsCodeSender();
    }

}

5.新建短信验证码处理类SmsValidateCodeProcessor及调用控制器ValidateCodeController更新:

/**
 * 短信验证码处理器
 *
 * @author zhaohaibin
 */
@Component("smsValidateCodeProcessor")
public class SmsValidateCodeProcessor extends AbstractValidateCodeProcessor<ValidateCode> {

    /**
     * 短信验证码发送器
     */
    @Autowired
    private SmsCodeSender smsCodeSender;

    @Override
    protected void send(ServletWebRequest request, ValidateCode smsCode) throws Exception {
        // 短信发送操作
        String mobile = ServletRequestUtils.getRequiredStringParameter(request.getRequest(), SecurityConstants.DEFAULT_PARAMETER_NAME_MOBILE);

        smsCodeSender.send(mobile, smsCode.getCode());
    }
}
/**
 * 验证码控制器
 *
 * @author zhaohaibin
 */
@RestController
public class ValidateCodeController {

    @Autowired
    private Map<String, ValidateCodeProcessor> validateCodeProcessors;

    /**
     * 验证码处理逻辑,根据类型不通,调用不同的{@link ValidateCodeProcessor}接口实现
     *
     * @param request
     * @param response
     * @throws IOException
     */
    @GetMapping(SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX + "/{type}")
    public void createCode(HttpServletRequest request, HttpServletResponse response, @PathVariable String type) throws Exception {

        // 获取处理器对应实现的处理方法
        validateCodeProcessors.get(type + "ValidateCodeProcessor").create(new ServletWebRequest(request, response));

    }

}

6.新建验证码处理器抽象类AbstractValidateCodeProcessor:

/**
 * 抽象验证码处理器
 *
 * @author zhailiang
 */
public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor {

    /**
     * 收集系统中所有的 {@link ValidateCodeGenerator} 接口的实现,以name为key放入Map
     */
    @Autowired
    private Map<String, ValidateCodeGenerator> validateCodeGenerators;

    @Autowired
    private ValidateCodeRepository validateCodeRepository;

    /**
     * 抽象创建逻辑
     *
     * @param request
     * @throws Exception
     */
    @Override
    public void create(ServletWebRequest request) throws Exception {
        C validateCode = generate(request);
        save(request, validateCode);
        send(request, validateCode);
    }

    /**
     * 生成校验码
     *
     * @param request
     * @return
     */
    @SuppressWarnings("unchecked")
    private C generate(ServletWebRequest request) {
        String type = getValidateCodeType(request).toString().toLowerCase();
        String generatorName = type + ValidateCodeGenerator.class.getSimpleName();
        ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(generatorName);
        if (validateCodeGenerator == null) {
            throw new ValidateCodeException("验证码生成器" + generatorName + "不存在");
        }
        return (C) validateCodeGenerator.generate(request);
    }

    /**
     * 保存校验码
     *
     * @param request
     * @param validateCode
     */
    private void save(ServletWebRequest request, C validateCode) {
        ValidateCode code = new ValidateCode(validateCode.getCode(), validateCode.getExpireTime());
        validateCodeRepository.save(request, code, getValidateCodeType(request));
    }

    /**
     * 发送校验码,由子类实现
     *
     * @param request
     * @param validateCode
     * @throws Exception
     */
    protected abstract void send(ServletWebRequest request, C validateCode) throws Exception;

    /**
     * 根据请求的url获取校验码的类型
     *
     * @param request
     * @return
     */
    private ValidateCodeType getValidateCodeType(ServletWebRequest request) {
        String type = StringUtils.substringBefore(getClass().getSimpleName(), "ValidateCodeProcessor");
        return ValidateCodeType.valueOf(type.toUpperCase());
    }

    @Override
    public void validate(ServletWebRequest request) {

        ValidateCodeType codeType = getValidateCodeType(request);

        C codeInSession = (C) validateCodeRepository.get(request, codeType);

        String codeInRequest;
        try {
            codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),
                    codeType.getParamNameOnValidate());
        } catch (ServletRequestBindingException e) {
            throw new ValidateCodeException("获取验证码的值失败");
        }

        if (StringUtils.isBlank(codeInRequest)) {
            throw new ValidateCodeException(codeType + "验证码的值不能为空");
        }

        if (codeInSession == null) {
            throw new ValidateCodeException(codeType + "验证码不存在");
        }

        if (codeInSession.isExpried()) {
            validateCodeRepository.remove(request, codeType);
            throw new ValidateCodeException(codeType + "验证码已过期");
        }

        if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
            throw new ValidateCodeException(codeType + "验证码不匹配");
        }

        validateCodeRepository.remove(request, codeType);

    }

}

7.更新验证码生成器接口ValidateCodeGenerator:

/**
 * 验证码生成逻辑抽象类
 *
 * @author zhaohaibin
 */
public interface ValidateCodeGenerator {

    /**
     * 验证码生成抽象方法
     *
     * @param request
     * @return
     */
    ValidateCode generate(ServletWebRequest request);

}

8.新建验证码存取接口及Session方式实现:

/**
 * 验证码存取接口
 *
 * @author zhaohaibin
 */
public interface ValidateCodeRepository {

    /**
     * 保存验证码
     *
     * @param request
     * @param code
     * @param validateCodeType
     */
    void save(ServletWebRequest request, ValidateCode code, ValidateCodeType validateCodeType);

    /**
     * 获取验证码
     *
     * @param request
     * @param validateCodeType
     * @return
     */
    ValidateCode get(ServletWebRequest request, ValidateCodeType validateCodeType);

    /**
     * 移除验证码
     *
     * @param request
     * @param codeType
     */
    void remove(ServletWebRequest request, ValidateCodeType codeType);

}
/**
 * 基于session的验证码存取器
 *
 * @author zhaohaibin
 */
@Component
public class SessionValidateCodeRepository implements ValidateCodeRepository {

    /**
     * 验证码放入session时的前缀
     */
    String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_";

    /**
     * 操作session的工具类
     */
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    @Override
    public void save(ServletWebRequest request, ValidateCode code, ValidateCodeType validateCodeType) {
        sessionStrategy.setAttribute(request, getSessionKey(request, validateCodeType), code);
    }

    /**
     * 构建验证码放入session时的key
     *
     * @param request
     * @return
     */
    private String getSessionKey(ServletWebRequest request, ValidateCodeType validateCodeType) {
        return SESSION_KEY_PREFIX + validateCodeType.toString().toUpperCase();
    }

    @Override
    public ValidateCode get(ServletWebRequest request, ValidateCodeType validateCodeType) {
        return (ValidateCode) sessionStrategy.getAttribute(request, getSessionKey(request, validateCodeType));
    }

    @Override
    public void remove(ServletWebRequest request, ValidateCodeType codeType) {
        sessionStrategy.removeAttribute(request, getSessionKey(request, codeType));
    }

}

9.新建验证码类型枚举:

/**
 * 验证码类型
 *
 * @author zhaohaibin
 */
public enum ValidateCodeType {

    /**
     * 短信验证码
     */
    SMS {
        @Override
        public String getParamNameOnValidate() {
            return SecurityConstants.DEFAULT_PARAMETER_NAME_CODE_SMS;
        }
    },
    /**
     * 图片验证码
     */
    IMAGE {
        @Override
        public String getParamNameOnValidate() {
            return SecurityConstants.DEFAULT_PARAMETER_NAME_CODE_IMAGE;
        }
    };

    /**
     * 校验时从请求中获取的参数的名字
     *
     * @return
     */
    public abstract String getParamNameOnValidate();

}

10.更新验证码拦截器ValidateCodeFilter:

/**
 * 自定义验证码拦截器
 * InitializingBean覆写afterPropertiesSet方法
 *
 * @author zhaohaibin
 */
@Data
@Slf4j
@Component("validateCodeFilter")
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {

    /**
     * 引入失败处理逻辑
     */
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    /**
     * 系统配置信息
     */
    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private ValidateCodeProcessorHolder validateCodeProcessorHolder;

    /**
     * 存放所有需要校验验证码的url
     */
    private Map<String, ValidateCodeType> urlMap = new HashMap<>();

    /**
     * 缓存获取验证码
     */
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    /**
     * 方便匹配/*等路径
     */
    private AntPathMatcher pathMatcher = new AntPathMatcher();

    @Override
    public void afterPropertiesSet() throws ServletException {

        super.afterPropertiesSet();

        // 图片验证码
        urlMap.put(SecurityConstants.DEFAULT_PROJECT_NAME_URL + SecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_FORM, ValidateCodeType.IMAGE);
        addUrlToMap(securityProperties.getCode().getImage().getUrl(), ValidateCodeType.IMAGE);

        // 短信验证码
        urlMap.put(SecurityConstants.DEFAULT_PROJECT_NAME_URL + SecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_MOBILE, ValidateCodeType.SMS);
        addUrlToMap(securityProperties.getCode().getImage().getUrl(), ValidateCodeType.SMS);

    }

    /**
     * 将系统中配置的需要校验验证码的URL根据校验的类型放入map
     *
     * @param urlString
     * @param type
     */
    protected void addUrlToMap(String urlString, ValidateCodeType type) {

        if (StringUtils.isNotBlank(urlString)) {
            // ","号分隔
            String[] urls = StringUtils.splitByWholeSeparatorPreserveAllTokens(urlString, ",");

            if (null != urls && urls.length > 0) {

                for (String url : urls) {
                    urlMap.put(url, type);
                }

            }
        }
    }

    /**
     * 判断路径是否需要校验并校验
     *
     * @param request
     * @param response
     * @param filterChain
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        ValidateCodeType type = getValidateCodeType(request);

        if (type != null) {

            log.info("校验请求【" + request.getRequestURI() + "】中的验证码,验证码类型:" + type);

            try {

                validateCodeProcessorHolder.findValidateCodeProcessor(type)
                        .validate(new ServletWebRequest(request, response));

                log.info("验证码校验通过");

            } catch (ValidateCodeException exception) {
                authenticationFailureHandler.onAuthenticationFailure(request, response, exception);
                return;
            }

        }

        filterChain.doFilter(request, response);

    }

    /**
     * 获取校验码的类型,如果当前请求不需要校验,则返回null
     *
     * @param request
     * @return
     */
    private ValidateCodeType getValidateCodeType(HttpServletRequest request) {

        ValidateCodeType result = null;

        if (!StringUtils.equalsIgnoreCase(request.getMethod(), "get")) {

            Set<String> urls = urlMap.keySet();

            if (null != urls && urls.size() > 0) {

                for (String url : urls) {
                    if (pathMatcher.match(url, request.getRequestURI())) {
                        result = urlMap.get(url);
                    }
                }

            }

        }
        return result;
    }

}

ValidateCodeProcessorHolder:

/**
 * 校验码处理器管理器
 *
 * @author zhaohaibin
 */
@Component
public class ValidateCodeProcessorHolder {

    @Autowired
    private Map<String, ValidateCodeProcessor> validateCodeProcessors;

    /**
     * @param type
     * @return
     */
    public ValidateCodeProcessor findValidateCodeProcessor(ValidateCodeType type) {
        return findValidateCodeProcessor(type.toString().toLowerCase());
    }

    /**
     * @param type
     * @return
     */
    public ValidateCodeProcessor findValidateCodeProcessor(String type) {
        String name = type.toLowerCase() + ValidateCodeProcessor.class.getSimpleName();
        ValidateCodeProcessor processor = validateCodeProcessors.get(name);
        if (processor == null) {
            throw new ValidateCodeException("验证码处理器" + name + "不存在");
        }
        return processor;
    }

}

11.更新Security拦截配置WebSecurityConfig:

/**
 * SpringSecurity配置
 *
 * @author zhaohaibin
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private SecurityProperties securityProperties;

//    /**
//     * 自定义成功处理逻辑
//     */
//    @Autowired
//    private DemoAuthenticationSuccessHandler demoAuthenticationSuccessHandler;
//    /**
//     * 自定义失败处理逻辑
//     */
//    @Autowired
//    private DemoAuthenticationFailureHandler demoAuthenticationFailureHandler;

    /**
     * 记住我功能引入数据源配置
     */
    @Autowired
    private DataSource dataSource;

    /**
     * 对UserDetailsService的自定义实现
     */
    @Autowired
    private ISystemUserService userDetailsService;

    @Autowired
    private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;

    @Autowired
    private ValidateCodeSecurityConfig validateCodeSecurityConfig;

    @Autowired
    private FormAuthenticationConfig formAuthenticationConfig;

    /**
     * 加密解密官方实现
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 记住我功能token存储配置
     *
     * @return
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        // 启动创建表,创建后记得注释掉
//        tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

//        // 图片验证码相关
//        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
//        // 传入自定义失败处理
//        validateCodeFilter.setAuthenticationFailureHandler(demoAuthenticationFailureHandler);
//        // 传入自定义路径配置
//        validateCodeFilter.setSecurityProperties(securityProperties);
//        // 执行分隔操作
//        validateCodeFilter.afterPropertiesSet();
//
//        // 短信验证码相关
//        SmsCodeFilter smsCodeFilter = new SmsCodeFilter();
//        // 传入自定义失败处理
//        smsCodeFilter.setAuthenticationFailureHandler(demoAuthenticationFailureHandler);
//        // 传入自定义路径配置
//        smsCodeFilter.setSecurityProperties(securityProperties);
//        // 执行分隔操作
//        smsCodeFilter.afterPropertiesSet();

        // 表单登录改为配置引入
        formAuthenticationConfig.configure(http);

        // 默认/表单登录方式
//        http.httpBasic()
        http
//                // 在默认UsernamePasswordAuthenticationFilter前增加自定义验证码校验拦截,并传入自定义失败对象
//                .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class)
//                // 在默认UsernamePasswordAuthenticationFilter前增加自定义验证码校验拦截,并传入自定义失败对象
//                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
//                .formLogin()
//                // 自定义登录页面
//                .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
//                .loginProcessingUrl(SecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_FORM)
//                .successHandler(demoAuthenticationSuccessHandler)
//                .failureHandler(demoAuthenticationFailureHandler)

                // 自定义拦截配置应用
                // 验证码拦截配置
                .apply(validateCodeSecurityConfig)
                .and()
                // 短信验证码认证拦截配置
                .apply(smsCodeAuthenticationSecurityConfig)
                .and()

                // 记住我相关配置
                .rememberMe()
                .tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
                .userDetailsService(userDetailsService)
                .and()
                // 对任何请求授权
                .authorizeRequests()
                // 匹配页面授权所有权限
                .antMatchers(
                        // API
                        "/swagger-ui.html",
                        // 默认登录页
                        SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
                        // 自定义登录页(demoLogin)
                        securityProperties.getBrowser().getLoginPage(),
                        // 验证码
                        SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX + "/*").permitAll()
                // 任何请求
                .anyRequest()
                // 都需要被认证
                .authenticated()
                .and()
                // 请求伪造防护功能关闭
                .csrf().disable();

    }

}

FormAuthenticationConfig:

/**
 * 表单登录配置
 *
 * @author zhaohaibin
 */
@Component
public class FormAuthenticationConfig {

	@Autowired
	protected AuthenticationSuccessHandler demoAuthenticationSuccessHandler;
	
	@Autowired
	protected AuthenticationFailureHandler demoAuthenticationFailureHandler;
	
	public void configure(HttpSecurity http) throws Exception {
		http.formLogin()
			.loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
			.loginProcessingUrl(SecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_FORM)
			.successHandler(demoAuthenticationSuccessHandler)
			.failureHandler(demoAuthenticationFailureHandler);
	}
	
}

SecurityConstants:

/**
 * 拦截器常量配置
 *
 * @author zhaohaibin
 */
public interface SecurityConstants {

    /**
     * 默认的处理验证码的url前缀
     */
    String DEFAULT_VALIDATE_CODE_URL_PREFIX = "/code";
    /**
     * 当请求需要身份认证时,默认跳转的url
     *
     * @see BrowserSecurityController
     */
    String DEFAULT_UNAUTHENTICATION_URL = "/authentication/require";

    String DEFAULT_PROJECT_NAME = "";
//    String DEFAULT_PROJECT_NAME = "demo";
    String DEFAULT_URL_SEPARATOR = "/";
    String DEFAULT_PROJECT_NAME_URL = StringUtils.isNotBlank(DEFAULT_PROJECT_NAME) ? DEFAULT_URL_SEPARATOR + DEFAULT_PROJECT_NAME : "";
    /**
     * 默认的用户名密码登录请求处理url
     */
    String DEFAULT_SIGN_IN_PROCESSING_URL_FORM = "/authentication/form";
    /**
     * 默认的手机验证码登录请求处理url
     */
    String DEFAULT_SIGN_IN_PROCESSING_URL_MOBILE = "/authentication/mobile";
    /**
     * 默认登录页面
     *
     * @see BrowserSecurityController
     */
    String DEFAULT_SIGN_IN_PAGE_URL = DEFAULT_PROJECT_NAME_URL + "/login.html";
    /**
     * 验证图片验证码时,http请求中默认的携带图片验证码信息的参数的名称
     */
    String DEFAULT_PARAMETER_NAME_CODE_IMAGE = "imageCode";
    /**
     * 验证短信验证码时,http请求中默认的携带短信验证码信息的参数的名称
     */
    String DEFAULT_PARAMETER_NAME_CODE_SMS = "smsCode";
    /**
     * 发送短信验证码 或 验证短信验证码时,传递手机号的参数的名称
     */
    String DEFAULT_PARAMETER_NAME_MOBILE = "mobile";
}

12.登录页增加短信验证码表单提交代码,启动测试:

<h2>短信登录</h2>
<form action="authentication/mobile" method="post">
    <div><label>手机号</label><input type="text" name="mobile" placeholder="请输入手机号" value="13012345678"/></div>
    <div><label>验证码</label><input type="text" name="smsCode" placeholder="请输入验证码"/>
        <a href="code/sms?mobile=13012345678">发送验证码</a>
    </div>
    <div><input type="submit" value="登录"/></div>
</form>

问题排查:

测试过程若出现"请引导用户到登录页"提示,且控制台无报错,提示"引发跳转的请求是...",请debug跟踪检查,可能是代码异常失败跳转导致
发布了81 篇原创文章 · 获赞 12 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/u012382791/article/details/105263306
今日推荐