咱们以前的用户名/密码的方式已经能够正常返回oauth协议的 访问令牌,也就是access_token.给咱们前端app,前端app拿着这个token就能够访问咱们的服务了,接下来咱们分析另外两种认证方式。前端
2.App
App端,咱们服务器是没有session的,这种状况下会出现什么问题呢? web
咱们先获取短信验证码:redis
http://127.0.0.1:8088/code/sms?mobile=13012345678
获取到短信验证码后登陆:spring
http://127.0.0.1:8088/authentication/mobile 参数是: mobile:13226595347 smsCode:875653
注意咱们此时不能使用Rest Client去发送短信登陆请求,由于Rest Client基于浏览器,而且把cookie携带到后端,可是咱们可使用其组织请求参数,而后在dos命令行请求。
组织参数: 数据库
拷贝参数: 后端
在dos非浏览器窗口请求:(其在app端发送代码是一致的,没有cookie) 浏览器
一样的数据,咱们使用web去发送就会返回200: 服务器
咱们使用短信验证码去登陆时候,咱们携带一个参数:deviceId;而后咱们会将此参数存储在数据库或者redis里面。而后校验时候也是带着这个deviceId,此时咱们去从数据库查询。 cookie
spring-security-app模块时候:
咱们在验证码存储,验证码校验和验证码移除都是从数据库存储中进行操做。而不是直接操做session。 session
spring-security-web模块时候:
咱们在验证码存储,验证码校验和验证码移除都是对Session进行操做。
1.定义一个操做验证码的接口
在Spring-Security-Core工程的包:com.yxm.security.core.validate.code下面建立一个接口。
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); }
2.spring-security-web定义使用session存储方式实现ValidateCodeRepository接口的类:
@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); } @Override public ValidateCode get(ServletWebRequest request, ValidateCodeType validateCodeType) { return (ValidateCode)sessionStrategy.getAttribute(request,getSessionKey(request,validateCodeType)); } @Override public void remove(ServletWebRequest request, ValidateCodeType validateCodeType) { sessionStrategy.removeAttribute(request,getSessionKey(request,validateCodeType)); } /** * 构建验证码放入session时的key * @param request * @return */ private String getSessionKey(ServletWebRequest request, ValidateCodeType validateCodeType) { return SESSION_KEY_PREFIX + validateCodeType.toString().toUpperCase(); } }
3.spring-security-app定义使用redis存储方式实现ValidateCodeRepository接口的类:
@Component public class RedisValidateCodeRepository implements ValidateCodeRepository { @Autowired private RedisTemplate<Object, Object> redisTemplate; @Override public void save(ServletWebRequest request, ValidateCode code, ValidateCodeType type) { redisTemplate.opsForValue().set(buildKey(request, type), code, 30, TimeUnit.MINUTES); } @Override public ValidateCode get(ServletWebRequest request, ValidateCodeType type) { Object value = redisTemplate.opsForValue().get(buildKey(request, type)); if (value == null) { return null; } return (ValidateCode) value; } @Override public void remove(ServletWebRequest request, ValidateCodeType type) { redisTemplate.delete(buildKey(request, type)); } /** * @param request * @param type * @return */ private String buildKey(ServletWebRequest request, ValidateCodeType type) { String deviceId = request.getHeader("deviceId"); if (StringUtils.isBlank(deviceId)) { throw new ValidateCodeException("请在请求头中携带deviceId参数"); } return "code:" + type.toString().toLowerCase() + ":" + deviceId; } }
当咱们的spring-security-demo项目依赖spring-security-app的时候就会走RedisValidateCodeRepository,当咱们的spring-security-demo项目依赖spring-security-web项目时候就会走SessionValidateCodeRepository实现。
public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor { /** * 操做session的工具类 */ private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @Autowired private ValidateCodeRepository validateCodeRepository; /** * 收集系统中全部的 {@link ValidateCodeGenerator} 接口的实现。 */ @Autowired private Map<String, ValidateCodeGenerator> validateCodeGenerators; @Override public void create(ServletWebRequest request) throws Exception { //主干逻辑3步骤:1.生成 2.保存 3.发送(发送是抽象方法,让子类本身去实现) C validateCode = generate(request); save(request, validateCode); send(request, validateCode); } /** * 生成校验码:这里涉及到Spring里面的开发技巧;依赖查找 * * @param request * @return */ @SuppressWarnings("unchecked") private C generate(ServletWebRequest request) { /*String type = getProcessorType(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)); } /** * 构建验证码放入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 processorType = getValidateCodeType(request); String sessionKey = getSessionKey(request); //C codeInSession = (C) sessionStrategy.getAttribute(request, sessionKey); ValidateCode validateCode = validateCodeRepository.get(request, getValidateCodeType(request)); String codeInRequest; try { codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), processorType.getParamNameOnValidate()); } catch (ServletRequestBindingException e) { throw new ValidateCodeException("获取验证码的值失败"); } if (StringUtils.isBlank(codeInRequest)) { throw new ValidateCodeException(processorType + "验证码的值不能为空"); } if (validateCode == null) { throw new ValidateCodeException(processorType + "验证码不存在"); } if (validateCode.isExpried()) { sessionStrategy.removeAttribute(request, sessionKey); throw new ValidateCodeException(processorType + "验证码已过时"); } if (!StringUtils.equals(validateCode.getCode(), codeInRequest)) { throw new ValidateCodeException(processorType + "验证码不匹配"); } sessionStrategy.removeAttribute(request, sessionKey); } }
咱们在请求头里面添加字段:deviceId
查看redis中存储:
登陆校验: