重点标识
思路:先输入手机号,再生成验证码,存到session或者redis,发给手机,然后登陆验证。
准备数据库
先准备一张user表,如下
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(0) NULL DEFAULT NULL,
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`enabled` tinyint(1) NULL DEFAULT NULL,
`accountNonExpired` tinyint(1) NULL DEFAULT NULL,
`accountNonLocked` tinyint(1) NULL DEFAULT NULL,
`credentialsNonExpired` tinyint(1) NULL DEFAULT NULL,
`phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
数据库这样就行了,接下来我们来构建一个Spring Boot项目,加入依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
准备User类,这里简单点,就不专门搞那些锁定了,直接给true。
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
private String phone;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
准备UserService:
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(username);
if(user == null){
throw new UsernameNotFoundException("用户名找不到!");
};
return user;
}
}
最后,准备一个mapper
@Mapper
public interface UserMapper {
@Select("SELECT * FROM USER WHERE USERNAME=#{username}")
public User loadUserByUsername(String username) ;
}
别忘了,配置数据库地址!
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql:///security_mybatis
配置Security
@Configuration
public class SecurityConfig {
@Bean
WebSecurityCustomizer webSecurityCustomizer(){
return web -> web.ignoring().requestMatchers("*.js");
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(a->a
.requestMatchers("/sendSms").permitAll()
.anyRequest().authenticated())
//这里注意,不配置就是默认的登陆地址login,但是如果配置了登录页面,则后面的登录地址也要配置
.formLogin(f->f.loginPage("/login.html").loginProcessingUrl("/login").permitAll())
.csrf(c->c.disable());
return http.build();
}
}
登录页面也放这里吧:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="jquery-3.7.1.min.js"></script>
</head>
<body>
<form action="/login" method="post">
<table>
<tr>
<td>用户名</td>
<td><input type="text" name="username" value="admin"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password" value="123"></td>
</tr>
<tr>
<td><input type="submit" name="登录"></td>
</tr>
</table>
</form>
<h1>验证码</h1>
<form action="/login" method="post">
<table>
<tr>
<td>手机号</td>
<td><input type="text" name="phone" value="" id="phone"></td>
</tr>
<tr>
<td>验证码</td>
<td><input type="text" name="code" value=""></td>
<td><button type="button" onclick="sendsmS()" name="">发送验证码</button></td>
</tr>
<tr>
<td><input type="submit" name="登录"></td>
</tr>
</table>
</form>
</body>
<script>
function sendsmS(){
let phone = $("#phone").val();
$.get("/sendSms",{phone:phone})
}
</script>
</html>
再来一个验证登录和发送验证码的接口就ok了。
@RestController
public class UserController {
SecureRandom secureRandom = new SecureRandom();
@GetMapping("/user")
public String username(){
return SecurityContextHolder.getContext().getAuthentication().getName();
}
/**
* 调用这个方法发送验证码
*/
@GetMapping("/sendSms")
public void sendSms(HttpSession httpSession,String phone){
//1000-9000的数字
int code= secureRandom.nextInt(9000)+ 1000;
System.out.println(code);
}
}
简单测试一下,用户密码可以登录,点击发送验证码,可以在后台打印出来,这样,前期的准备工作就OK了
验证验证码
在之前,我们看过用户名,密码验证是通过UsernamePasswordAuthenticationFilter过滤器来实现的,在这个过滤器里面,主要是UsernamePasswordAuthenticationToken来进行认证标识的。
那就好办了,我们模仿他,也写一个Sms的token。
我们知道,每一种认证方式,都有自己的token,短信验证码也不列外。
认证逻辑我们之前看过,登录是交给AuthenticationManager处理的,AuthenticationManager又交给AuthenticationProvider,而AuthenticationProvider则是ProviderManager统一管理的
每一个AuthenticationProvider都有一个supports方法,就例如用户名密码验证的DaoAuthenticationProvider,判断是不是UsernamePasswordAuthenticationToken这个token。(在它的父类里面实现的)
可以看到AbstractUserDetailsAuthenticationProvider这个抽象类是实现了AuthenticationProvider的一些接口,然后再交给DaoAuthenticationProvider,那我们也这样来。
先创建一个SmsAuthenticationToken
package org.tongzhou.sms_login.config;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.util.Assert;
import java.util.Collection;
public class SmsAuthenticationToken extends AbstractAuthenticationToken{
private static final long serialVersionUID = 620L;
private final Object principal;
private Object credentials;
public SmsAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public SmsAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
public static SmsAuthenticationToken unauthenticated(Object principal, Object credentials) {
return new SmsAuthenticationToken(principal, credentials);
}
public static SmsAuthenticationToken authenticated(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
return new SmsAuthenticationToken(principal, credentials, authorities);
}
public Object getCredentials() {
return this.credentials;
}
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
Assert.isTrue(!isAuthenticated, "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
super.setAuthenticated(false);
}
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}
有了token还不够,还要把他交给Provider进行管理,看一下DaoAuthenticationProvider这个怎么实现的,继承自一个抽象类,然后重写additionalAuthenticationChecks方法,那我们也这样弄。
先创建一个AbstractSmsAuthenticationProvider
package org.tongzhou.sms_login.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.util.Assert;
public abstract class AbstractSmsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
protected final Log logger = LogFactory.getLog(this.getClass());
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private UserCache userCache = new NullUserCache();
private boolean forcePrincipalAsString = false;
protected boolean hideUserNotFoundExceptions = true;
private UserDetailsChecker preAuthenticationChecks = new AbstractSmsAuthenticationProvider.DefaultPreAuthenticationChecks();
private UserDetailsChecker postAuthenticationChecks = new AbstractSmsAuthenticationProvider.DefaultPostAuthenticationChecks();
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
public AbstractSmsAuthenticationProvider() {
}
protected abstract void additionalAuthenticationChecks(UserDetails userDetails, SmsAuthenticationToken authentication) throws AuthenticationException;
public final void afterPropertiesSet() throws Exception {
Assert.notNull(this.userCache, "A user cache must be set");
Assert.notNull(this.messages, "A message source must be set");
this.doAfterPropertiesSet();
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(SmsAuthenticationToken.class, authentication, () -> {
return this.messages.getMessage("AbstractSmsAuthenticationProvider.onlySupports", "Only SmsAuthenticationToken is supported");
});
String username = this.determineUsername(authentication);
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = this.retrieveUser(username, (SmsAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("Failed to find user '" + username + "'");
if (!this.hideUserNotFoundExceptions) {
throw var6;
}
throw new BadCredentialsException(this.messages.getMessage("AbstractSmsAuthenticationProvider.badCredentials", "Bad credentials"));
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (SmsAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
}
cacheWasUsed = false;
user = this.retrieveUser(username, (SmsAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (SmsAuthenticationToken)authentication);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
private String determineUsername(Authentication authentication) {
return authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
}
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
SmsAuthenticationToken result = SmsAuthenticationToken.authenticated(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
this.logger.debug("Authenticated user");
return result;
}
protected void doAfterPropertiesSet() throws Exception {
}
public UserCache getUserCache() {
return this.userCache;
}
public boolean isForcePrincipalAsString() {
return this.forcePrincipalAsString;
}
public boolean isHideUserNotFoundExceptions() {
return this.hideUserNotFoundExceptions;
}
protected abstract UserDetails retrieveUser(String username, SmsAuthenticationToken authentication) throws AuthenticationException;
public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
this.forcePrincipalAsString = forcePrincipalAsString;
}
public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public void setUserCache(UserCache userCache) {
this.userCache = userCache;
}
public boolean supports(Class<?> authentication) {
return SmsAuthenticationToken.class.isAssignableFrom(authentication);
}
protected UserDetailsChecker getPreAuthenticationChecks() {
return this.preAuthenticationChecks;
}
public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) {
this.preAuthenticationChecks = preAuthenticationChecks;
}
protected UserDetailsChecker getPostAuthenticationChecks() {
return this.postAuthenticationChecks;
}
public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
this.postAuthenticationChecks = postAuthenticationChecks;
}
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
this.authoritiesMapper = authoritiesMapper;
}
private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
private DefaultPreAuthenticationChecks() {
}
public void check(UserDetails user) {
if (!user.isAccountNonLocked()) {
AbstractSmsAuthenticationProvider.this.logger.debug("Failed to authenticate since user account is locked");
throw new LockedException(AbstractSmsAuthenticationProvider.this.messages.getMessage("AbstractSmsAuthenticationProvider.locked", "User account is locked"));
} else if (!user.isEnabled()) {
AbstractSmsAuthenticationProvider.this.logger.debug("Failed to authenticate since user account is disabled");
throw new DisabledException(AbstractSmsAuthenticationProvider.this.messages.getMessage("AbstractSmsAuthenticationProvider.disabled", "User is disabled"));
} else if (!user.isAccountNonExpired()) {
AbstractSmsAuthenticationProvider.this.logger.debug("Failed to authenticate since user account has expired");
throw new AccountExpiredException(AbstractSmsAuthenticationProvider.this.messages.getMessage("AbstractSmsAuthenticationProvider.expired", "User account has expired"));
}
}
}
private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
private DefaultPostAuthenticationChecks() {
}
public void check(UserDetails user) {
if (!user.isCredentialsNonExpired()) {
AbstractSmsAuthenticationProvider.this.logger.debug("Failed to authenticate since user account credentials have expired");
throw new CredentialsExpiredException(AbstractSmsAuthenticationProvider.this.messages.getMessage("AbstractSmsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
}
}
}
}
然后,在创建它的继承类SmsAuthenticationProvider
package org.tongzhou.sms_login.config;
import jakarta.servlet.http.HttpSession;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.tongzhou.sms_login.service.UserService;
public class SmsAuthenticationProvider extends AbstractSmsAuthenticationProvider{
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
public SmsAuthenticationProvider() {
this(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
public SmsAuthenticationProvider(PasswordEncoder passwordEncoder) {
this.setPasswordEncoder(passwordEncoder);
}
/**
*
* 这里是做密码校验的,我们可以在这做验证码校验
* @param userDetails
* @param authentication
* @throws AuthenticationException
*/
protected void additionalAuthenticationChecks(UserDetails userDetails, SmsAuthenticationToken authentication) throws AuthenticationException {
//获取到session
HttpSession session = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession();
//拿到session中的验证码
String code = String.valueOf(session.getAttribute((String) authentication.getPrincipal()));
//用户输入的验证码
String credentials = (String) authentication.getCredentials();
//不相等,
if(!code.equalsIgnoreCase(credentials)){
throw new BadCredentialsException("验证码输入错误!");
}
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
// if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
// this.logger.debug("Failed to authenticate since password does not match stored value");
// throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
// }
}
}
protected void doAfterPropertiesSet() {
Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
}
/**
*
* 这里原本是根据用户名去查询的,修改下,让他根据手机号查询
* 用户登录的时候,手机号和验证码被封装成authentication对象
* @param username
* @param authentication
* @return
* @throws AuthenticationException
*/
protected final UserDetails retrieveUser(String username, SmsAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
UserDetails loadedUser = null;
try {
// UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
UserDetailsService us = this.getUserDetailsService();
if(us instanceof UserService userService){
loadedUser = userService.loadUserByPhone((String)authentication.getPrincipal());
}
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword());
//密码升级,这里不需要升级,先注释掉。
// if (upgradeEncoding) {
// String presentedPassword = authentication.getCredentials().toString();
// String newPassword = this.passwordEncoder.encode(presentedPassword);
// user = this.userDetailsPasswordService.updatePassword(user, newPassword);
// }
return super.createSuccessAuthentication(principal, authentication, user);
}
private void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode("userNotFoundPassword");
}
}
private void mitigateAgainstTimingAttack(SmsAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
protected PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
protected UserDetailsService getUserDetailsService() {
return this.userDetailsService;
}
public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
this.userDetailsPasswordService = userDetailsPasswordService;
}
}
最后,我们定义一个过滤器,把这个token放进去
public class SmsAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String phone = request.getParameter("phone");
if(phone != null && !"".equals(phone)){
SmsAuthenticationToken authenticationToken = SmsAuthenticationToken.unauthenticated(phone,request.getParameter("code"));
return this.getAuthenticationManager().authenticate(authenticationToken);
}
return super.attemptAuthentication(request, response);
}
}
最后,别忘了配置。
package org.tongzhou.sms_login.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.tongzhou.sms_login.service.UserService;
@Configuration
public class SecurityConfig {
@Autowired
UserService userService;
@Bean
SmsAuthenticationFilter smsAuthenticationFilter(){
SmsAuthenticationFilter smsAuthenticationFilter = new SmsAuthenticationFilter();
smsAuthenticationFilter.setFilterProcessesUrl("/login");
smsAuthenticationFilter.setAuthenticationSuccessHandler(((request, response, authentication) -> {
response.getWriter().write(authentication.getName());
}));
smsAuthenticationFilter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
smsAuthenticationFilter.setAuthenticationManager(authenticationManager());
return smsAuthenticationFilter;
}
@Bean
AuthenticationManager authenticationManager(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
SmsAuthenticationProvider smsAuthenticationProvider = new SmsAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userService);
smsAuthenticationProvider.setUserDetailsService(userService);
ProviderManager providerManager = new ProviderManager(daoAuthenticationProvider, smsAuthenticationProvider);
return providerManager;
}
@Bean
WebSecurityCustomizer webSecurityCustomizer(){
return web -> web.ignoring().requestMatchers("*.js");
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(a->a
.requestMatchers("/sendSms").permitAll()
.anyRequest().authenticated())
//这里注意,不配置就是默认的登陆地址login,但是如果配置了登录页面,则后面的登录地址也要配置
.formLogin(f->f.loginPage("/login.html").loginProcessingUrl("/login").permitAll())
.csrf(c->c.disable())
.addFilterBefore(smsAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
这样,就大功告成了,把请求页面和请求接口也放出来:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="jquery-3.7.1.min.js"></script>
</head>
<body>
<form action="/login" method="post">
<table>
<tr>
<td>用户名</td>
<td><input type="text" name="username" value="admin"></td>
</tr>
<tr>
<td>密码</td>
<td><input type="password" name="password" value="123"></td>
</tr>
<tr>
<td><input type="submit" name="登录"></td>
</tr>
</table>
</form>
<h1>验证码</h1>
<form action="/login" method="post">
<table>
<tr>
<td>手机号</td>
<td><input type="text" name="phone" value="" id="phone"></td>
</tr>
<tr>
<td>验证码</td>
<td><input type="text" name="code" value=""></td>
<td><button type="button" onclick="sendsmS()" name="">发送验证码</button></td>
</tr>
<tr>
<td><input type="submit" name="登录"></td>
</tr>
</table>
</form>
</body>
<script>
function sendsmS(){
let phone = $("#phone").val();
$.get("/sendSms",{phone:phone})
}
</script>
</html>
@RestController
public class UserController {
SecureRandom secureRandom = new SecureRandom();
@GetMapping("/user")
public String username(){
return SecurityContextHolder.getContext().getAuthentication().getName();
}
/**
* 调用这个方法发送验证码
*/
@GetMapping("/sendSms")
public void sendSms(HttpSession httpSession,String phone){
//1000-9000的数字
int code= secureRandom.nextInt(9000)+ 1000;
httpSession.setAttribute(phone,code);
System.out.println(code);
}
}
感兴趣的同学可以试一下,下一篇,我们来看看怎么基于阿里云真正的发短信到手机上进行验证。
结语
夜深人静,好思考!