Spring Security 实现手机验证码登录
思路:参考用户名密码登录过滤器链,重写认证和授权
示例如下(该篇示例以精简为主,演示主要实现功能,全面完整版会在以后的博文中发出):
由于涉及内容较多,建议先复制到本地工程中,然后在细细研究。
1. 新建Maven项目 sms-code-validate
2. pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>com.java</groupId>
<artifactId>sms-code-validate</artifactId>
<version>1.0.0</version> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent> <dependencies> <!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.0.0.RELEASE</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.11</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency> <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency> <!-- 热部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.8.RELEASE</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>provided</scope>
</dependency> </dependencies> <build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin> <plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
3. 启动类 SmsCodeStarter.java
package com.java; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; /**
* <blockquote><pre>
*
* 主启动类
*
* </pre></blockquote>
*
* @author Logan
*
*/
@SpringBootApplication
public class SmsCodeStarter { public static void main(String[] args) {
SpringApplication.run(SmsCodeStarter.class, args);
} }
4. ValidateCode.java
package com.java.validate.code; import java.time.LocalDateTime; /**
* 验证码封装类
*
* @author Logan
*
*/
public class ValidateCode { /**
* 验证码
*/
private String code; /**
* 过期时间
*/
private LocalDateTime expireTime; /**
* 指定验证码和有效分钟数的构造方法
*
* @param code 验证码
* @param validityMinutes 有效分钟数
*/
public ValidateCode(String code, int validityMinutes) {
this.code = code;
this.expireTime = LocalDateTime.now().plusMinutes(validityMinutes);
} /**
* 指定验证码和过期时间的构造方法
*
* @param code 验证码
* @param expireTime 过期时间
*/
public ValidateCode(String code, LocalDateTime expireTime) {
this.code = code;
this.expireTime = expireTime;
} public String getCode() {
return code;
} public LocalDateTime getExpireTime() {
return expireTime;
} }
5. CodeGenerator.java
package com.java.validate.generator; import org.apache.commons.lang3.RandomStringUtils; import com.java.validate.code.ValidateCode; /**
* 验证码生成器
*
* @author Logan
*
*/
public class CodeGenerator { /**
* 验证码生成方法
*
* @param length 验证码长度
* @param validityMinutes 过期分钟数
* @return
*/
public static ValidateCode generate(int length, int validityMinutes) {
String code = RandomStringUtils.randomNumeric(length);
return new ValidateCode(code, validityMinutes);
} }
6. ValidateCodeSender.java
package com.java.validate.sender; import java.io.IOException;
import java.io.PrintWriter; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; /**
* 验证码发送器
*
* @author Logan
*
*/
@Component
public class ValidateCodeSender { /**
* 模拟发送手机验证码,此处发回浏览器,实际情况根据短信服务商做调整
*
* @param response HTTP响应对象
* @param mobile 手机号
* @param code 验证码
*/
public void sendSmsCode(HttpServletResponse response, String mobile, String code) {
System.out.println(String.format("模拟向手机号【%s】发送验证码【%s】", mobile, code));
write(response, "验证码为:" + code);
} /**
* 发送HTTP响应信息
*
* @param response HTTP响应对象
* @param message 信息内容
*/
private void write(HttpServletResponse response, String message) {
response.setContentType("text/html; charset=UTF-8"); try (
PrintWriter writer = response.getWriter();
) {
writer.write(message);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
} }
7. ValidateCodeFilter.java
package com.java.validate.filter; import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.filter.OncePerRequestFilter; import com.java.controller.ValidateCodeController;
import com.java.validate.code.ValidateCode; /**
* 校验验证码过滤器
*
* @author Logan
* @createDate 2019-02-07
*
*/
@Component
public class ValidateCodeFilter extends OncePerRequestFilter { /**
* 需要校验短信验证码的请求
*/
private List<String> smsCodeUrls = new ArrayList<>(); @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { /**
* 如果需要校验短信验证码的请求集合中,包含当前请求,则进行短信验证码校验
*/
if (smsCodeUrls.contains(request.getRequestURI())) {
if (smsCodeValid(request, response)) { // 校验通过,继续向后执行
filterChain.doFilter(request, response);
} } // 其它请求,直接放过
else {
filterChain.doFilter(request, response);
} } @Override
protected void initFilterBean() throws ServletException { // 初始化添加需要校验的请求到集合中,可由配置文件中配置,此处为了简洁,直接添加
smsCodeUrls.add("/login/mobile");
} /**
* 短信验证码是否有效
*
* @param request HTTP请求对象
* @param response HTTP响应对象
* @return 有效,返回true;无效,返回false
* @throws ServletRequestBindingException
*/
private boolean smsCodeValid(HttpServletRequest request, HttpServletResponse response) throws ServletRequestBindingException {
String smsCode = ServletRequestUtils.getStringParameter(request, "smsCode");
ValidateCode validateCode = (ValidateCode) request.getSession().getAttribute(ValidateCodeController.SESSION_CODE_KEY);
if (StringUtils.isBlank(smsCode)) {
write(response, "验证码不能为空!");
return false;
} else if (null == validateCode) {
write(response, "验证码不存在!");
return false;
} else if (LocalDateTime.now().isAfter(validateCode.getExpireTime())) {
write(response, "验证码已过期!");
return false;
} else if (!StringUtils.equals(smsCode, validateCode.getCode())) {
write(response, "验证码不正确!");
return false;
} // 验证成功,移除Session中验证码
request.getSession().removeAttribute(ValidateCodeController.SESSION_CODE_KEY);
return true;
} /**
* 发送HTTP响应信息
*
* @param response HTTP响应对象
* @param message 信息内容
*/
private void write(HttpServletResponse response, String message) {
response.setContentType("text/html; charset=UTF-8"); try (
PrintWriter writer = response.getWriter();
) {
writer.write(message);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
} }
8. 自定义UserDetailsService实现类,具体逻辑根据实际情况调整。
SecurityUserDetailsService.java
package com.java.service; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component; /**
* UserDetailsService实现类
*
* @author Logan
*
*/
@Component
public class SecurityUserDetailsService implements UserDetailsService { @Autowired
private PasswordEncoder passwordEncoder; @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 数据库存储密码为加密后的密文(明文为123456)
String password = passwordEncoder.encode("123456"); System.out.println("username: " + username);
System.out.println("password: " + password); // 模拟查询数据库,获取属于Admin和Normal角色的用户
User user = new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("Admin,Normal")); return user;
} }
9. 获取主机信息接口,模拟演示功能需要
HostController.java
package com.java.controller; import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
public class HostController { @GetMapping("/getHostMessage")
public Map<String, Object> getHostMessage() {
Map<String, Object> map = new HashMap<>();
try {
InetAddress serverHost = InetAddress.getLocalHost();
map.put("hostname", serverHost.getHostName());
map.put("hostAddress", serverHost.getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
map.put("msg", e.getMessage());
} return map; } }
10. 验证码生成接口,可扩展集成图片验证码,后续博文中会发出。
ValidateCodeController.java
package com.java.controller; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import com.java.validate.code.ValidateCode;
import com.java.validate.generator.CodeGenerator;
import com.java.validate.sender.ValidateCodeSender; /**
* 创建验证码接口
*
* @author Logan
*
*/
@RestController
@RequestMapping("/code")
public class ValidateCodeController { /**
* 验证码存放Session中的key
*/
public static final String SESSION_CODE_KEY = "code"; /**
* 验证码长度,可以提取到配置中,此处只做演示,简单处理
*/
private int length = 6; /**
* 过期分钟数,可以提取到配置中,此处只做演示,简单处理
*/
private int validityMinutes = 30; /**
* 验证码发送器
*/
@Autowired
private ValidateCodeSender validateCodeSender; /**
* 创建短信验证码接口
*
* @param request 请求对象
* @param response 响应对象
* @param mobile 手机号
*/
@GetMapping("/sms")
public void createSmsCode(HttpServletRequest request, HttpServletResponse response, String mobile) {
ValidateCode validateCode = CodeGenerator.generate(length, validityMinutes); // 存储验证码到Session中,登录时验证
request.getSession().setAttribute(SESSION_CODE_KEY, validateCode); // 调用验证码发送器发送短信验证码
validateCodeSender.sendSmsCode(response, mobile, validateCode.getCode());
} }
11. AuthenticationSuccessHandler.java
package com.java.authentication.handler; import java.io.IOException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component; /**
* 授权成功处理器
*
* @author Logan
* @createDate 2019-02-07
*
*/
@Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { // 设置默认跳转页面,当没有重定向页面时(例如:直接访问登录页面),此配置生效
super.setDefaultTargetUrl("/main.html");
super.onAuthenticationSuccess(request, response, authentication);
} }
12. SmsCodeAuthenticationToken.java
package com.java.authentication.mobile; import java.util.Collection; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion; /**
* <pre>
*
* 短信验证码Token,封装短信验证码登录信息。
*
* 参照{@link org.springframework.security.authentication.UsernamePasswordAuthenticationToken}
*
* </pre>
*
* @author Logan
*
*/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; // ~ Instance fields
// ================================================================================================ private final Object principal; // ~ Constructors
// =================================================================================================== /**
* This constructor can be safely used by any code that wishes to create a
* <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()}
* will return <code>false</code>.
*
*/
public SmsCodeAuthenticationToken(String mobile) {
super(null);
this.principal = mobile;
super.setAuthenticated(false);
} /**
* This constructor should only be used by <code>AuthenticationManager</code> or
* <code>AuthenticationProvider</code> implementations that are satisfied with
* producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
* authentication token.
*
* @param mobile
* @param authorities
*/
public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true); // must use super, as we override
} // ~ Methods
// ======================================================================================================== public Object getCredentials() {
return null;
} public Object getPrincipal() {
return this.principal;
} }
13. SmsCodeAuthenticationFilter.java
package com.java.authentication.mobile; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationServiceException;
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 org.springframework.util.Assert; /**
* <pre>
*
* 短信验证码过滤器,
*
* 参照{@link org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter}
*
* </pre>
*
* @author Logan
*
*/
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter { // ~ Static fields/initializers
// ===================================================================================== public static final String MOBILE_KEY = "mobile"; private String mobileParameter = MOBILE_KEY;
private boolean postOnly = true; // ~ Constructors
// =================================================================================================== public SmsCodeAuthenticationFilter() {
super(new AntPathRequestMatcher("/login/mobile", "POST"));
} // ~ Methods
// ======================================================================================================== public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} String mobile = StringUtils.trimToEmpty(request.getParameter(mobileParameter));
SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile); // Allow subclasses to set the "details" property
setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest);
} /**
* Provided so that subclasses may configure what is put into the
* authentication request's details property.
*
* @param request that an authentication request is being created for
* @param authRequest the authentication request object that should have its
* details set
*/
protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
} /**
* Sets the parameter name which will be used to obtain the mobile from the
* login request.
*
* @param mobileParameter the parameter name. Defaults to "mobile".
*/
public void setMobileParameter(String mobileParameter) {
Assert.hasText(mobileParameter, "mobile parameter must not be empty or null");
this.mobileParameter = mobileParameter;
} /**
* Defines whether only HTTP POST requests will be allowed by this filter.
* If set to true, and an authentication request is received which is not a
* POST request, an exception will be raised immediately and authentication
* will not be attempted. The <tt>unsuccessfulAuthentication()</tt> method
* will be called as if handling a failed authentication.
* <p>
* Defaults to <tt>true</tt> but may be overridden by subclasses.
*/
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
} public final String getMobileParameter() {
return mobileParameter;
}
}
14. SmsCodeAuthenticationProvider.java
package com.java.authentication.mobile; import org.springframework.security.authentication.AuthenticationProvider;
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.UserDetailsService; /**
* 短信验证码授权认证类
*
* @author Logan
* @createDate 2019-02-07
*
*/
public class SmsCodeAuthenticationProvider implements AuthenticationProvider { private UserDetailsService userDetailsService; @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 未认证Token
SmsCodeAuthenticationToken token = (SmsCodeAuthenticationToken) authentication;
UserDetails user = userDetailsService.loadUserByUsername((String) token.getPrincipal()); if (null == user) {
throw new InternalAuthenticationServiceException("未绑定用户!");
} // 已认证的Token
SmsCodeAuthenticationToken authenticationToken = new SmsCodeAuthenticationToken(user, user.getAuthorities()); // 复制之前的请求信息到认证后的Token中
authenticationToken.setDetails(token.getDetails()); return authenticationToken;
} @Override
public boolean supports(Class<?> authentication) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
} public UserDetailsService getUserDetailsService() {
return userDetailsService;
} public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
} }
15. ApplicationContextConfig.java
package com.java.config; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; /**
* 配置文件类
*
* @author Logan
*
*/
@Configuration
public class ApplicationContextConfig { /**
* <blockquote><pre>
*
* 配置密码编码器,Spring Security 5.X必须配置,否则登录时报空指针异常
*
* </pre></blockquote>
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} }
16. RepositoryConfig.java
package com.java.config; import javax.sql.DataSource; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; /**
* 数据库相关配置
*
* @author Logan
*
*/
@Configuration
public class RepositoryConfig { @Bean
public PersistentTokenRepository tokenRepository(DataSource dataSource) {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
// tokenRepository.setCreateTableOnStartup(true); // 第一次启动时可使用此功能自动创建表,第二次要关闭,否则表已存在会启动报错
return tokenRepository;
} }
17. SmsCodeSecurityConfig.java
package com.java.config; 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 com.java.authentication.handler.AuthenticationSuccessHandler;
import com.java.authentication.mobile.SmsCodeAuthenticationFilter;
import com.java.authentication.mobile.SmsCodeAuthenticationProvider; /**
* 短信验证码安全配置,串联自定义短信验证码验证流程
*
* @author Logan
* @createDate 2019-02-07
*
*/
@Component
public class SmsCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { @Autowired
private UserDetailsService userDetailsService; @Autowired
private AuthenticationSuccessHandler successHandler; @Override
public void configure(HttpSecurity http) throws Exception { // 此处采用new的方式,而不是@Component和@Autowired结合,目的为了方便安装和卸载,可重用可移植性强
SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter(); // 设置AuthenticationManager
smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(successHandler); // 短信验证码认证Provider类
SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService); // 设置短信验证码认证Provider类到AuthenticationManager管理集合中
http.authenticationProvider(smsCodeAuthenticationProvider) // 设置短信验证码在用户名密码验证过滤器之后验证
.addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } }
18. LoginConfig.java
package com.java.config; 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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import com.java.validate.filter.ValidateCodeFilter; /**
* 登录相关配置
*
* @author Logan
*
*/
@Configuration
public class LoginConfig extends WebSecurityConfigurerAdapter { @Autowired
private PersistentTokenRepository tokenRepository; @Autowired
private UserDetailsService userDetailsService; @Autowired
private ValidateCodeFilter validateCodeFilter; @Autowired
private SmsCodeSecurityConfig smsCodeSecurityConfig; @Override
protected void configure(HttpSecurity http) throws Exception { http.apply(smsCodeSecurityConfig) // 设置验证码过滤器到过滤器链中,在UsernamePasswordAuthenticationFilter之前执行
.and().addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) // 设置自定义表单登录页面
.formLogin().loginPage("/login.html") // 设置登录验证请求地址为自定义登录页配置action ("/login/form")
.loginProcessingUrl("/login/form") // 设置默认登录成功跳转页面
.defaultSuccessUrl("/main.html") /* 授权请求设置 */
.and().authorizeRequests() // 设置不需要授权的请求
.antMatchers("/js/*", "/code/*", "/login.html").permitAll() // 其它任何请求都需要验证权限
.anyRequest().authenticated() /* 记住我功能设置 */
.and().rememberMe().tokenRepository(tokenRepository) // 【记住我功能】有效期为两周
.tokenValiditySeconds(3600 * 24 * 14) // 设置UserDetailsService
.userDetailsService(userDetailsService) // 暂时停用csrf,否则会影响验证
.and().csrf().disable();
} }
19. src/main/resources 下配置文件如下
20. application.properties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.32.10:3306/security?useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
21. login.html
<!DOCTYPE html>
<html> <head>
<title>登录</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
<script>
function sendSmsCode() {
var mobile = $("#mobile").val().trim(); // 简单校验由11位数字组成
var reg = /^\d{11}$/;
if(reg.test(mobile)) {
$.ajax({
type: "get",
url: "/code/sms?mobile=" + mobile,
async: true,
success: function(data) {
alert(data);
}
});
} else {
alert("手机号输入格式错误");
}
}
</script>
</head> <body> <!--登录框-->
<div align="center">
<h2>用户自定义登录页面</h2>
<fieldset style="width: 390px;">
<legend>表单登录框</legend>
<form action="/login/form" method="post">
<table>
<tr>
<th>用户名:</th>
<td><input name="username" /> </td>
</tr>
<tr>
<th>密码:</th>
<td><input type="password" name="password" /> </td>
</tr>
<tr>
<th>记住我:</th>
<td><input type="checkbox" name="remember-me" value="true" checked="checked" /></td>
</tr>
<tr>
<th></th>
<td></td>
</tr>
<tr>
<td colspan="2" align="center"><button type="submit">登录</button></td>
</tr>
</table>
</form>
</fieldset>
<fieldset style="width: 390px;margin-top: 30px;">
<legend>手机验证码登录框</legend>
<form action="/login/mobile" method="post">
<table>
<tr>
<th>手机号:</th>
<td><input id="mobile" name="mobile" value="13166668888" /></td>
</tr>
<tr>
<th>验证码:</th>
<td>
<input id="smsCode" name="smsCode" />
<button type="button" onclick="sendSmsCode()">发送手机验证码</button>
</td>
</tr>
<tr>
<td colspan="2" align="center"><button type="submit">登录</button></td>
</tr>
</table>
</form>
</fieldset> </div> </body> </html>
22. main.html
<!DOCTYPE html>
<html> <head>
<title>首页</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
<script>
function getHostMessage() {
$.ajax({
type: "get",
url: "/getHostMessage",
async: true,
success: function(data) {
$("#msg").val(JSON.stringify(data));
}
});
}
</script>
</head> <body> <div>
<h2>首页</h2>
<table>
<tr>
<td><button onclick="getHostMessage()">获取主机信息</button></td>
</tr>
</table> </div> <!--响应内容-->
<div>
<textarea id="msg" style="width: 800px;height: 800px;"></textarea>
</div> </body> </html>
23. js/jquery-3.3.1.min.js 可在官网下载
https://code.jquery.com/jquery-3.3.1.min.js
24. 创建数据库
DROP DATABASE IF EXISTS security;
CREATE DATABASE security;
USE security;
create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null
);
25. 运行 SmsCodeStarter.java , 启动测试
浏览器输入首页 http://localhost:8080/main.html
地址栏自动跳转到登录页面,如下:
表单登录可自行研究,只讲解手机验证码登录过程
单击【发送手机验证码】按钮,控制台和浏览器都会显示生成验证码。
输入正确的手机验证码,单击【登录】按钮。跳转到首页,如下所示:
获取主机信息接口功能调用正常。
验证码输入错误情况可自习研究。
搭建完成!
.
Spring Security 实现手机验证码登录的更多相关文章
- Spring Security 一键接入验证码登录和小程序登录
最近实现了一个多端登录的Spring Security组件,用起来非常丝滑,开箱即用,可插拔,而且灵活性非常强.我觉得能满足大部分场景的需要.目前完成了手机号验证码和微信小程序两种自定义登录,加上默认 ...
- SpringCloud微服务实战——搭建企业级开发框架(四十):使用Spring Security OAuth2实现单点登录(SSO)系统
一.单点登录SSO介绍 目前每家企业或者平台都存在不止一套系统,由于历史原因每套系统采购于不同厂商,所以系统间都是相互独立的,都有自己的用户鉴权认证体系,当用户进行登录系统时,不得不记住每套系统的 ...
- Spring Security 入门(1-3-1)Spring Security - http元素 - 默认登录和登录定制
登录表单配置 - http 元素下的 form-login 元素是用来定义表单登录信息的.当我们什么属性都不指定的时候 Spring Security 会为我们生成一个默认的登录页面. 如果不想使用默 ...
- Spring Security默认的用户登录表单 页面源代码
Spring Security默认的用户登录表单 页面源代码 <html><head><title>Login Page</title></hea ...
- Spring Security OAuth2 SSO 单点登录
基于 Spring Security OAuth2 SSO 单点登录系统 SSO简介 单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自 ...
- Spring Security之多次登录失败后账户锁定功能的实现
在上一次写的文章中,为大家说到了如何动态的从数据库加载用户.角色.权限信息,从而实现登录验证及授权.在实际的开发过程中,我们通常会有这样的一个需求:当用户多次登录失败的时候,我们应该将账户锁定,等待一 ...
- spring security实现记录用户登录时间等信息
目录 spring security实现记录用户登录时间等信息 一.原理分析 二.实现方式 2.1 自定义AuthenticationSuccessHandler实现类 2.2 在spring-sec ...
- RabbitMQ+Redis模拟手机验证码登录
RabbitMQ+Redis模拟手机验证码登录 依赖 <dependency> <groupId>org.springframework.boot</groupId> ...
- 七:Spring Security 前后端分离登录,非法请求直接返回 JSON
Spring Security 前后端分离登录,非法请求直接返回 JSON 解决方案 在 Spring Security 中未获认证的请求默认会重定向到登录页,但是在前后端分离的登录中,这个默认行为则 ...
随机推荐
- 《mac的git安装手册-1》
<mac的git安装手册-1> 下载地址 https://git-scm.com/downloads 如果遇到上面这个问题打开系统偏好设置: OK,这样就能安装了
- 安装NetCDF及HDF5
平台信息 Description: CentOS Linux release 7.6.1810 (Core) 安装步骤 下载NetCDF.HDF5.zlib.curl[使用wget命令即可] 解包:t ...
- mysql初期使用全本
mysql mysql前戏 数据库服务器-:运行数据库管理软件 =>pc 数据库管理软件:管理-数据库 => mysql 数据库:用来组织文件/表 => 文件夹 表:用来存放多行内容 ...
- [转]使用Node.js完成的第一个项目的实践总结
本文转自:http://blog.csdn.net/yanghua_kobe/article/details/17199417 https://github.com/yanghua/FixedAsse ...
- (转)ping命令
ping命令 原文:https://www.cnblogs.com/peida/archive/2013/03/06/2945407.html Linux系统的ping命令是常用的网络命令,它通常用来 ...
- C++(SOCKET)简单爬虫制作
先给出代码:(我使用的是VS编译器,需要在项目->project属性-> #include <iostream> #include <stdio.h> #inclu ...
- 获得数据库image图片二进制
/// <summary> /// 获得图片二进制 /// </summary> /// <param name="u ...
- 北航oo作业第一单元小结
前言 在经过了三次艰辛的oo作业后,oo课程的第一单元告一段落,这一单元,我作为一个oo小白,开始了解oo的编程思想,也有了自己的一点心得体会.把笔粗成字,不当之处,还请各位大佬多多指教. 一.分析程 ...
- hdu 4044 树形DP 炮台打怪 (好题)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4044 题目大意:给定n个节点组成的树,1为敌方基地,叶子结点为我方结点.我们可以在每个结点安放炮台,至 ...
- eclipse 构建从 SVN 上下载的可识别的 maven 项目
从 SVN 上下载的 maven 项目中含有父项目,属于 maven 的嵌套,每个子项目和父项目虽有 pom.xml 文件,在结构上也是 maven 然而并不是 eclipse 识别的 maven 项 ...