spring-security-oauth2(六) 短信验证码接口开发

短信验证码接口开发

  1. 短信验证码生成接口
  2. 短信验证码发送接口
  3. 短信生成策略模板模式重构

1.短信验证码接口开发

1.1短信验证码生成接口

发送短信验证码controller

package com.rui.tiger.auth.core.captcha;

import lombok.extern.slf4j.Slf4j;
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.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 验证码控制器
 *
 * @author CaiRui
 * @date 2018-12-10 12:13
 */
@RestController
@Slf4j
public class CaptchaController {

    public static final String CAPTCHA_SESSION_KEY = "captcha_session_key";
    private static final String FORMAT_NAME = "JPEG";

    @Autowired
    private CaptchaGenerate imageCaptchaGenerate;
    @Autowired
    private CaptchaGenerate smsCaptchaGenerate;
    @Autowired
    private SmsCaptchaSend smsCaptchaSend;

    //spring session 工具类
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    /**
     * 获取图片验证码
     *
     * @param request
     * @param response
     * @throws IOException
     */
    @GetMapping("/captcha/image")
    public void createKaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //1.接口生成验证码
        ImageCaptchaVo imageCaptcha = (ImageCaptchaVo) imageCaptchaGenerate.generate();
        //2.保存到session中
        sessionStrategy.setAttribute(new ServletWebRequest(request), CAPTCHA_SESSION_KEY, imageCaptcha);
        //3.写到响应流中
        response.setHeader("Cache-Control", "no-store, no-cache");// 没有缓存
        response.setContentType("image/jpeg");
        ImageIO.write(imageCaptcha.getImage(), FORMAT_NAME, response.getOutputStream());
    }


    /**
     * 获取图片验证码
     *
     * @param request
     * @param response
     * @throws IOException
     */
    @GetMapping("/captcha/sms")
    public void createSms(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletRequestBindingException {
        log.info("获取短信验证码");
        //1.获取短信验证码
        CaptchaVo captchaVo = smsCaptchaGenerate.generate();
        //2.保存到session中
        sessionStrategy.setAttribute(new ServletWebRequest(request), CAPTCHA_SESSION_KEY + "sms", captchaVo);
        //3.发送
        String mobile = ServletRequestUtils.getRequiredStringParameter(request, "mobile");
        smsCaptchaSend.sendSms(mobile, captchaVo.getCode());
    }


}

由于短信验证码和图片验证码基本结构都差不多,只是没有图形,故对实体进行调整

package com.rui.tiger.auth.core.captcha;

import lombok.Data;

import java.awt.image.BufferedImage;
import java.time.LocalDateTime;

/**
 * 验证码
 *
 * @author CaiRui
 * @Date 2018/12/15 9:11
 */
@Data
public class CaptchaVo {
    /**
     * 验证码
     */
    private String code;
    /**
     * 失效时间 这个通常放在缓存中或维护在数据库中
     */
    private LocalDateTime expireTime;

    public CaptchaVo(String code, int expireAfterSeconds) {
        this.code = code;
        //多少秒后
        this.expireTime = LocalDateTime.now().plusSeconds(expireAfterSeconds);
    }

    public CaptchaVo(String code, LocalDateTime expireTime) {
        this.code = code;
        this.expireTime = expireTime;
    }
    /**
     * 是否失效
     *
     * @return
     */
    public boolean isExpried() {
        return LocalDateTime.now().isAfter(expireTime);
    }
}
package com.rui.tiger.auth.core.captcha;

import lombok.Data;

import java.awt.image.BufferedImage;
import java.time.LocalDateTime;

/**
 * 图片验证码信息对象
 *
 * @author CaiRui
 * @Date 2018/12/9 18:03
 */
@Data
public class ImageCaptchaVo extends CaptchaVo {
    /**
     * 图片验证码
     */
    private BufferedImage image;

    public ImageCaptchaVo(BufferedImage image, String code, int expireAfterSeconds) {
        super(code, expireAfterSeconds);
        this.image = image;
    }

