思路:参考用户名密码登录过滤器链,重写认证和授权

示例如下(该篇示例以精简为主,演示主要实现功能,全面完整版会在以后的博文中发出):

由于涉及内容较多,建议先复制到本地工程中,然后在细细研究。

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 实现手机验证码登录的更多相关文章

  1. Spring Security 一键接入验证码登录和小程序登录

    最近实现了一个多端登录的Spring Security组件,用起来非常丝滑,开箱即用,可插拔,而且灵活性非常强.我觉得能满足大部分场景的需要.目前完成了手机号验证码和微信小程序两种自定义登录,加上默认 ...

  2. SpringCloud微服务实战——搭建企业级开发框架(四十):使用Spring Security OAuth2实现单点登录(SSO)系统

    一.单点登录SSO介绍   目前每家企业或者平台都存在不止一套系统,由于历史原因每套系统采购于不同厂商,所以系统间都是相互独立的,都有自己的用户鉴权认证体系,当用户进行登录系统时,不得不记住每套系统的 ...

  3. Spring Security 入门(1-3-1)Spring Security - http元素 - 默认登录和登录定制

    登录表单配置 - http 元素下的 form-login 元素是用来定义表单登录信息的.当我们什么属性都不指定的时候 Spring Security 会为我们生成一个默认的登录页面. 如果不想使用默 ...

  4. Spring Security默认的用户登录表单 页面源代码

    Spring Security默认的用户登录表单 页面源代码 <html><head><title>Login Page</title></hea ...

  5. Spring Security OAuth2 SSO 单点登录

    基于 Spring Security OAuth2 SSO 单点登录系统 SSO简介 单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自 ...

  6. Spring Security之多次登录失败后账户锁定功能的实现

    在上一次写的文章中,为大家说到了如何动态的从数据库加载用户.角色.权限信息,从而实现登录验证及授权.在实际的开发过程中,我们通常会有这样的一个需求:当用户多次登录失败的时候,我们应该将账户锁定,等待一 ...

  7. spring security实现记录用户登录时间等信息

    目录 spring security实现记录用户登录时间等信息 一.原理分析 二.实现方式 2.1 自定义AuthenticationSuccessHandler实现类 2.2 在spring-sec ...

  8. RabbitMQ+Redis模拟手机验证码登录

    RabbitMQ+Redis模拟手机验证码登录 依赖 <dependency> <groupId>org.springframework.boot</groupId> ...

  9. 七:Spring Security 前后端分离登录,非法请求直接返回 JSON

    Spring Security 前后端分离登录,非法请求直接返回 JSON 解决方案 在 Spring Security 中未获认证的请求默认会重定向到登录页,但是在前后端分离的登录中,这个默认行为则 ...

随机推荐

  1. 《mac的git安装手册-1》

    <mac的git安装手册-1> 下载地址 https://git-scm.com/downloads 如果遇到上面这个问题打开系统偏好设置: OK,这样就能安装了

  2. 安装NetCDF及HDF5

    平台信息 Description: CentOS Linux release 7.6.1810 (Core) 安装步骤 下载NetCDF.HDF5.zlib.curl[使用wget命令即可] 解包:t ...

  3. mysql初期使用全本

    mysql mysql前戏 数据库服务器-:运行数据库管理软件 =>pc 数据库管理软件:管理-数据库 => mysql 数据库:用来组织文件/表 => 文件夹 表:用来存放多行内容 ...

  4. [转]使用Node.js完成的第一个项目的实践总结

    本文转自:http://blog.csdn.net/yanghua_kobe/article/details/17199417 https://github.com/yanghua/FixedAsse ...

  5. (转)ping命令

    ping命令 原文:https://www.cnblogs.com/peida/archive/2013/03/06/2945407.html Linux系统的ping命令是常用的网络命令,它通常用来 ...

  6. C++(SOCKET)简单爬虫制作

    先给出代码:(我使用的是VS编译器,需要在项目->project属性-> #include <iostream> #include <stdio.h> #inclu ...

  7. 获得数据库image图片二进制

    /// <summary>        /// 获得图片二进制        /// </summary>        /// <param name="u ...

  8. 北航oo作业第一单元小结

    前言 在经过了三次艰辛的oo作业后,oo课程的第一单元告一段落,这一单元,我作为一个oo小白,开始了解oo的编程思想,也有了自己的一点心得体会.把笔粗成字,不当之处,还请各位大佬多多指教. 一.分析程 ...

  9. hdu 4044 树形DP 炮台打怪 (好题)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4044 题目大意:给定n个节点组成的树,1为敌方基地,叶子结点为我方结点.我们可以在每个结点安放炮台,至 ...

  10. eclipse 构建从 SVN 上下载的可识别的 maven 项目

    从 SVN 上下载的 maven 项目中含有父项目,属于 maven 的嵌套,每个子项目和父项目虽有 pom.xml 文件,在结构上也是 maven 然而并不是 eclipse 识别的 maven 项 ...