【Spring Security + OAuth2 + JWT入门到实战】21. 改造短信登录

女生的网名这么多〃 提交于 2020-03-19 13:34:03

3 月,跳不动了?>>>

之前代码都是基于浏览器做的,验证码保存在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
}

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!