    public ImageCaptchaVo(BufferedImage image, String code, LocalDateTime expireTime) {
        super(code, expireTime);
        this.image = image;
    }
}

相关配置类调整修改

package com.rui.tiger.auth.core.properties;

/**
 * 短信验证码配置类
 * @author CaiRui
 * @Date 2018/12/15 9:30
 */
public class SmsCaptchaProperties {
    /**
     * 长度
     */
    private int length=6;
    /**
     * 过期秒数 默认3分钟
     */
    private int expireSeconds=180;

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public int getExpireSeconds() {
        return expireSeconds;
    }

    public void setExpireSeconds(int expireSeconds) {
        this.expireSeconds = expireSeconds;
    }

}
package com.rui.tiger.auth.core.properties;

/**
 * 验证码配置类
 * @author CaiRui
 * @Date 2018/12/15 9:43
 */
public class CaptchaProperties {
    /**
     *图片验证码配置
     */
    private ImageCaptchaProperties image=new ImageCaptchaProperties();
    /**
     * 短信验证码配置
     */
    private SmsCaptchaProperties sms=new SmsCaptchaProperties();

    public ImageCaptchaProperties getImage() {
        return image;
    }

    public void setImage(ImageCaptchaProperties image) {
        this.image = image;
    }

    public SmsCaptchaProperties getSms() {
        return sms;
    }

    public void setSms(SmsCaptchaProperties sms) {
        this.sms = sms;
    }
}
package com.rui.tiger.auth.core.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 权限配置文件父类(注意这里不用lombok 会读取不到)
 * 这里会有很多权限配置子模块
 *
 * @author CaiRui
 * @date 2018-12-6 8:41
 */

@ConfigurationProperties(value = "tiger.auth", ignoreInvalidFields = true)
public class SecurityProperties {
    /**
     * 浏览器配置类
     */
    private BrowserProperties browser = new BrowserProperties();
    /**
     * 验证码配置类
     */
    private CaptchaProperties captcha = new CaptchaProperties();

    public BrowserProperties getBrowser() {
        return browser;
    }

    public void setBrowser(BrowserProperties browser) {
        this.browser = browser;
    }

    public CaptchaProperties getCaptcha() {
        return captcha;
    }

    public void setCaptcha(CaptchaProperties captcha) {
        this.captcha = captcha;
    }
}

短信验证码生成接口及实现类

package com.rui.tiger.auth.core.captcha;

/**
 * 验证码生成接口
 *
 * @author CaiRui
 * @date 2018-12-10 12:03
 */
public interface CaptchaGenerate {
	/**
	 * 生成验证码
	 *
	 * @return
	 */
	CaptchaVo generate();
}
package com.rui.tiger.auth.core.captcha;

import com.rui.tiger.auth.core.properties.SecurityProperties;
import org.apache.commons.lang.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 短信验证码生成器
 *
 * @author CaiRui
 * @Date 2018/12/15 9:10
 */
@Component("smsCaptchaGenerate")
public class SmsCaptchaGenerate implements CaptchaGenerate {

    @Autowired
    private SecurityProperties securityProperties;
    /**
     * 生成短信验证码
     *
     * @return
     */
    @Override
    public CaptchaVo generate() {
        String code = RandomStringUtils.randomNumeric(securityProperties.getCaptcha().getSms().getLength());
        return new CaptchaVo(code, securityProperties.getCaptcha().getSms().getExpireSeconds());
    }
}

1.2短信登陆码发送接口

短信验证码发送接口及其实现类

package com.rui.tiger.auth.core.captcha;

/**
 * 短信验证码发送接口
 * @author CaiRui
 * @Date 2018/12/15 10:03
 */
public interface SmsCaptchaSend {

    /**
     * 发送短信验证码
     * @param mobile
     * @param code
     * @return
     */
    boolean sendSms(String mobile,String code);

}
package com.rui.tiger.auth.core.captcha;

import lombok.extern.slf4j.Slf4j;

