27.SpringSecurity-重构用户名密码登陆

前言

咱们以前的用户名/密码的方式已经能够正常返回oauth协议的 访问令牌,也就是access_token.给咱们前端app,前端app拿着这个token就能够访问咱们的服务了,接下来咱们分析另外两种认证方式。前端

  1. 短信验证码登陆
  2. 第三方社交方式登陆

内容

1 短信验证码登陆

1.1 验证码存储问题

  1. 浏览器
    以前咱们的验证码是放在session里面的,用户点击验证码时候,咱们后台会生成验证码,而后存储到session里面,并返回到浏览器端,而后登陆时候携带此验证码进行校验。

2.App
App端,咱们服务器是没有session的,这种状况下会出现什么问题呢? web

image.png

咱们先获取短信验证码:redis

http://127.0.0.1:8088/code/sms?mobile=13012345678

image.png

获取到短信验证码后登陆:spring

http://127.0.0.1:8088/authentication/mobile  
参数是:  
mobile:13226595347 
smsCode:875653

注意咱们此时不能使用Rest Client去发送短信登陆请求,由于Rest Client基于浏览器,而且把cookie携带到后端,可是咱们可使用其组织请求参数,而后在dos命令行请求。
组织参数:
image.png数据库

拷贝参数:
image.png后端

image.png

在dos非浏览器窗口请求:(其在app端发送代码是一致的,没有cookie) 浏览器

image.png

一样的数据,咱们使用web去发送就会返回200:
image.png服务器

1.2 短信验证码登陆

咱们使用短信验证码去登陆时候,咱们携带一个参数:deviceId;而后咱们会将此参数存储在数据库或者redis里面。而后校验时候也是带着这个deviceId,此时咱们去从数据库查询。 cookie

image.png

1.2.1验证码在不一样模块下处理

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实现。

  1. 在Spring-Security-Core工程
    在Spring-Security-Core工程下面咱们在对验证码的处理器,处理时候须要保存和移除验证码到对应的ValidateCodeRepository中:
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);
    }
}

1.3 整改后测试

咱们在请求头里面添加字段:deviceId
image.png

查看redis中存储:
image.png

登陆校验:
image.png

image.png

相关文章
相关标签/搜索