之前代码都是基于浏览器做的,验证码保存在session中,现在基于APP是没有session我们只需要把保存到session的换成保存到redis

定义一个ValidateCodeRepository,用于验证码的增删改:
core项目com.spring.security.validate.code路径
package com.spring.security.validate.code;
import org.springframework.web.context.request.ServletWebRequest;
/**
* 验证码添加 查询 删除
*/
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 validateCodeType
*/
void remove(ServletWebRequest request, ValidateCodeType validateCodeType);
}
app项目创建实现类RedisValidateCodeRepository:
package com.spring.security.validate.code.impl;
import com.spring.security.validate.code.ValidateCode;
import com.spring.security.validate.code.ValidateCodeException;
import com.spring.security.validate.code.ValidateCodeRepository;
import com.spring.security.validate.code.ValidateCodeType;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import java.util.concurrent.TimeUnit;
@Component
public class RedisValidateCodeRepository implements ValidateCodeRepository {
/**
* redis操作
*/
@Autowired
private RedisTemplate redisTemplate;
//验证码自动清理时间 分钟
private final static Integer TIME_OUT = 30;
/**
* 添加验证码
*
* @param request
* @param code
* @param validateCodeType
*/
@Override
public void save(ServletWebRequest request, ValidateCode code, ValidateCodeType validateCodeType) {
redisTemplate.opsForValue().set(buildKey(request, validateCodeType), code, TIME_OUT, TimeUnit.MINUTES);
}
/**
* 查询验证码
*
* @param request
* @param validateCodeType
* @return
*/
@Override
public ValidateCode get(ServletWebRequest request, ValidateCodeType validateCodeType) {
Object value = redisTemplate.opsForValue().get(buildKey(request, validateCodeType));
if (value == null) {
return null;
} else {
return (ValidateCode) value;
}
}
/**
* 删除验证码
*
* @param request
* @param validateCodeType
*/
@Override
public void remove(ServletWebRequest request, ValidateCodeType validateCodeType) {
redisTemplate.delete(buildKey(request, validateCodeType));
}
/**
* 解析请求头
*
* @param request
* @param validateCodeType
* @return
*/
private String buildKey(ServletWebRequest request, ValidateCodeType validateCodeType) {
String deviceId = request.getHeader("deviceId");
if (StringUtils.isBlank(deviceId)) {
throw new ValidateCodeException("请在请求头中设置deviceId");
}
return "code:" + validateCodeType.toString().toLowerCase() + ":" + deviceId;
}
}
修改验证码处理AbstractValidateCodeProcessor类:
package com.spring.security.validate.code.impl;
import java.util.Map;
import com.spring.security.validate.code.*;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
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;
public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor {
@Autowired
private ValidateCodeRepository validateCodeRepository;
/**
* 操作session的工具类
*/
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
/**
* 收集系统中所有的 {@link ValidateCodeGenerator} 接口的实现。
*/
@Autowired
private Map<String, ValidateCodeGenerator> validateCodeGenerators;
@Override
public void createCode(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.createCode(request);
}
/**
* 保存校验码
*
* @param request
* @param validateCode
*/
private void save(ServletWebRequest request, C validateCode) {
//图片不保存
ValidateCode code = new ValidateCode(validateCode.getCode(), validateCode.getExpireTime());
//sessionStrategy.setAttribute(request, getSessionKey(request), code);
validateCodeRepository.save(request, code, getValidateCodeType(request));
}
/**
* 构建验证码放入session时的key
*
* @param request
* @return
*/
private String getSessionKey(ServletWebRequest request) {
return SESSION_KEY_PREFIX + getValidateCodeType(request).toString().toUpperCase();
}
/**
* 发送校验码,由子类实现
*
* @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(), "CodeProcessor");
return ValidateCodeType.valueOf(type.toUpperCase());
}
@SuppressWarnings("unchecked")
@Override
public void validate(ServletWebRequest request) {
ValidateCodeType codeType = getValidateCodeType(request);
//String sessionKey = getSessionKey(request);
//C codeInSession = (C) sessionStrategy.getAttribute(request, sessionKey);
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.isExpire()) {
//sessionStrategy.removeAttribute(request, sessionKey);
validateCodeRepository.remove(request, codeType);
throw new ValidateCodeException(codeType + "验证码已过期");
}
if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
throw new ValidateCodeException(codeType + "验证码不匹配");
}
//sessionStrategy.removeAttribute(request, sessionKey);
validateCodeRepository.remove(request, codeType);
}
}
修改资源服务器配置把之前验证码习惯的配置拷贝过来:
package com.spring.security;
import com.spring.security.authentication.HkAuthenticationFailureHandler;
import com.spring.security.authentication.HkAuthenticationSuccessHandler;
import com.spring.security.authentication.mobile.SmsCodeAuthenticationSecurityConfig;
import com.spring.security.properties.SecurityConstants;
import com.spring.security.validate.code.ValidateCodeSecurityConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.social.security.SpringSocialConfigurer;
/**
* 资源服务器
*/
@Configuration
@EnableResourceServer
public class HkResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private HkAuthenticationSuccessHandler hkAuthenticationSuccessHandler;
@Autowired
private HkAuthenticationFailureHandler hkAuthenticationFailureHandler;
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
@Autowired
private ValidateCodeSecurityConfig validateCodeSecurityConfig;
@Autowired
private SpringSocialConfigurer hkSocialSecurityConfig;
@Override
public void configure(HttpSecurity http) throws Exception {
// 表单登录
http.formLogin()
.loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
.loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)// 处理表单登录 URL
.successHandler(hkAuthenticationSuccessHandler)// 处理登录成功
.failureHandler(hkAuthenticationFailureHandler);// 处理登录失败
//配置
http.apply(validateCodeSecurityConfig)
.and()
//加入短信验证码认证流程
.apply(smsCodeAuthenticationSecurityConfig)
.and()
//加入社交登录
.apply(hkSocialSecurityConfig)
.and()
.authorizeRequests() // 授权配置
//不需要认证的路径
.antMatchers(SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*",
SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE).permitAll()
.anyRequest() // 所有请求
.authenticated() // 都需要认证
.and().csrf().disable();
}
}
启动系统,使用postman发送验证码:


点击发送后,控制台输出:

接着用这个验证码去换取令牌,使用postman发送如下请求:


点击发送后,结果输出:
{
"access_token": "c1ba7219-9ba5-48d3-b437-756101d4ca62",
"token_type": "bearer",
"refresh_token": "bb25bf8c-9a26-49bd-b313-a22b761b4c6e",
"expires_in": 43137
}
来源:oschina
链接:https://my.oschina.net/u/1046143/blog/3198089