/**
 * @author CaiRui
 * @Date 2018/12/15 10:05
 */
@Slf4j
public class DefaultSmsCaptchaSender implements SmsCaptchaSend {

    //实际生产环境中,调用渠道供应商发送短信
    @Override
    public boolean sendSms(String mobile, String code) {
        log.info("模拟向手机{}发送短信验证码{}",mobile,code);
        log.info("短信渠道发送中...发送成功");
        return true;
    }

}

验证码配置类

package com.rui.tiger.auth.core.config;

import com.google.code.kaptcha.Producer;
import com.rui.tiger.auth.core.captcha.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 验证码Bean生成配置类
 *
 * @author CaiRui
 * @date 2018-12-12 8:41
 */
@Configuration
public class CaptchaBeanConfig {

	//图片验证码生成
	@Bean
	// spring 容器中如果存在imageCaptchaGenerate的bean就不会再初始化该bean了
	//可参见:https://www.cnblogs.com/yixianyixian/p/7346894.html 这篇博文
	@ConditionalOnMissingBean(name = "imageCaptchaGenerate")
	public CaptchaGenerate imageCaptchaGenerate() {
		ImageCaptchaGenerate imageCaptchaGenerate = new ImageCaptchaGenerate();
		return imageCaptchaGenerate;
	}
	//短信验证码生成
	@Bean
	@ConditionalOnMissingBean(name = "smsCaptchaGenerate")
	public CaptchaGenerate smsCaptchaGenerate() {
		SmsCaptchaGenerate smsCaptchaGenerate = new SmsCaptchaGenerate();
		return smsCaptchaGenerate;
	}

	@Bean
	@ConditionalOnMissingBean(DefaultSmsCaptchaSender.class)
	public SmsCaptchaSend defaultSmsCaptchaSender() {
		DefaultSmsCaptchaSender defaultSmsCaptchaSender=new DefaultSmsCaptchaSender();
		return defaultSmsCaptchaSender;
	}

}

在权限路径中放行 .antMatchers(securityProperties.getBrowser().getLoginPage(), "/authentication/require", "/captcha/*")

package com.rui.tiger.auth.browser.config;

