短信密码登录重构
- 抽取重复的代码
- 将一些变量用常量或枚举类管理起来
- 对一些流程进行抽象封装,方便扩展
比如前面我们的短信和验证码过滤器流程很相似,还有系统置不够清晰糅杂在一起了等。
关于用户名密码登录和短信登录表单提交的url地址,不需要真实存在,
因为这个是提供这两个特定过滤器框架特定的拦截点。只有提交到指定的拦截点,
才会进入认证功能服务。
重构
验证码过滤器重构
CaptchaUnionFilter 将短信和图形验证码的过滤器合并成一个 ,将原来的2个过滤器删除
-
package com.rui.tiger.auth.core.captcha;
-
-
import com.rui.tiger.auth.core.authentication.TigerAuthenticationFailureHandler;
-
import com.rui.tiger.auth.core.properties.SecurityConstants;
-
import com.rui.tiger.auth.core.properties.SecurityProperties;
-
import lombok.extern.slf4j.Slf4j;
-
import org.apache.commons.lang.StringUtils;
-
import org.springframework.beans.factory.InitializingBean;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.stereotype.Component;
-
import org.springframework.util.AntPathMatcher;
-
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;
-
import java.util.HashMap;
-
import java.util.Map;
-
import java.util.Set;
-
-
/**
-
* 重构的验证码拦截器 (图片验证+短信验证)
-
*
-
* @author CaiRui
-
* @date 2018-12-25 8:37
-
*/
-
@Component(
"captchaUnionFilter")
-
@Slf4j
-
public
class CaptchaUnionFilter extends OncePerRequestFilter implements InitializingBean {
-
@Autowired
-
private SecurityProperties securityProperties;
-
@Autowired
-
private TigerAuthenticationFailureHandler tigerAuthenticationFailureHandler;
-
@Autowired
-
private CaptchaProcessorHolder captchaProcessorHolder;
-
-
/**
-
* 存放所有需要校验验证码的url
-
* key: 验证码类型
-
* value: 验证路径
-
*/
-
private Map<String, CaptchaTypeEnum> urlMap =
new HashMap<>();
-
-
/**
-
* 验证请求url与配置的url是否匹配的工具类
-
*/
-
private AntPathMatcher pathMatcher =
new AntPathMatcher();
-
-
/**
-
* bean初始化后调用
-
*
-
* @throws ServletException
-
*/
-
-
@Override
-
public void afterPropertiesSet() throws ServletException {
-
super.afterPropertiesSet();
-
//短信验证码
-
urlMap.put(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, CaptchaTypeEnum.SMS);
-
addVaildateUrlToUrlMap(securityProperties.getCaptcha().getSms().getInterceptUrl(), CaptchaTypeEnum.SMS);
-
//图片验证码
-
urlMap.put(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM, CaptchaTypeEnum.IMAGE);
-
addVaildateUrlToUrlMap(securityProperties.getCaptcha().getImage().getInterceptImageUrl(), CaptchaTypeEnum.IMAGE);
-
}
-
-
/**
-
* 验证码拦截核心逻辑
-
*
-
* @param request
-
* @param response
-
* @param filterChain
-
* @throws ServletException
-
* @throws IOException
-
*/
-
@Override
-
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
-
CaptchaTypeEnum captchaTypeEnum = getCaptchaTypeWithRequestUrl(request);
-
if (captchaTypeEnum !=
null) {
-
try {
-
log.info(
"校验请求【" + request.getRequestURI() +
"】" + captchaTypeEnum.getDesc() +
"验证码");
-
captchaProcessorHolder.findCaptchaProcessor(captchaTypeEnum)
-
.validate(
new ServletWebRequest(request, response),captchaTypeEnum);
-
}
catch (CaptchaException captchaException) {
-
log.info(
"验证码校验异常", captchaException.getMessage());
-
tigerAuthenticationFailureHandler.onAuthenticationFailure(request, response, captchaException);
-
return;
-
}
-
//filterChain.doFilter(request, response); 就是null后续都不执行 被坑了很久
-
}
-
filterChain.doFilter(request, response);
-
}
-
-
/**
-
* 根据请求路径返回验证码类型
-
*
-
* @param request
-
* @return
-
*/
-
private CaptchaTypeEnum getCaptchaTypeWithRequestUrl(HttpServletRequest request) {
-
String requestUrl = request.getRequestURI();
//返回除去host(域名或者ip)部分的路径
-
if (!StringUtils.equalsIgnoreCase(
"get", request.getMethod())) {
-
Set<String> urlSet = urlMap.keySet();
-
for (String url : urlSet) {
-
if (pathMatcher.match(url, requestUrl)) {
-
return urlMap.get(url);
-
}
-
}
-
}
-
return
null;
-
}
-
-
/**
-
* 不同类型拦截路径赋值
-
*
-
* @param interceptUrl
-
* @param captchaTypeEnum
-
*/
-
private void addVaildateUrlToUrlMap(String interceptUrl, CaptchaTypeEnum captchaTypeEnum) {
-
if (StringUtils.isNotBlank(interceptUrl)) {
-
String[] interceptUrlArray = StringUtils.split(interceptUrl,
",");
-
for (String url : interceptUrlArray) {
-
urlMap.put(url, captchaTypeEnum);
-
}
-
}
-
-
}
-
-
}
CaptchaProcessorHolder 验证码处理器持有者 根据枚举类型查找验证码处理器
-
package com.rui.tiger.auth.core.captcha;
-
-
import com.rui.tiger.auth.core.support.strategy.StrategyContainerImpl;
-
import lombok.extern.slf4j.Slf4j;
-
import org.springframework.stereotype.Component;
-
-
/**
-
* CaptchaProcessor接口 持有者
-
*
-
* @author CaiRui
-
* @date 2018-12-25 9:02
-
*/
-
-
@Component
-
@Slf4j
-
public
class CaptchaProcessorHolder {
-
-
/**
-
* 获取CaptchaProcessor接口实现类
-
*
-
* @param name
-
* @return
-
*/
-
CaptchaProcessor findCaptchaProcessor(String name) {
-
CaptchaTypeEnum captchaTypeEnum = CaptchaTypeEnum.forCode(name);
-
if (captchaTypeEnum ==
null) {
-
log.error(
"验证码类型枚举" + name +
"不存在");
-
throw
new CaptchaException(
"验证码类型枚举类" + name +
"不存在");
-
}
-
return findCaptchaProcessor(captchaTypeEnum);
-
}
-
-
/**
-
* 获取CaptchaProcessor 接口实现类
-
*
-
* @param captchaTypeEnum
-
* @return
-
*/
-
CaptchaProcessor findCaptchaProcessor(CaptchaTypeEnum captchaTypeEnum) {
-
if (captchaTypeEnum ==
null) {
-
throw
new CaptchaException(
"验证码类型枚举类不存在");
-
}
-
CaptchaProcessor captchaProcessor = StrategyContainerImpl.getStrategy(CaptchaProcessor.class, captchaTypeEnum);
-
if (captchaProcessor ==
null) {
-
log.error(
"{}处理器不存在", captchaTypeEnum.getDesc());
-
throw
new CaptchaException(captchaTypeEnum.getDesc() +
"处理器不存在");
-
}
-
log.info(
"{}处理器获取", captchaTypeEnum.getDesc());
-
-
return captchaProcessor;
-
}
-
-
}
CaptchaProcessor 验证码处理器接口 新增校验逻辑
-
package com.rui.tiger.auth.core.captcha;
-
-
import com.rui.tiger.auth.core.support.strategy.IStrategy;
-
import org.springframework.web.context.request.ServletWebRequest;
-
-
/**
-
* 验证码处理器接口
-
* @author CaiRui
-
* @Date 2018/12/15 17:53
-
*/
-
public
interface CaptchaProcessor extends IStrategy<CaptchaTypeEnum> {
-
/**
-
* 验证码缓存KEY值前缀
-
*/
-
String CAPTCHA_SESSION_KEY=
"captcha_session_key_";
-
/**
-
* 创建验证码
-
* @param request 封装请求和响应
-
* @throws Exception
-
*/
-
void create(ServletWebRequest request) throws Exception;
-
-
/**
-
* 校验验证码
-
* @param servletWebRequest
-
* @param captchaTypeEnum
-
*/
-
void validate(ServletWebRequest servletWebRequest, CaptchaTypeEnum captchaTypeEnum) throws CaptchaException ;
-
}
AbstractCaptchaProcessor 将验证逻辑放到抽象父类中进行统一验证
-
package com.rui.tiger.auth.core.captcha;
-
-
import org.apache.commons.lang.StringUtils;
-
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 java.io.IOException;
-
-
/**
-
* 验证码处理器抽象父类
-
*
-
* @author CaiRui
-
* @Date 2018/12/15 18:21
-
*/
-
public
abstract
class AbstractCaptchaProcessor<C extends CaptchaVo> implements CaptchaProcessor {
-
-
private SessionStrategy sessionStrategy =
new HttpSessionSessionStrategy();
-
-
/**
-
* 创建验证码
-
*
-
* @param request 封装请求和响应
-
* @throws Exception
-
*/
-
@Override
-
public void create(ServletWebRequest request) throws Exception {
-
//生成
-
C captcha = generateCaptcha(request);
-
//保存
-
save(request, captcha);
-
//发送
-
send(request, captcha);
-
}
-
-
/**
-
* 短信和手机验证码的通用验证
-
*
-
* @param request
-
* @param captchaType 验证码
-
*/
-
@Override
-
public void validate(ServletWebRequest request, CaptchaTypeEnum captchaType) throws CaptchaException {
-
-
String sessionKey = getSessionKey(captchaType);
-
C captchaInSession = (C) sessionStrategy.getAttribute(request, sessionKey);
-
-
String captchaInRequest;
-
try {
-
captchaInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),
-
captchaType.getParamNameOnValidate());
-
}
catch (ServletRequestBindingException e) {
-
throw
new CaptchaException(
"获取验证码的值失败");
-
}
-
-
if (StringUtils.isBlank(captchaInRequest)) {
-
throw
new CaptchaException(captchaType +
"验证码的值不能为空");
-
}
-
-
if (captchaInSession ==
null) {
-
throw
new CaptchaException(captchaType +
"验证码不存在");
-
}
-
-
if (captchaInSession.isExpried()) {
-
sessionStrategy.removeAttribute(request, sessionKey);
-
throw
new CaptchaException(captchaType +
"验证码已过期");
-
}
-
-
if (!StringUtils.equals(captchaInSession.getCode(), captchaInRequest)) {
-
throw
new CaptchaException(captchaType +
"验证码不匹配");
-
}
-
//验证成功清除缓存中的key
-
sessionStrategy.removeAttribute(request, sessionKey);
-
}
-
-
/**
-
* 生成验证码
-
* @param request
-
* @return
-
*/
-
protected abstract C generateCaptcha(ServletWebRequest request);
-
-
/**
-
* 发送验证码
-
* @param request
-
* @param captcha
-
*/
-
protected abstract void send(ServletWebRequest request, C captcha) throws IOException, ServletRequestBindingException;
-
-
/**
-
* 保存验证码到session中
-
* @param request
-
* @param captcha
-
*/
-
private void save(ServletWebRequest request, C captcha) {
-
sessionStrategy.setAttribute(request, CAPTCHA_SESSION_KEY +getCondition().getCode(), captcha);
-
}
-
-
-
/**
-
* 获取验证码session key值
-
*
-
* @param captchaType
-
* @return
-
*/
-
private String getSessionKey(CaptchaTypeEnum captchaType) {
-
return CAPTCHA_SESSION_KEY + captchaType.getCode();
-
}
-
-
}
配置重构
AbstractChannelSecurityConfig 密码登录的抽取到core项目中 这个是浏览器和app项目共同的配置项
-
package com.rui.tiger.auth.core.config;
-
-
import com.rui.tiger.auth.core.properties.SecurityConstants;
-
import org.springframework.beans.factory.annotation.Autowired;
-
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.AuthenticationSuccessHandler;
-
-
/**
-
* 密码登录的通用安全配置
-
* @author CaiRui
-
* @date 2018-12-26 18:11
-
*/
-
public
class AbstractChannelSecurityConfig extends WebSecurityConfigurerAdapter {
-
-
@Autowired
-
private AuthenticationSuccessHandler tigerAuthenticationSuccessHandler;
-
@Autowired
-
private AuthenticationFailureHandler tigerAuthenticationFailureHandler;
-
-
/**
-
* 密码登录配置
-
* @param http
-
* @throws Exception
-
*/
-
protected void applyPasswordAuthenticationConfig(HttpSecurity http) throws Exception {
-
http.formLogin()
-
.loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
-
.loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
//
-
.successHandler(tigerAuthenticationSuccessHandler)
-
.failureHandler(tigerAuthenticationFailureHandler);
-
}
-
-
-
}
CaptchaSecurityConfig 验证码过滤器配置
-
package com.rui.tiger.auth.core.config;
-
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
-
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-
import org.springframework.security.web.DefaultSecurityFilterChain;
-
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-
import org.springframework.stereotype.Component;
-
-
import javax.servlet.Filter;
-
-
/**
-
* 验证码过滤器配置
-
* @author CaiRui
-
* @date 2018-12-26 18:22
-
*/
-
@Component(
"captchaSecurityConfig")
-
public
class CaptchaSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
-
-
/**
-
* 重构的验证码拦截器 (图片验证+短信验证)
-
*/
-
@Autowired
-
private Filter captchaUnionFilter;
-
-
/**
-
* 验证码验证放在密码登录之前
-
* @param http
-
* @throws Exception
-
*/
-
@Override
-
public void configure(HttpSecurity http) throws Exception {
-
http.addFilterBefore(captchaUnionFilter, UsernamePasswordAuthenticationFilter.class);
-
}
-
}
BrowserSecurityConfig 浏览器配置只保留自己特有的如记住我等
-
package com.rui.tiger.auth.browser.config;
-
-
import com.rui.tiger.auth.core.config.AbstractChannelSecurityConfig;
-
import com.rui.tiger.auth.core.config.CaptchaSecurityConfig;
-
import com.rui.tiger.auth.core.config.SmsAuthenticationSecurityConfig;
-
import com.rui.tiger.auth.core.properties.SecurityConstants;
-
import com.rui.tiger.auth.core.properties.SecurityProperties;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.context.annotation.Bean;
-
import org.springframework.context.annotation.Configuration;
-
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-
import org.springframework.security.core.userdetails.UserDetailsService;
-
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-
import org.springframework.security.crypto.password.PasswordEncoder;
-
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
-
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
-
-
import javax.sql.DataSource;
-
-
/**
-
* 浏览器security配置类
-
*
-
* @author CaiRui
-
* @date 2018-12-4 8:41
-
*/
-
@Configuration
-
public
class BrowserSecurityConfig extends AbstractChannelSecurityConfig {
-
-
@Autowired
-
private SecurityProperties securityProperties;
-
@Autowired
-
private DataSource dataSource;
-
@Autowired
-
private UserDetailsService userDetailsService;
-
@Autowired
-
private SmsAuthenticationSecurityConfig smsAuthenticationSecurityConfig;
//短信登陆配置
-
@Autowired
-
private CaptchaSecurityConfig captchaSecurityConfig;
//验证码配置
-
-
/**
-
* 密码加密解密
-
*
-
* @return
-
*/
-
@Bean
-
public PasswordEncoder passwordEncoder() {
-
return
new BCryptPasswordEncoder();
-
}
-
-
/**
-
* 记住我持久化数据源
-
* JdbcTokenRepositoryImpl CREATE_TABLE_SQL 建表语句可以先在数据库中执行
-
*
-
* @return
-
*/
-
@Bean
-
public PersistentTokenRepository persistentTokenRepository() {
-
JdbcTokenRepositoryImpl jdbcTokenRepository =
new JdbcTokenRepositoryImpl();
-
jdbcTokenRepository.setDataSource(dataSource);
-
//第一次会执行CREATE_TABLE_SQL建表语句 后续会报错 可以关掉
-
//jdbcTokenRepository.setCreateTableOnStartup(true);
-
return jdbcTokenRepository;
-
}
-
-
/**
-
* 核心配置
-
* @param http
-
* @throws Exception
-
*/
-
@Override
-
protected void configure(HttpSecurity http) throws Exception {
-
/**
-
* 表单密码配置
-
*/
-
applyPasswordAuthenticationConfig(http);
-
-
http
-
.apply(captchaSecurityConfig)
-
.and()
-
.apply(smsAuthenticationSecurityConfig)
-
.and()
-
.rememberMe()
-
.tokenRepository(persistentTokenRepository())
-
.tokenValiditySeconds(securityProperties.getBrowser().getRemberMeSeconds())
-
.userDetailsService(userDetailsService)
-
.and()
-
.authorizeRequests()
-
.antMatchers(
-
SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
//权限认证
-
SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
//手机
-
securityProperties.getBrowser().getLoginPage(),
//登录页面
-
SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+
"/*")
// /captcha/* 验证码放行
-
.permitAll()
-
.anyRequest()
-
.authenticated()
-
.and()
-
.csrf().disable();
-
-
}
-
-
}
常量字典
SecurityConstants 权限常量配置类
-
package com.rui.tiger.auth.core.properties;
-
-
/**
-
* 权限常量配置类
-
* @author CaiRui
-
* @date 2018-12-25 8:52
-
*/
-
public
class SecurityConstants {
-
-
/**
-
* 默认的处理验证码的url前缀
-
*/
-
public
static
final String DEFAULT_VALIDATE_CODE_URL_PREFIX =
"/captcha";
-
/**
-
* 当请求需要身份认证时,默认跳转的url
-
*
-
*/
-
public
static
final String DEFAULT_UNAUTHENTICATION_URL =
"/authentication/require";
-
/**
-
* 默认的用户名密码登录请求处理url
-
*/
-
public
static
final String DEFAULT_LOGIN_PROCESSING_URL_FORM =
"/authentication/form";
-
/**
-
* 默认的手机验证码登录请求处理url
-
*/
-
public
static
final String DEFAULT_LOGIN_PROCESSING_URL_MOBILE =
"/authentication/mobile";
-
/**
-
* 默认登录页面
-
*
-
*/
-
public
static
final String DEFAULT_LOGIN_PAGE_URL =
"/tiger-login.html";
-
/**
-
* 验证图片验证码时,http请求中默认的携带图片验证码信息的参数的名称
-
*/
-
public
static
final String DEFAULT_PARAMETER_NAME_CODE_IMAGE =
"imageCode";
-
/**
-
* 验证短信验证码时,http请求中默认的携带短信验证码信息的参数的名称
-
*/
-
public
static
final String DEFAULT_PARAMETER_NAME_CODE_SMS =
"smsCode";
-
/**
-
* 发送短信验证码 或 验证短信验证码时,传递手机号的参数的名称
-
*/
-
public
static
final String DEFAULT_PARAMETER_NAME_MOBILE =
"mobile";
-
-
-
}
ok 重构核心代码完成 我们来看下测试结果,首先打开登录界面,在没有发送验证码前先随便填个短信验证码试试
输入短信验证码看看
ok 说明我们的配置通过了,其它请自行测试。
总结:
文章转载至:https://blog.csdn.net/ahcr1026212/article/details/85244082