本文在前文 Spring Security 入门(一):认证和原理分析 的基础上介绍图形验证码和手机短信验证码登录的实现。
图形验证码
在用户登录时,一般通过表单的方式进行登录都会要求用户输入验证码,Spring Security默认没有实现图形验证码的功能,所以需要我们自己实现。
实现流程分析
前文中实现的用户名、密码登录是在UsernamePasswordAuthenticationFilter过滤器进行认证的,而图形验证码一般是在用户名、密码认证之前进行验证的,所以需要在UsernamePasswordAuthenticationFilter过滤器之前添加一个自定义过滤器 ImageCodeValidateFilter,用来校验用户输入的图形验证码是否正确。自定义过滤器继承 OncePerRequestFilter 类,该类是 Spring 提供的在一次请求中只会调用一次的 filter。
自定义的过滤器 ImageCodeValidateFilter 首先会判断请求是否为 POST 方式的登录表单提交请求,如果是就将其拦截进行图形验证码校验。如果验证错误,会抛出自定义异常类对象 ValidateCodeException,该异常类需要继承 AuthenticationException 类。在自定义过滤器中,我们需要手动捕获自定义异常类对象,并将捕获到自定义异常类对象交给自定义失败处理器进行处理。
kpatcha 使用
Kaptcha 是谷歌提供的生成图形验证码的工具,参考地址为:https://github.com/penggle/kaptcha,依赖如下:
<!-- 图形验证码工具 kaptcha -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
☕ 创建 KaptchaConfig 配置类
package com.example.config;
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
* 图形验证码的配置类
*/
@Configuration
public class KaptchaConfig {
@Bean
public DefaultKaptcha captchaProducer() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框
properties.setProperty(Constants.KAPTCHA_BORDER, "yes");
// 边框颜色
properties.setProperty(Constants.KAPTCHA_BORDER_COLOR, "192,192,192");
// 验证码图片的宽和高
properties.setProperty(Constants.KAPTCHA_IMAGE_WIDTH, "110");
properties.setProperty(Constants.KAPTCHA_IMAGE_HEIGHT, "40");
// 验证码颜色
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "0,0,0");
// 验证码字体大小
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "32");
// 验证码生成几个字符
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
// 验证码随机字符库
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYAZ");
// 验证码图片默认是有线条干扰的,我们设置成没有干扰
properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
kaptcha 配置的参数说明(定义在 Constants 常量类中):
-
kaptcha.border:是否有图片边框,合法值:yes,no;默认值为 yes。 -
kaptcha.border.color:边框颜色,合法值:rgb 或者 white,black,blue;默认值为 black。 -
kaptcha.border.thickness边框厚度,合法值:>0;默认为 1。 -
kaptcha.image.width:图片宽,默认值为 200px。 -
kaptcha.image.height:图片高,默认值为 50px。 -
kaptcha.producer.impl:图片实现类,默认值为com.google.code.kaptcha.impl.DefaultKaptcha。 -
kaptcha.textproducer.impl:文本实现类,默认值为com.google.code.kaptcha.text.impl.DefaultTextCreator。 -
kaptcha.textproducer.char.string:文本集合,验证码值从此集合中获取,默认值为abcde2345678gfynmnpwx。 -
kaptcha.textproducer.char.length:验证码长度,默认值为 5。 -
kaptcha.textproducer.font.names:字体,默认值为 Arial, Courier。 -
kaptcha.textproducer.font.size:字体大小,默认值为 40px。 -
kaptcha.textproducer.font.color:字体颜色,合法值:rgb 或者 white,black,blue;默认值为black。 -
kaptcha.textproducer.char.space:文字间隔,默认值为 2px。 -
kaptcha.noise.impl:干扰实现类,com.google.code.kaptcha.impl.NoNoise为没有干扰。默认值为com.google.code.kaptcha.impl.DefaultNoise。 -
kaptcha.noise.color:干扰线颜色,合法值:rgb 或者 white,black,blue;默认值为 black。 -
kaptcha.obscurificator.impl:图片样式,合法值:水纹com.google.code.kaptcha.impl.WaterRipple,鱼眼com.google.code.kaptcha.impl.FishEyeGimpy, 阴影com.google.code.kaptcha.impl.ShadowGimpy;默认值为com.google.code.kaptcha.impl.WaterRipple。 -
kaptcha.background.impl:背景实现类,默认值为com.google.code.kaptcha.impl.DefaultBackground。 -
kaptcha.background.clear.from:背景颜色渐变,开始颜色,默认值为light grey。 -
kaptcha.background.clear.to:背景颜色渐变, 结束颜色,默认值为 white。 -
kaptcha.word.impl:文字渲染器 实现类,默认值为com.google.code.kaptcha.text.impl.DefaultWordRenderer。 -
kaptcha.session.key:session key,默认值为KAPTCHA_SESSION_KEY。 -
kaptcha.session.date:session date,默认值为KAPTCHA_SESSION_DATE。
☕ 创建验证码的实体类 CheckCode
package com.example.entity;
import java.io.Serializable;
import java.time.LocalDateTime;
public class CheckCode implements Serializable {
private String code; // 验证码字符
private LocalDateTime expireTime; // 过期时间
/**
* @param code 验证码字符
* @param expireTime 过期时间,单位秒
*/
public CheckCode(String code, int expireTime) {
this.code = code;
this.expireTime = LocalDateTime.now().plusSeconds(expireTime);
}
public CheckCode(String code) {
// 默认验证码 60 秒后过期
this(code, 60);
}
// 是否过期
public boolean isExpried() {
return this.expireTime.isBefore(LocalDateTime.now());
}
public String getCode() {
return this.code;
}
}
☕ 在 LoginController 中添加获取图形验证码的 Controller 方法
package com.example.constans;
public class Constants {
// Session 中存储图形验证码的属性名
public static final String KAPTCHA_SESSION_KEY = "KAPTCHA_SESSION_KEY";
}
@Controller
public class LoginController {
@Autowired
private DefaultKaptcha defaultKaptcha;
//...
@GetMapping("/code/image")
public void imageCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 创建验证码文本
String capText = defaultKaptcha.createText();
// 创建验证码图片
BufferedImage image = defaultKaptcha.createImage(capText);
// 将验证码文本放进 Session 中
CheckCode code = new CheckCode(capText);
request.getSession().setAttribute(Constants.KAPTCHA_SESSION_KEY, code);
// 将验证码图片返回,禁止验证码图片缓存
response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
ImageIO.write(image, "jpg", response.getOutputStream());
}
}
☕ 在 login.html 中添加验证码功能
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h3>表单登录</h3>
<form method="post" th:action="@{/login/form}">
<input type="text" name="name" placeholder="用户名"><br>
<input type="password" name="pwd" placeholder="密码"><br>
<input name="imageCode" type="text" placeholder="验证码"><br>
<img th:onclick="this.src=\'/code/image?\'+Math.random()" th:src="@{/code/image}" alt="验证码"/><br>
<div th:if="${param.error}">
<span th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}" style="color:red">用户名或密码错误</span>
</div>
<button type="submit">登录</button>
</form>
</body>
</html>
☕ 更改安全配置类 SpringSecurityConfig,设置访问/code/image不需要任何权限
@EnableWebSecurity // 开启 MVC Security 安全配置
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
//...
/**
* 定制基于 HTTP 请求的用户访问控制
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//...
// 开启基于 HTTP 请求访问控制
http.authorizeRequests()
// 以下访问不需要任何权限,任何人都可以访问
.antMatchers("/login/page", "/code/image").permitAll()
// 以下访问需要 ROLE_ADMIN 权限
.antMatchers("/admin/**").hasRole("ADMIN")
// 以下访问需要 ROLE_USER 权限
.antMatchers("/user/**").hasAuthority("ROLE_USER")
// 其它任何请求访问都需要先通过认证
.anyRequest().authenticated();
//...
}
//...
}
☕ 测试
访问localhost:8080/login/page,出现图形验证的信息
自定义验证码过滤器
⭐️ 创建自定义异常类 ValidateCodeException
package com.example.exception;
import org.springframework.security.core.AuthenticationException;
/**
* 自定义验证码校验错误的异常类,继承 AuthenticationException
*/
public class ValidateCodeException extends AuthenticationException {
public ValidateCodeException(String msg, Throwable t) {
super(msg, t);
}
public ValidateCodeException(String msg) {
super(msg);
}
}
⭐ 自定义图形验证码校验过滤器 ImageCodeValidateFilter
package com.example.config.security;
import com.example.constans.Constants;
import com.example.entity.CheckCode;
import com.example.exception.ValidateCodeException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@Component
public class ImageCodeValidateFilter extends OncePerRequestFilter {
private String codeParamter = "imageCode"; // 前端输入的图形验证码参数名
@Autowired
private CustomAuthenticationFailureHandler authenticationFailureHandler; // 自定义认证失败处理器
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 非 POST 方式的表单提交请求不校验图形验证码
if ("/login/form".equals(request.getRequestURI()) && "POST".equals(request.getMethod())) {
try {
// 校验图形验证码合法性
validate(request);
} catch (ValidateCodeException e) {
// 手动捕获图形验证码校验过程抛出的异常,将其传给失败处理器进行处理
authenticationFailureHandler.onAuthenticationFailure(request, response, e);
return;
}
}
// 放行请求,进入下一个过滤器
filterChain.doFilter(request, response);
}
// 判断验证码的合法性
private void validate(HttpServletRequest request) {
// 获取用户传入的图形验证码值
String requestCode = request.getParameter(this.codeParamter);
if(requestCode == null) {
requestCode = "";
}
requestCode = requestCode.trim();
// 获取 Session
HttpSession session = request.getSession();
// 获取存储在 Session 里的验证码值
CheckCode savedCode = (CheckCode) session.getAttribute(Constants.KAPTCHA_SESSION_KEY);
if (savedCode != null) {
// 随手清除验证码,无论是失败,还是成功。客户端应在登录失败时刷新验证码
session.removeAttribute(Constants.KAPTCHA_SESSION_KEY);
}
// 校验出错,抛出异常
if (StringUtils.isBlank(requestCode)) {
throw new ValidateCodeException("验证码的值不能为空");
}
if (savedCode == null) {
throw new ValidateCodeException("验证码不存在");
}
if (savedCode.isExpried()) {
throw new ValidateCodeException("验证码过期");
}
if (!requestCode.equalsIgnoreCase(savedCode.getCode())) {
throw new ValidateCodeException("验证码输入错误");
}
}
}
⭐️ 更改安全配置类 SpringSecurityConfig,将自定义过滤器添加过滤器链中
@EnableWebSecurity // 开启 MVC Security 安全配置
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
//...
@Autowired
private ImageCodeValidateFilter imageCodeValidateFilter; // 自定义过滤器(图形验证码校验)
//...
/**
* 定制基于 HTTP 请求的用户访问控制
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//...
// 将自定义过滤器(图形验证码校验)添加到 UsernamePasswordAuthenticationFilter 之前
http.addFilterBefore(imageCodeValidateFilter, UsernamePasswordAuthenticationFilter.class);
}
//...
}
完整的安全配置类 SpringSecurityConfig 如下:
package com.example.config;
import com.example.config.security.CustomAuthenticationFailureHandler;
import com.example.config.security.CustomAuthenticationSuccessHandler;
import com.example.config.security.ImageCodeValidateFilter;
import com.example.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@EnableWebSecurity // 开启 MVC Security 安全配置
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private CustomAuthenticationSuccessHandler authenticationSuccessHandler; // 自定义认证成功处理器
@Autowired
private CustomAuthenticationFailureHandler authenticationFailureHandler; // 自定义认证失败处理器
@Autowired
private ImageCodeValidateFilter imageCodeValidateFilter; // 自定义过滤器(图形验证码校验)
/**
* 密码编码器,密码不能明文存储
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
// 使用 BCryptPasswordEncoder 密码编码器,该编码器会将随机产生的 salt 混入最终生成的密文中
return new BCryptPasswordEncoder();
}
/**
* 定制用户认证管理器来实现用户认证
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 采用内存存储方式,用户认证信息存储在内存中
// auth.inMemoryAuthentication()
// .withUser("admin").password(passwordEncoder()
// .encode("123456")).roles("ROLE_ADMIN");
// 不再使用内存方式存储用户认证信息,而是动态从数据库中获取
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
/**
* 定制基于 HTTP 请求的用户访问控制
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 启动 form 表单登录
http.formLogin()
// 设置登录页面的访问路径,默认为 /login,GET 请求;该路径不设限访问
.loginPage("/login/page")
// 设置登录表单提交路径,默认为 loginPage() 设置的路径,POST 请求
.loginProcessingUrl("/login/form")
// 设置登录表单中的用户名参数,默认为 username
.usernameParameter("name")
// 设置登录表单中的密码参数,默认为 password
.passwordParameter("pwd")
// 认证成功处理,如果存在原始访问路径,则重定向到该路径;如果没有,则重定向 /index
//.defaultSuccessUrl("/index")
// 认证失败处理,重定向到指定地址,默认为 loginPage() + ?error;该路径不设限访问
//.failureUrl("/login/page?error");
// 不再使用 defaultSuccessUrl() 和 failureUrl() 方法进行认证成功和失败处理,
// 使用自定义的认证成功和失败处理器
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailureHandler);
// 开启基于 HTTP 请求访问控制
http.authorizeRequests()
// 以下访问不需要任何权限,任何人都可以访问
.antMatchers("/login/page", "/code/image").permitAll()
// 以下访问需要 ROLE_ADMIN 权限
.antMatchers("/admin/**").hasRole("ADMIN")
// 以下访问需要 ROLE_USER 权限
.antMatchers("/user/**").hasAuthority("ROLE_USER")
// 其它任何请求访问都需要先通过认证
.anyRequest().authenticated();
// 关闭 csrf 防护
http.csrf().disable();
// 将自定义过滤器(图形验证码校验)添加到 UsernamePasswordAuthenticationFilter 之前
http.addFilterBefore(imageCodeValidateFilter, UsernamePasswordAuthenticationFilter.class);
}
/**
* 定制一些全局性的安全配置,例如:不拦截静态资源的访问
*/
@Override
public void configure(WebSecurity web) throws Exception {
// 静态资源的访问不需要拦截,直接放行
web.ignoring().antMatchers("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
}
}
⭐ 测试
访问localhost:8080/login/page,等待 60 秒后,输入正确的用户名、密码和验证码:
验证码过期,重定向到localhost:8080/login/page?error,显示错误信息:
手机短信验证码
一般登录除了用户名、密码登录,还可以使用手机短信验证码登录,Spring Security默认没有实现手机短信验证码的功能,所以需要我们自己实现。
实现流程分析
手机短信验证码登录和前面的带有图形验证码的用户名、密码登录流程类似,红色标记的部分是需要我们自定义实现的类。
我们首先分析下带有图形验证码的用户名、密码登录流程:
-
在 ImageCodeValidateFilter 过滤器中校验用户输入的图形验证码是否正确。
-
在 UsernamePasswordAuthenticationFilter 过滤器中将 username 和 password 生成一个用于认证的 Token(UsernamePasswordAuthenticationToken),并将其传递给 ProviderManager 接口的实现类 AuthenticationManager。
-
AuthenticationManager 管理器寻找到一个合适的处理器 DaoAuthenticationProvider 来处理 UsernamePasswordAuthenticationToken。
-
DaoAuthenticationProvider 通过 UserDetailsService 接口的实现类 CustomUserDetailsService 从数据库中获取指定 username 的相关信息,并校验用户输入的 password。如果校验成功,那就认证通过,用户信息类对象 Authentication 标记为已认证。
-
认证通过后,将已认证的用户信息对象 Authentication 存储到 SecurityContextHolder 中,最终存储到 Session 中。
仿照上述流程,我们分析手机短信验证码登录流程:
- 仿照 ImageCodeValidateFilter 过滤器设计 MobileVablidateFilter 过滤器,该过滤器用来校验用户输入手机短信验证码。
- 仿照 UsernamePasswordAuthenticationFilter 过滤器设计 MobileAuthenticationFilter 过滤器,该过滤器将用户输入的手机号生成一个 Token(MobileAuthenticationToken),并将其传递给 ProviderManager 接口的实现类 AuthenticationManager。
- AuthenticationManager 管理器寻找到一个合适的处理器 MobileAuthenticationProvider 来处理 MobileAuthenticationToken,该处理器是仿照 DaoAuthenticationProvider 进行设计的。
- MobileAuthenticationProvider 通过 UserDetailsService 接口的实现类 MobileUserDetailsService 从数据库中获取指定手机号对应的用户信息,此处不需要进行任何校验,直接将用户信息类对象 Authentication 标记为已认证。
- 认证通过后,将已认证的用户信息对象 Authentication 存储到 SecurityContextHolder 中,最终存储到 Session 中,此处的操作不需要我们编写。
最后通过自定义配置类 MobileAuthenticationConfig 组合上述组件,并添加到安全配置类 SpringSecurityConfig 中。
模拟发送短信验证码
✏️ 在 UserMapper 接口中添加根据 mobile 查询用户的方法
public interface UserMapper {
//...
@Select("select * from user where mobile = #{mobile}")
User selectByMobile(String mobile);
}
✏️ 创建 UserService 类,编写判断指定 mobile 是否存在的方法
package com.example.service;
import com.example.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* 判断指定 mobile 是否存在
*/
public boolean isExistByMobile(String mobile) {
return userMapper.selectByMobile(mobile) != null;
}
}
✏️ 创建 MobileCodeSendService 类,模拟手机短信验证码发送服务
package com.example.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class MobileCodeSendService {
/**
* 模拟发送手机短信验证码
*/
public void send(String mobile, String code) {
String sendContent = String.format("验证码为 %s,请勿泄露!", code);
log.info("向手机号 " + mobile + " 发送短信:" + sendContent);
}
}
✏️ 在 LoginController 中添加手机短信验证码相关的 Controller 方法
public class Constants {
//...
// Session 中存储手机短信验证码的属性名
public static final String MOBILE_SESSION_KEY = "MOBILE_SESSION_KEY";
}
@Controller
public class LoginController {
//...
@Autowired
private MobileCodeSendService mobileCodeSendService; // 模拟手机短信验证码发送服务
@Autowired
private UserService userService;
//...
@GetMapping("/mobile/page")
public String mobileLoginPage() { // 跳转到手机短信验证码登录页面
return "login-mobile";
}
@GetMapping("/code/mobile")
@ResponseBody
public Object sendMoblieCode(String mobile, HttpServletRequest request) {
// 随机生成一个 4 位的验证码
String code = RandomStringUtils.randomNumeric(4);
// 将手机验证码文本存储在 Session 中,设置过期时间为 10 * 60s
CheckCode mobileCode = new CheckCode(code, 10 * 60);
request.getSession().setAttribute(Constants.MOBILE_SESSION_KEY, mobileCode);
// 判断该手机号是否注册
if(!userService.isExistByMobile(mobile)) {
return new ResultData<>(1, "该手机号不存在!");
}
// 模拟发送手机短信验证码到指定用户手机
mobileCodeSendService.send(mobile, code);
return new ResultData<>(0, "发送成功!");
}
}
✏️ 编写手机短信验证码登录页面 login-mobile.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
<script src="https://s3.pstatp.com/cdn/expire-1-M/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<form method="post" th:action="@{/mobile/form}">
<input id="mobile" name="mobile" type="text" placeholder="手机号码"><br>
<div>
<input name="mobileCode" type="text" placeholder="验证码">
<button type="button" id="sendCode">获取验证码</button>
</div>
<div th:if="${param.error}">
<span th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}" style="color:red">用户名或密码错误</span>
</div>
<button type="submit">登录</button>
</form>
<script>
// 获取手机短信验证码
$("#sendCode").click(function () {
var mobile = $(\'#mobile\').val().trim();
if(mobile == \'\') {
alert("手机号不能为空");
return;
}
// /code/mobile?mobile=123123123
var url = "/code/mobile?mobile=" + mobile;
$.get(url, function(data){
alert(data.msg);
});
});
</script>
</body>
</html>
✏️ 更改安全配置类 SpringSecurityConfig,设置访问/mobile/page和/code/mobile不需要任何权限
@EnableWebSecurity // 开启 MVC Security 安全配置
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
//...
/**
* 定制基于 HTTP 请求的用户访问控制
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//...
// 开启基于 HTTP 请求访问控制
http.authorizeRequests()
// 以下访问不需要任何权限,任何人都可以访问
.antMatchers("/login/page", "/code/image","/mobile/page", "/code/mobile").permitAll()
// 以下访问需要 ROLE_ADMIN 权限
.antMatchers("/admin/**").hasRole("ADMIN")
// 以下访问需要 ROLE_USER 权限
.antMatchers("/user/**").hasAuthority("ROLE_USER")
// 其它任何请求访问都需要先通过认证
.anyRequest().authenticated();
//...
}
//...
}
✏️ 测试
访问localhost:8080/mobile/page,页面显示:
手机号码输入11111111111,控制台输出:
向手机号 11111111111 发送短信:验证码为 2561,请勿泄露!
浏览器弹出窗口显示”发送成功“。