import com.rui.tiger.auth.core.authentication.TigerAuthenticationFailureHandler;
import com.rui.tiger.auth.core.authentication.TigerAuthenticationSuccessHandler;
import com.rui.tiger.auth.core.captcha.CaptchaFilter;
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
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.UsernamePasswordAuthenticationFilter;
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 WebSecurityConfigurerAdapter {

	@Autowired
	private SecurityProperties securityProperties;
	@Autowired
	private TigerAuthenticationFailureHandler tigerAuthenticationFailureHandler;
	@Autowired
	private TigerAuthenticationSuccessHandler tigerAuthenticationSuccessHandler;
	@Autowired
	private DataSource dataSource;
	@Autowired
	private UserDetailsService userDetailsService;

	/**
	 * 密码加密解密
	 *
	 * @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;
	}


	@Override
	protected void configure(HttpSecurity http) throws Exception {
		//加入图片验证码过滤器
		CaptchaFilter captchaFilter = new CaptchaFilter();
		captchaFilter.setFailureHandler(tigerAuthenticationFailureHandler);
		captchaFilter.setSecurityProperties(securityProperties);
		captchaFilter.afterPropertiesSet();

		//图片验证码放在认证之前
		http.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)
				.formLogin()
				.loginPage("/authentication/require")//自定义登录请求
				.loginProcessingUrl("/authentication/form")//自定义登录表单请求
				.successHandler(tigerAuthenticationSuccessHandler)
				.failureHandler(tigerAuthenticationFailureHandler)
				.and()
				//记住我相关配置
				.rememberMe()
				.tokenRepository(persistentTokenRepository())
				.tokenValiditySeconds(securityProperties.getBrowser().getRemberMeSeconds())
				.userDetailsService(userDetailsService)
				.and()
				.authorizeRequests()
				.antMatchers(securityProperties.getBrowser().getLoginPage(),
						"/authentication/require", "/captcha/*")//此路径放行 否则会陷入死循环
				.permitAll()
				.anyRequest()
				.authenticated()
				.and()
				.csrf().disable()//跨域关闭
		;
	}

}

postman测试下

看控制台日志,可以看见我们的短信发送接口可以用

1.3 验证码生成策略模板方式重构

经过分析图片验证码和手机验证码的创建流程基本都是一样,主要经过三步。

  1. 生成验证码
  2. 保存到session中
  3. 发送(图片响应流写回,短信调用渠道直接发送)

下面结合UML图和代码看看是怎么实现的吧。

CaptchaController调用CaptchaCreateService,CaptchaCreateService策略调用CaptchaProcessor

CaptchaController 经过调整后如下,将原来的分别生成图片和短信的合并调整

package com.rui.tiger.auth.core.captcha;

import com.rui.tiger.auth.core.captcha.sms.SmsCaptchaSend;
import lombok.extern.slf4j.Slf4j;
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.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 验证码控制器
 *
 * @author CaiRui
 * @date 2018-12-10 12:13
 */
@RestController
@Slf4j
public class CaptchaController {
    @Autowired
    private CaptchaCreateService captchaCreateService;
    /**
     * 获取验证码
     *
     * @param request
     * @param response
     * @throws IOException
     */
    @GetMapping("/captcha/{type}")
    public void createCaptcha(HttpServletRequest request, HttpServletResponse response, @PathVariable String type) throws Exception {
        log.info("获取验证码开始");
        captchaCreateService.createCaptcha(new ServletWebRequest(request, response), type);
        log.info("获取验证码结束");
    }
}

验证码生成接口

package com.rui.tiger.auth.core.captcha;

import org.springframework.web.context.request.ServletWebRequest;

/**
 * @author CaiRui
 * @Date 2018/12/15 21:27
 */
public interface CaptchaCreateService {
    /**
     *  生成验证码
     * @param request
     * @param type
     */
    void createCaptcha(ServletWebRequest request, String type);
}
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.Service;
import org.springframework.web.context.request.ServletWebRequest;

/**
 * @author CaiRui
 * @Date 2018/12/15 21:27
 */
@Service
@Slf4j
public class CaptchaCreateServiceImpl implements CaptchaCreateService {
    /**
     * 生成验证码
     *
     * @param request
     * @param type
     */
    @Override
    public void createCaptcha(ServletWebRequest request, String type) {
        CaptchaTypeEnum captchaType=CaptchaTypeEnum.forCode(type);
        if   (type==null){
            throw new  CaptchaException("验证码类型不支持");
        }
        try {
            StrategyContainerImpl.getStrategy(CaptchaProcessor.class,captchaType)
                    .create(request);
        } catch (Exception e) {
            log.info("");
        }
    }
}

验证码类型枚举类

package com.rui.tiger.auth.core.captcha;

import java.util.HashMap;
import java.util.Map;

/**
 * 验证码类型枚举类
 * @author CaiRui
 * @Date 2018/12/15 17:58
 */
public enum CaptchaTypeEnum {

    SMS("sms","短信"),
    IMAGE("image","图形验证码");

    CaptchaTypeEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    private static Map<String,CaptchaTypeEnum> codeLookup = new HashMap<String,CaptchaTypeEnum>();
    private String code;
    private String desc;

    static {
        for (CaptchaTypeEnum type : CaptchaTypeEnum.values()) {
            codeLookup.put(type.code, type);
        }
    }

    /**
     * 根据类型获取枚举类
     * @param code
     * @return
     */
    public static CaptchaTypeEnum forCode(String code)  {
        return codeLookup.get(code);
    }

    public String getCode() {
        return code;
    }

}

验证码生成策略接口,

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> {
    /**
     * 验证码
     */
    String CAPTCHA_SESSION_KEY="captcha_session_key_";
    /**
     * 创建验证码
     * @param request 封装请求和响应
     * @throws Exception
     */
    void create(ServletWebRequest request) throws  Exception;
}

抽象实现父类

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.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);
    }

    protected abstract C generateCaptcha(ServletWebRequest request);

    protected abstract void send(ServletWebRequest request, C captcha) throws IOException, ServletRequestBindingException;

    private void save(ServletWebRequest request, C captcha) {
        sessionStrategy.setAttribute(request, CAPTCHA_SESSION_KEY+getCaptchaTypeFromUrl(request), captcha);
    }

    /**
     * 根据请求的url获取校验码的类型
     * @param request
     * @return
     */
    private String getCaptchaTypeFromUrl(ServletWebRequest request) {
        return StringUtils.substringAfter(request.getRequest().getRequestURI(), "/captcha/");
    }


}

