增加手机验证码登录

This commit is contained in:
WenC 2024-04-09 16:04:24 +08:00
parent 4c79271601
commit 594a732d14
6 changed files with 236 additions and 21 deletions

View File

@ -87,6 +87,7 @@ public class SystemUserRouteController extends DefaultBaseController {
public ModelAndView login(HttpServletRequest request) { public ModelAndView login(HttpServletRequest request) {
ModelAndView mv = new ModelAndView(); ModelAndView mv = new ModelAndView();
Map<String, String> pageParams = oAuthService.getPageParams(request); Map<String, String> pageParams = oAuthService.getPageParams(request);
pageParams.put("loginPhoneAction", "oauth/phone");
mv.addObject(IUserCenterConst.PAGE_PARAMS, pageParams); mv.addObject(IUserCenterConst.PAGE_PARAMS, pageParams);
mv.addObject(IUserCenterConst.CUSTOM_LOGIN_FORM, loginFormService.getActive()); mv.addObject(IUserCenterConst.CUSTOM_LOGIN_FORM, loginFormService.getActive());
mv.addObject("HomeServerUrl", ProjectConfigUtil.getText("HomeServerUrl")); mv.addObject("HomeServerUrl", ProjectConfigUtil.getText("HomeServerUrl"));

View File

@ -0,0 +1,52 @@
package cn.com.tenlion.operator.login.phone;
import cn.com.tenlion.operator.login.phone.auth.LoginPhoneAuthFilter;
import cn.com.tenlion.operator.login.phone.auth.LoginPhoneAuthProvider;
import ink.wgink.interfaces.config.ICustomUserSecurityConfig;
import ink.wgink.interfaces.expand.login.ILoginHandlerService;
import ink.wgink.login.base.handler.LoginFailureHandler;
import ink.wgink.login.base.service.user.UserLoginService;
import ink.wgink.module.sms.service.sms.ISmsService;
import ink.wgink.properties.BaseProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
@Component
public class LoginPhoneConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> implements ICustomUserSecurityConfig {
@Autowired
private BaseProperties baseProperties;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private UserLoginService userLoginService;
@Autowired(required = false)
private ILoginHandlerService loginHandler;
@Autowired
private HttpSession httpSession;
@Autowired
private ISmsService smsService;
@Override
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
LoginPhoneAuthFilter loginPhoneAuthFilter = new LoginPhoneAuthFilter(smsService);
loginPhoneAuthFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
loginPhoneAuthFilter.setAuthenticationFailureHandler(new LoginFailureHandler(baseProperties.getLoginFailure()));
LoginPhoneAuthProvider loginPhoneAuthProvider = new LoginPhoneAuthProvider(userDetailsService,
userLoginService,
loginHandler,
httpSession
);
http.authenticationProvider(loginPhoneAuthProvider).addFilterBefore(loginPhoneAuthFilter, UsernamePasswordAuthenticationFilter.class);
}
}

View File

@ -0,0 +1,46 @@
package cn.com.tenlion.operator.login.phone.auth;
import ink.wgink.login.base.exceptions.UserAuthenticationException;
import ink.wgink.module.sms.service.sms.ISmsService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LoginPhoneAuthFilter extends AbstractAuthenticationProcessingFilter {
private ISmsService smsService;
public LoginPhoneAuthFilter(ISmsService smsService) {
super(new AntPathRequestMatcher("/oauth/phone", "POST"));
this.smsService = smsService;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String phone = request.getParameter("username").trim();
String code = request.getParameter("code").trim();
if (StringUtils.isBlank(phone)) {
throw new UserAuthenticationException("手机号不能为空");
}
if (StringUtils.isBlank(code)) {
throw new UserAuthenticationException("验证码不能为空");
}
// String verifyCode = smsService.getVerifyCode(phone);
// if (StringUtils.isBlank(verifyCode)) {
// throw new UserAuthenticationException("验证码错误");
// }
// if (!StringUtils.equals(code, verifyCode)) {
// throw new UserAuthenticationException("验证码不匹配");
// }
LoginPhoneAuthToken loginPhoneAuthToken = new LoginPhoneAuthToken(phone, null);
loginPhoneAuthToken.setDetails(authenticationDetailsSource.buildDetails(request));
return this.getAuthenticationManager().authenticate(loginPhoneAuthToken);
}
}

View File

@ -0,0 +1,75 @@
package cn.com.tenlion.operator.login.phone.auth;
import ink.wgink.interfaces.expand.login.ILoginHandlerService;
import ink.wgink.login.base.consts.IUserCenterConst;
import ink.wgink.login.base.enums.LoginTypeEnum;
import ink.wgink.login.base.exceptions.UserAuthenticationException;
import ink.wgink.login.base.service.user.UserLoginService;
import ink.wgink.pojo.bos.LoginUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import javax.servlet.http.HttpSession;
public class LoginPhoneAuthProvider implements AuthenticationProvider {
private static final Logger LOG = LoggerFactory.getLogger(LoginPhoneAuthProvider.class);
private UserDetailsService userDetailsService;
private UserLoginService userLoginService;
private ILoginHandlerService loginHandler;
private HttpSession httpSession;
public LoginPhoneAuthProvider(UserDetailsService userDetailsService, UserLoginService userLoginService, ILoginHandlerService loginHandler, HttpSession httpSession) {
this.userDetailsService = userDetailsService;
this.userLoginService = userLoginService;
this.loginHandler = loginHandler;
this.httpSession = httpSession;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
LoginPhoneAuthToken loginPhoneAuthToken = (LoginPhoneAuthToken) authentication;
String username = (String) loginPhoneAuthToken.getPrincipal();
httpSession.setAttribute(IUserCenterConst.LOGIN_USERNAME, username);
LoginUser loginUser = (LoginUser) userDetailsService.loadUserByUsername(username);
userLoginService.setLoginUserInfo(loginUser);
if (loginUser.getAuthorities().isEmpty()) {
throw new UserAuthenticationException(loginUser.getUsername() + "用户无任何角色");
}
// 登录逻辑处理
loginHandler(loginUser);
// 清空session中的错误信息
httpSession.removeAttribute(IUserCenterConst.ERROR_MESSAGE);
httpSession.removeAttribute(IUserCenterConst.LOGIN_USERNAME);
// 更新登录信息
userLoginService.updateUserLoginInfo(loginUser.getUserId(), loginUser.getUserName(), LoginTypeEnum.USERNAME_AND_PASSWORD.getValue());
LoginPhoneAuthToken loginPhoneAuthTokenResult = new LoginPhoneAuthToken(loginUser, null, loginUser.getAuthorities());
loginPhoneAuthTokenResult.setDetails(loginPhoneAuthToken.getDetails());
return loginPhoneAuthTokenResult;
}
@Override
public boolean supports(Class<?> aClass) {
return LoginPhoneAuthToken.class.isAssignableFrom(aClass);
}
/**
* 登录处理
*
* @param loginUser
*/
private void loginHandler(LoginUser loginUser) {
if (loginHandler != null) {
try {
loginHandler.handle(loginUser);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
throw new UserAuthenticationException("登录异常");
}
}
}
}

View File

@ -0,0 +1,37 @@
package cn.com.tenlion.operator.login.phone.auth;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
public class LoginPhoneAuthToken extends AbstractAuthenticationToken {
private final Object principal;
private Object credentials;
public LoginPhoneAuthToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
public LoginPhoneAuthToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(true);
}
@Override
public Object getCredentials() {
return credentials;
}
@Override
public Object getPrincipal() {
return principal;
}
}

View File

@ -288,7 +288,6 @@
} }
#loginBox .denglu .yzmtx1 { #loginBox .denglu .yzmtx1 {
position: relative; position: relative;
display: none;
margin-bottom: 20px; margin-bottom: 20px;
width: 100%; width: 100%;
height: 38px; height: 38px;
@ -306,7 +305,6 @@
border: none; border: none;
} }
#loginBox .denglu .yzmbox1 { #loginBox .denglu .yzmbox1 {
display: none;
width: 100%; width: 100%;
height: 38px; height: 38px;
line-height: 38px; line-height: 38px;
@ -404,11 +402,11 @@
<div id="loginBox" class="layadmin-user-login-main"> <div id="loginBox" class="layadmin-user-login-main">
<div class="btnlist"> <div class="btnlist">
<div> <div>
<button id="zhanghaomima">账号密码</button> <button id="zhanghaomima" @click="loginTypeChange('zhanghaomima')">账号密码</button>
<span class="span1"></span> <span class="span1"></span>
</div> </div>
<div> <div>
<button id="yzmlongin">验证码登录</button> <button id="yzmlongin" @click="loginTypeChange('yanzhengma')">验证码登录</button>
<span class="span2"></span> <span class="span2"></span>
</div> </div>
@ -425,22 +423,22 @@
</div> </div>
</div> </div>
<div id="loginFormBox" class="layadmin-user-login-box layadmin-user-login-body layui-form"> <div id="loginFormBox" class="layadmin-user-login-box layadmin-user-login-body layui-form">
<form id="loginForm" :action="pageParams.loginFormAction" method="post" @submit.prevent="submitForm" class="denglu"> <form id="loginForm" :action="activeAction" method="post" @submit.prevent="submitForm" class="denglu">
<input type="hidden" name="referToken" v-model="formData.referToken"/> <input type="hidden" name="referToken" v-model="formData.referToken"/>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-username" for="username"></label> <label class="layadmin-user-login-icon layui-icon layui-icon-username" for="username"></label>
<input type="text" v-model="formData.username" id="username" name="username" lay-verify="username" placeholder="用户名" class="layui-input"> <input type="text" v-model="formData.username" id="username" name="username" lay-verify="username" placeholder="用户名" class="layui-input">
</div> </div>
<div class="layui-form-item mima" id="passwordBox" > <div class="layui-form-item mima" id="passwordBox" v-if="loginType === 'zhanghaomima'">
<label class="layadmin-user-login-icon layui-icon layui-icon-password" for="password"></label> <label class="layadmin-user-login-icon layui-icon layui-icon-password" for="password"></label>
<input type="password" v-model="formData.password" id="password" name="password" lay-verify="password" placeholder="密码" class="layui-input"> <input type="password" v-model="formData.password" id="password" name="password" lay-verify="password" placeholder="密码" class="layui-input">
</div> </div>
<div class="yzmbox1" @click="getLoginVerificationCode"> <div class="yzmbox1" @click="getLoginVerificationCode" v-if="loginType === 'yanzhengma'">
<span id="yzm11">点击获取验证码</span> <span id="yzm11">点击获取验证码</span>
</div> </div>
<div class="yzmtx1"> <div class="yzmtx1" v-if="loginType === 'yanzhengma'">
<label class="layadmin-user-login-icon layui-icon layui-icon-password" for="password"></label> <label class="layadmin-user-login-icon layui-icon layui-icon-password" for="inputyzm11"></label>
<input type="text" placeholder="请输入验证码" id="inputyzm11"> <input type="text" v-model="formData.code" name="code" placeholder="请输入验证码" lay-verify="code" id="inputyzm11">
</div> </div>
<div class="layui-form-item" id="verificationCodeBox" v-if="pageParams.verificationCode == 'true'" > <div class="layui-form-item" id="verificationCodeBox" v-if="pageParams.verificationCode == 'true'" >
<!-- 验证码 --> <!-- 验证码 -->
@ -525,6 +523,8 @@
el: '#app', el: '#app',
data: { data: {
pageParams: pageParams, pageParams: pageParams,
loginType: 'zhanghaomima',
activeAction: pageParams.loginFormAction,
formData: { formData: {
referToken: pageParams.referToken, referToken: pageParams.referToken,
verificationCode: '', verificationCode: '',
@ -680,7 +680,15 @@
$('#loginForm').submit(); $('#loginForm').submit();
}) })
}, },
loginTypeChange: function(type) {
if(type === 'zhanghaomima') {
this.activeAction = this.pageParams.loginFormAction;
}
if(type === 'yanzhengma') {
this.activeAction = this.pageParams.loginPhoneAction;
}
this.loginType = type;
}
}, },
mounted: function() { mounted: function() {
var self = this; var self = this;
@ -703,6 +711,11 @@
return '密码不能为空'; return '密码不能为空';
} }
}, },
code: function(value, item) {
if(!value) {
return '验证码不能为空';
}
},
verificationCode: function(value, item) { verificationCode: function(value, item) {
if(!value) { if(!value) {
return '验证码不能为空'; return '验证码不能为空';
@ -713,6 +726,7 @@
}) })
// 切换账号密码登录和验证码登录 // 切换账号密码登录和验证码登录
let loginForm = document.querySelector("#loginForm");
let mima = document.querySelector(".mima") let mima = document.querySelector(".mima")
let yzmbox1 = document.querySelector('.yzmbox1') let yzmbox1 = document.querySelector('.yzmbox1')
let yzmtx1 = document.querySelector(".yzmtx1") let yzmtx1 = document.querySelector(".yzmtx1")
@ -731,10 +745,7 @@
span2.style.display = 'none' span2.style.display = 'none'
yzmlongin.style.fontWeight = 400 yzmlongin.style.fontWeight = 400
yzmlongin.style.borderBottom = '0' yzmlongin.style.borderBottom = '0'
mima.style.display = 'block'
bixuan.style.display = 'block' bixuan.style.display = 'block'
yzmtx1.style.display = 'none'
yzmbox1.style.display = 'none'
}) })
// 验证码登录 // 验证码登录
document.querySelector('#yzmlongin').addEventListener('click', function () { document.querySelector('#yzmlongin').addEventListener('click', function () {
@ -744,10 +755,7 @@
span2.style.display = 'block' span2.style.display = 'block'
zhanghaomima.style.fontWeight = 400 zhanghaomima.style.fontWeight = 400
zhanghaomima.style.borderBottom = '0' zhanghaomima.style.borderBottom = '0'
mima.style.display = 'none'
bixuan.style.display = 'none' bixuan.style.display = 'none'
yzmtx1.style.display = 'block'
yzmbox1.style.display = 'block'
}) })
// 点击 忘记密码 去登录 切换div // 点击 忘记密码 去登录 切换div
@ -761,14 +769,10 @@
// 忘记密码 // 忘记密码
document.querySelector('#goChangePassword').addEventListener('click', function () { document.querySelector('#goChangePassword').addEventListener('click', function () {
btnList.style.display = 'none' btnList.style.display = 'none'
change.style.display = 'block' change.style.display = 'block'
dengLu.style.display = 'none' dengLu.style.display = 'none'
changePassword.style.display = 'block' changePassword.style.display = 'block'
}) })
// 去登录 // 去登录