思路:参考用户名密码登录过滤器链,重写认证和授权
示例如下(该篇示例以精简为主,演示主要实现功能,全面完整版会在以后的博文中发出):
由于涉及内容较多,建议先复制到本地工程中,然后在细细研究。
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
地址栏自动跳转到登录页面,如下:
表单登录可自行研究,只讲解手机验证码登录过程
单击【发送手机验证码】按钮,控制台和浏览器都会显示生成验证码。
输入正确的手机验证码,单击【登录】按钮。跳转到首页,如下所示:
获取主机信息接口功能调用正常。
验证码输入错误情况可自习研究。
搭建完成!
.