图片验证码生成实现

package com.rui.tiger.auth.core.captcha.image;

import com.rui.tiger.auth.core.captcha.AbstractCaptchaProcessor;
import com.rui.tiger.auth.core.captcha.CaptchaGenerate;
import com.rui.tiger.auth.core.captcha.CaptchaTypeEnum;
import com.rui.tiger.auth.core.captcha.ImageCaptchaVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.ServletWebRequest;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author CaiRui
 * @Date 2018/12/15 18:31
 */
@Service
@Slf4j
public class ImageCaptchaProcessor extends AbstractCaptchaProcessor<ImageCaptchaVo> {
    private static final String FORMAT_NAME = "JPEG";

    @Autowired
    private CaptchaGenerate imageCaptchaGenerate;

    /**
     * 获得策略条件
     *
     * @return 用来注册的策略处理条件
     */
    @Override
    public CaptchaTypeEnum getCondition() {
        return CaptchaTypeEnum.IMAGE;
    }

    @Override
    protected ImageCaptchaVo generateCaptcha(ServletWebRequest request) {
        return (ImageCaptchaVo) imageCaptchaGenerate.generate();
    }

    @Override
    protected void send(ServletWebRequest request, ImageCaptchaVo captcha) throws IOException {
        HttpServletResponse response=request.getResponse();
        response.setHeader("Cache-Control", "no-store, no-cache");// 没有缓存
        response.setContentType("image/jpeg");
        ImageIO.write(captcha.getImage(), FORMAT_NAME, response.getOutputStream());

    }
}

短信验证码生成实现

package com.rui.tiger.auth.core.captcha.sms;

import com.rui.tiger.auth.core.captcha.AbstractCaptchaProcessor;
import com.rui.tiger.auth.core.captcha.CaptchaGenerate;
import com.rui.tiger.auth.core.captcha.CaptchaTypeEnum;
import com.rui.tiger.auth.core.captcha.CaptchaVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;

/**
 * @author CaiRui
 * @Date 2018/12/15 18:29
 */
@Service
@Slf4j
public class SmsCaptchaProcessor extends AbstractCaptchaProcessor<CaptchaVo> {

    @Autowired
    private CaptchaGenerate smsCaptchaGenerate;
    @Autowired
    private SmsCaptchaSend captchaSend;

    /**
     * 获得策略条件
     *
     * @return 用来注册的策略处理条件
     */
    @Override
    public CaptchaTypeEnum getCondition() {
        return CaptchaTypeEnum.SMS;
    }

    @Override
    protected CaptchaVo generateCaptcha(ServletWebRequest request) {
        return smsCaptchaGenerate.generate();
    }

    @Override
    protected void send(ServletWebRequest request, CaptchaVo captcha) throws ServletRequestBindingException {
        String mobile= ServletRequestUtils.getRequiredStringParameter(request.getRequest(),"mobile");
        captchaSend.sendSms(mobile, captcha.getCode());
    }
}

ok  重构完成 下面我们自定义短信登陆开发

个人码云地址:https://gitee.com/krui/tiger-auth 

猜你喜欢

转载自blog.csdn.net/ahcr1026212/article/details/85001933