前言
各种网站运用的登录办法一直在往一个简单、便利且安全的方向发展,传统的账号暗码现已不能满意需求了,现在通常都是短信验证码登录、扫码登录、刷脸登录等等,今日这篇文章就写一下怎么运用Spring Authorization Server完成短信验证码登录;实际上这些也都是Spring security的内容。
完成思路
在Spring Authorization Server入门 (七) 登录增加图形验证码一文中自己带各位读者了解了一下登录时结构所做的处理,当页面建议post办法的/login
恳求时会由UsernamePasswordAuthenticationFilter
阻拦处理,之后从恳求参数中获取账号暗码生成一个UsernamePasswordAuthenticationToken
,然后交由ProviderManager
处理,ProviderManager会根据token找到DaoAuthenticationProvider
去处理账号暗码的认证,上文中增加图形验证码去阻拦非法恳求的逻辑便是经过承继DaoAuthenticationProvider
并重写认证办法完成的。今日就来了解一下DaoAuthenticationProvider
中的认证逻辑,找到校验暗码的地方,然后测验重写校验办法。
账号暗码认证逻辑分析
在DaoAuthenticationProvider
中查找authenticate办法没有查找到
看一下父类AbstractUserDetailsAuthenticationProvider
中的authenticate办法完成
- 获取用户信息
- 检查暗码是否过期
- 检查暗码是否正确
侧重看一下additionalAuthenticationChecks
办法
这是一个抽象办法,办法注释中阐明晰该办法的用处,允许子类运用userDetails做任何附加校验,假如需求完成自定义逻辑完成应该要重写该办法以供给认证。
那就阐明DaoAuthenticationProvider
也完成了该办法,来看下子类完成。
这儿边做了两个校验,一个判别暗码是否为空,一个判别数据库中的暗码是否匹配用户输入的暗码,到这儿就很清晰了,写一个子类来重写该办法完成短信验证码的校验。
代码完成
在AuthorizationController接口中增加一个获取验证码的接口
现在是demo就先固定1234
@ResponseBody
@GetMapping("/getSmsCaptcha")
public Map<String,Object> getSmsCaptcha(String phone, HttpSession session) {
// 这儿应该返回一个一致响应类,暂时运用map替代
Map<String,Object> result = new HashMap<>();
result.put("code", HttpStatus.OK.value());
result.put("success", true);
result.put("message", "获取短信验证码成功.");
// 固定1234
result.put("data", "1234");
// 存入session中
session.setAttribute(phone, "1234");
return result;
}
AuthorizationConfig中放行/getSmsCaptcha
/**
* 装备认证相关的过滤器链
*
* @param http spring security中心装备类
* @return 过滤器链
* @throws Exception 抛出
*/
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authorize) -> authorize
// 放行静态资源
.requestMatchers("/assets/**", "/webjars/**", "/login", "/getCaptcha", "/getSmsCaptcha").permitAll()
.anyRequest().authenticated()
)
// 指定登录页面
.formLogin(formLogin ->
formLogin.loginPage("/login")
);
// 增加BearerTokenAuthenticationFilter,将认证服务当做一个资源服务,解析恳求头中的token
http.oauth2ResourceServer((resourceServer) -> resourceServer
.jwt(Customizer.withDefaults())
.accessDeniedHandler(SecurityUtils::exceptionHandler)
.authenticationEntryPoint(SecurityUtils::exceptionHandler)
);
return http.build();
}
承继CaptchaAuthenticationProvider
并重写additionalAuthenticationChecks办法
这儿没有修正默许的key,假如想自定义key能够重写authenticate办法然后构建一个UsernamePasswordAuthencationToken再调用父类的authenticate办法就行,下方代码中有示例
package com.example.authorization.sms;
import com.example.authorization.captcha.CaptchaAuthenticationProvider;
import com.example.constant.SecurityConstants;
import com.example.exception.InvalidCaptchaException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Objects;
/**
* 短信验证码校验完成
*
* @author vains
*/
@Slf4j
@Component
public class SmsCaptchaLoginAuthenticationProvider extends CaptchaAuthenticationProvider {
/**
* 利用结构办法在经过{@link Component}注解初始化时
* 注入UserDetailsService和passwordEncoder,然后
* 设置调用父类关于这两个属性的set办法设置进去
*
* @param userDetailsService 用户服务,给结构供给用户信息
* @param passwordEncoder 暗码解析器,用于加密和校验暗码
*/
public SmsCaptchaLoginAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
super(userDetailsService, passwordEncoder);
}
/*@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 获取当时request
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
throw new InvalidCaptchaException("Failed to get the current request.");
}
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 获取手机号与验证码
String phone = request.getParameter("phone");
String smsCaptcha = request.getParameter("smsCaptcha");
// 非空校验
if (ObjectUtils.isEmpty(phone) || ObjectUtils.isEmpty(smsCaptcha)) {
throw new BadCredentialsException("账号暗码不能为空.");
}
// 构建UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken unauthenticated = UsernamePasswordAuthenticationToken.unauthenticated(phone, smsCaptcha);
unauthenticated.setDetails(new WebAuthenticationDetails(request));
return super.authenticate(unauthenticated);
}*/
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
log.info("Authenticate sms captcha...");
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException("The sms captcha cannot be empty.");
}
// 获取当时request
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
throw new InvalidCaptchaException("Failed to get the current request.");
}
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 获取当时登录办法
String loginType = request.getParameter("loginType");
if (Objects.equals(loginType, SecurityConstants.SMS_LOGIN_TYPE)) {
// 获取存入session的验证码(UsernamePasswordAuthenticationToken的principal中现在存入的是手机号)
String smsCaptcha = (String) request.getSession(Boolean.FALSE).getAttribute((String) authentication.getPrincipal());
// 校验输入的验证码是否正确(UsernamePasswordAuthenticationToken的credentials中现在存入的是输入的验证码)
if (!Objects.equals(smsCaptcha, authentication.getCredentials())) {
throw new BadCredentialsException("The sms captcha is incorrect.");
}
// 在这儿也能够拓展其它登录办法,比方邮箱登录什么的
} else {
log.info("Not sms captcha loginType, exit.");
// 其它调用父类默许完成的暗码办法登录
super.additionalAuthenticationChecks(userDetails, authentication);
}
log.info("Authenticated sms captcha.");
}
}
该类承继自CaptchaAuthenticationProvider
类, 这样就会保存图形验证码的校验流程,假如一起注入两个DaoAuthenticationProvider的子类则都不会收效,所以需求去除CaptchaAuthenticationProvider
类的Component
注解,短信验证码登录时不需求图形验证码校验的能够在CaptchaAuthenticationProvider
类中增加一个校验,判别一下登录类型,如下
package com.example.authorization.captcha;
import com.example.constant.SecurityConstants;
import com.example.exception.InvalidCaptchaException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Objects;
/**
* 验证码校验
* 注入ioc中替换原先的DaoAuthenticationProvider
* 在authenticate办法中增加校验验证码的逻辑
* 最终调用父类的authenticate办法并返回
*
* @author vains
*/
@Slf4j
public class CaptchaAuthenticationProvider extends DaoAuthenticationProvider {
/**
* 利用结构办法在经过{@link Component}注解初始化时
* 注入UserDetailsService和passwordEncoder,然后
* 设置调用父类关于这两个属性的set办法设置进去
*
* @param userDetailsService 用户服务,给结构供给用户信息
* @param passwordEncoder 暗码解析器,用于加密和校验暗码
*/
public CaptchaAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
super.setPasswordEncoder(passwordEncoder);
super.setUserDetailsService(userDetailsService);
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
log.info("Authenticate captcha...");
// 获取当时request
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
throw new InvalidCaptchaException("Failed to get the current request.");
}
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 获取当时登录办法
String loginType = request.getParameter("loginType");
if (Objects.equals(loginType, SecurityConstants.SMS_LOGIN_TYPE)) {
log.info("It isn't necessary captcha authenticate.");
return super.authenticate(authentication);
}
// 获取参数中的验证码
String code = request.getParameter("code");
if (ObjectUtils.isEmpty(code)) {
throw new InvalidCaptchaException("The captcha cannot be empty.");
}
// 获取session中存储的验证码
Object sessionCaptcha = request.getSession(Boolean.FALSE).getAttribute("captcha");
if (sessionCaptcha instanceof String sessionCode) {
if (!sessionCode.equalsIgnoreCase(code)) {
throw new InvalidCaptchaException("The captcha is incorrect.");
}
} else {
throw new InvalidCaptchaException("The captcha is abnormal. Obtain it again.");
}
log.info("Captcha authenticated.");
return super.authenticate(authentication);
}
}
修正登录页面,增加短信验证码办法登录
登录页面写的比较粗糙,大家运用自己的登录页面就好,主要的便是运用短信验证码登录时提交的表单数据的key与后端对应上就行。
login.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"
content="width=device-width, initial-scale=1 minimum-scale=1 maximum-scale=1 user-scalable=no"/>
<link rel="stylesheet" href="./assets/css/style.css" type="text/css"/>
<title>一致认证渠道</title>
</head>
<body>
<div class="bottom-container">
</div>
<!-- <div th:if="${error}" class="alert" id="alert">
<div class="error-alert">
<img src="" alt="logo" >
<div th:text="${error}">
</div>
</div>
</div> -->
<div id="error_box">
</div>
<div class="form-container">
<form class="form-signin" method="post" th:action="@{/login}">
<input type="hidden" id="loginType" name="loginType" value="passwordLogin"/>
<!-- <div th:if="${param.error}" class="alert alert-danger" role="alert" th:text="${param}">
Invalid username or password.
</div>
<div th:if="${param.logout}" class="alert alert-success" role="alert">
你现已登出成功.
</div> -->
<!-- <div class="text-placeholder">-->
<!-- 渠道登录-->
<!-- </div>-->
<div class="welcome-text">
<img src="./assets/img/logo.png" alt="logo" width="60">
<span>
一致认证渠道
</span>
</div>
<div>
<input type="text" id="username" name="username" class="form-control" placeholder="手机 / 邮箱"
autofocus onblur="leave()"/>
</div>
<div id="passContainer">
<input type="password" id="password" name="password" class="form-control" placeholder="请输入暗码"
onblur="leave()"/>
</div>
<div class="code-container" id="codeContainer">
<input type="text" id="code" name="code" class="form-control" placeholder="请输入验证码"
onblur="leave()"/>
<img src="" id="code-image" onclick="getVerifyCode()"/>
</div>
<div style="display: none; margin-bottom: 0" class="code-container" id="smsContainer">
<input type="text" name="" class="form-control" placeholder="请输入验证码" onblur="leave()"/>
<a id="getSmsCaptchaBtn" class="btn btn-light btn-block bg-white getCaptcha"
href="javascript:getSmsCaptcha()">获取验证码</a>
</div>
<div class="change-login-type">
<div></div>
<a id="changeLoginType" href="javascript:showSmsCaptchaPage()">短信验证登录</a>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">登 录</button>
<!--<a class="btn btn-light btn-block bg-white" href="https://juejin.im/oauth2/authorization/gitee" role="link">
Sign in with Gitee
</a>-->
<!-- <div class="text-placeholder">-->
<!-- 第三方登录-->
<!-- </div>-->
<!-- <div>-->
<!-- <a class="btn btn-light btn-block bg-white" href="https://juejin.im/oauth2/authorization/github-idp" role="link"-->
<!-- >-->
<!-- <img alt="Sign in with GitHub"-->
<!-- src="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" />-->
<!-- Github-->
<!-- </a>-->
<!-- </div>-->
</form>
</div>
</body>
</html>
<script>
function showSmsCaptchaPage() {
// 躲藏暗码框
let passContainer = document.getElementById('passContainer');
passContainer.style.display = 'none';
// 设置password输入框的name为空
passContainer.children[0].setAttribute('name', '')
// 躲藏验证码框
let codeContainer = document.getElementById('codeContainer');
codeContainer.style.display = 'none';
// 设置登录类型为短信验证码
let loginType = document.getElementById('loginType');
loginType.value = 'smsCaptcha';
// 显现获取短信验证码按钮与输入框
let smsContainer = document.getElementById('smsContainer');
smsContainer.style.display = '';
smsContainer.children[0].setAttribute('name', 'password')
// 设置切换按钮文字与点击作用
let changeLoginType = document.getElementById('changeLoginType');
changeLoginType.innerText = '账号暗码登录';
changeLoginType.setAttribute('href', 'javascript:showPasswordPage()')
changeLoginType.style.paddingTop = '25px';
changeLoginType.style.paddingBottom = '5px';
}
function showPasswordPage() {
// 显现暗码框
let passContainer = document.getElementById('passContainer');
passContainer.style.display = '';
// 设置password输入框
passContainer.children[0].setAttribute('name', 'password')
// 显现验证码框
let codeContainer = document.getElementById('codeContainer');
codeContainer.style.display = '';
// 设置登录类型为账号暗码
let loginType = document.getElementById('loginType');
loginType.value = 'passwordLogin';
// 躲藏获取短信验证码按钮与输入框
let smsContainer = document.getElementById('smsContainer');
smsContainer.style.display = 'none';
smsContainer.children[0].setAttribute('name', '')
// 设置切换按钮文字与点击作用
let changeLoginType = document.getElementById('changeLoginType');
changeLoginType.innerText = '短信验证登录'
changeLoginType.setAttribute('href', 'javascript:showSmsCaptchaPage()')
changeLoginType.style.paddingTop = '0';
}
function leave() {
document.body.scrollTop = document.documentElement.scrollTop = 0;
}
function getVerifyCode() {
let requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch(`${window.location.origin}/getCaptcha`, requestOptions)
.then(response => response.text())
.then(r => {
if (r) {
let result = JSON.parse(r);
document.getElementById('code-image').src = result.data
}
})
.catch(error => console.log('error', error));
}
function getSmsCaptcha() {
let phone = document.getElementById('username').value;
if (phone === null || phone === '' || typeof phone === 'undefined') {
showError('手机号码不能为空.')
return;
}
// 禁用按钮
let getSmsCaptchaBtn = document.getElementById('getSmsCaptchaBtn');
getSmsCaptchaBtn.style.pointerEvents = 'none';
// 开始1分钟倒计时
resetBtn(getSmsCaptchaBtn);
let requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch(`${window.location.origin}/getSmsCaptcha?phone=${phone}`, requestOptions)
.then(response => response.text())
.then(r => {
if (r) {
let result = JSON.parse(r);
if (result.success) {
showError('获取验证码成功.固定为:' + result.data)
}
}
})
.catch(error => console.log('error', error));
}
/**
* 1分钟倒计时
*/
function resetBtn(getSmsCaptchaBtn) {
let s = 60;
getSmsCaptchaBtn.innerText = `重新获取(${--s})`
// 定时器 每隔一秒变化一次(1000ms = 1s)
let t = setInterval(() => {
getSmsCaptchaBtn.innerText = `重新获取(${--s})`
if (s === 0) {
clearInterval(t)
getSmsCaptchaBtn.innerText = '获取验证码'
getSmsCaptchaBtn.style.pointerEvents = '';
}
}, 1000);
}
getVerifyCode();
</script>
<script th:inline="javascript">
function showError(message) {
let errorBox = document.getElementById("error_box");
errorBox.innerHTML = message;
errorBox.style.display = "block";
setTimeout(() => {
closeError();
}, 3000)
}
function closeError() {
let errorBox = document.getElementById("error_box");
errorBox.style.display = "none";
}
let error = [[${ error }]]
if (error) {
if (window.Notification) {
Notification.requestPermission(function () {
if (Notification.permission === 'granted') {
// 用户点击了允许
let n = new Notification('登录失败', {
body: error,
icon: './assets/img/logo.png'
})
setTimeout(() => {
n.close();
}, 3000)
} else {
showError(error);
}
})
}
}
</script>
style.css
* {
margin: 0;
padding: 0;
}
body {
height: 100vh;
overflow: hidden;
background: linear-gradient(200deg, #72afd3, #96fbc4);
}
/* 上方欢迎语 */
.welcome-text {
color: black;
display: flex;
font-size: 18px;
font-weight: 300;
line-height: 1.7;
align-items: center;
justify-content: center;
}
.welcome-text img {
margin-right: 12px !important;
}
/* 提示文字 */
.text-placeholder {
display: flex;
font-size: 80%;
color: #909399;
justify-content: center;
}
/* 下方布景色彩 */
.bottom-container {
width: 100%;
height: 50vh;
bottom: -15vh;
position: absolute;
transform: skew(0, 3deg);
background: rgb(23, 43, 77);
}
/* 表单卡片款式 */
.form-container {
width: 100vw;
display: flex;
height: 100vh;
align-items: center;
justify-content: center;
}
/* 表单款式 */
.form-signin {
z-index: 20;
width: 25vw;
display: flex;
border-radius: 3%;
padding: 35px 50px;
flex-direction: column;
background: rgb(247, 250, 252);
}
/* 按钮款式 */
.btn-primary {
height: 40px;
color: white;
cursor: pointer;
border-radius: 0.25rem;
background: #5e72e4;
border: 1px #5e72e4 solid;
transition: all 0.15s ease;
/* -webkit-box-shadow: 0 4px 6px rgb(50 50 93 / 11%), 0 1px 3px rgb(0 0 0 / 8%);
box-shadow: 0 4px 6px rgb(50 50 93 / 11%), 0 1px 3px rgb(0 0 0 / 8%); */
}
.btn-primary:hover {
transform: translateY(-3%);
-webkit-box-shadow: 0 4px 6px rgb(50 50 93 / 11%), 0 1px 3px rgb(0 0 0 / 8%);
box-shadow: 0 4px 6px rgb(50 50 93 / 11%), 0 1px 3px rgb(0 0 0 / 8%);
}
/* 表单距离 */
.form-signin div, button {
margin-bottom: 25px;
}
/* 表单输入框 */
.form-signin input {
width: 100%;
height: 40px;
outline: none;
text-indent: 15px;
border-radius: 3px;
border: 1px #e4e7ed solid;
}
/* 表单验证码容器 */
.code-container {
display: flex;
justify-content: space-between;
}
/* 表单验证码容器 */
.code-container input {
margin-right: 10px;
}
#code-image {
width: 150px;
height: 40px;
}
.code-btn {
width: 150px;
height: 40px;
}
/* 表单超链接 */
.btn-light {
height: 40px;
display: flex;
color: #5e72e4;
border-radius: 3px;
align-items: center;
justify-content: center;
border: 1px #5e72e4 solid;
}
.form-signin img {
margin: 0;
}
.form-signin a {
text-decoration: none;
}
.btn-light:hover {
transform: translateY(-3%);
-webkit-box-shadow: 0 4px 6px rgb(50 50 93 / 11%), 0 1px 3px rgb(0 0 0 / 8%);
box-shadow: 0 4px 6px rgb(50 50 93 / 11%), 0 1px 3px rgb(0 0 0 / 8%);
}
.form-signin input:focus {
border: 1px solid rgb(41, 50, 225);
}
.alert {
top: 20px;
width: 100%;
z-index: 50;
display: flex;
position: absolute;
align-items: center;
justify-content: center;
}
/* 弹框款式 */
#error_box {
background-color: rgba(0, 0, 0, 0.7);
color: #fff;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 10px;
padding: 15px;
display: none;
z-index: 500;
animation: shake 0.2s;
}
/* 切换登录办法按钮 */
.change-login-type {
display: flex;
font-size: 15px;
justify-content: space-between;
margin-top: -10px !important;
margin-bottom: 10px !important;
}
.getCaptcha {
width: 150px;
background-color: white;
}
#error_message {
padding-left: 10px;
}
@keyframes shake {
0% {
transform: translate(-50%, -50%);
}
25% {
transform: translate(-45%, -50%);
}
50% {
transform: translate(-50%, -50%);
}
75% {
transform: translate(-45%, -50%);
}
100% {
transform: translate(-50%, -50%);
}
}
/*修正提示信息的文本色彩*/
input::-webkit-input-placeholder {
/* WebKit browsers */
color: #8898aa;
}
input::-moz-placeholder {
/* Mozilla Firefox 19+ */
color: #8898aa;
}
input:-ms-input-placeholder {
/* Internet Explorer 10+ */
color: #8898aa;
}
/* 移动端css */
@media screen and (orientation: portrait) {
.form-signin {
width: 100%;
}
.form-container {
width: auto;
height: 90vh;
padding: 20px;
}
.welcome-text {
top: 9vh;
flex-direction: column;
}
}
/* 宽度 */
/* 屏幕 > 666px && < 800px */
@media (min-width: 667px) and (max-width: 800px) {
.form-signin {
width: 50vw;
}
.welcome-text {
top: 18vh;
}
}
/* 屏幕 > 800px */
@media (min-width: 800px) and (max-width: 1000px) {
.form-signin {
width: 500px;
}
}
/* 高度 */
@media (min-height: 600px) and (max-height: 600px) {
.welcome-text {
top: 6%;
}
}
@media (min-height: 800px) and (max-height: 1000px) {
.welcome-text {
top: 12%;
}
}
测验
到这儿编码部分就完成了,接下来测验下看看
拜访恳求授权接口
http://127.0.0.1:8080/oauth2/authorize?response_type=code&client_id=pkce-message-client&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Flogin%2Foauth2%2Fcode%2Fmessaging-client-oidc&scope=message.read&code_challenge=kfis_wJYpmCAPO-Ap1Sc6GXyz_x2dhhMsm9FOA7eEWY&code_challenge_method=S256
重定向至登录页面
点击短信验证登录,切换短信登录
输入手机号,点击获取验证码
因为没有重写UserDetailsService的loadByUsername办法,所以这儿用账号模拟手机号。
获取成功,开始倒数
输入验证码提交登录
携带token重定向至回调地址
检查控制台日志
观察日志能够发现自定义的短信验证码校验已收效
SecurityConstants.java
新建一个constant包
package com.example.constant;
/**
* security 常量类
*
* @author vains
*/
public class SecurityConstants {
/**
* 登录办法——短信验证码
*/
public static final String SMS_LOGIN_TYPE = "smsCaptcha";
/**
* 权限在token中的key
*/
public static final String AUTHORITIES_KEY = "authorities";
}
代码已提交至Gitee:gitee.com/vains-Sofia…