Security+Captcha验证码登录案例-带网页案例(附完整git项目源码)
Security系列教程
- ①Security入门体验
- ②Security自定义账号密码验证+thymeleaf登录案例(附带网页案例
- ③Security+验证码登录案例(附带网页案例)
- ④Security+jwt 实现无状态认证,前后端分离(附带网页案例))
- ⑤Security+jwt 短信认证登录,前后端分离(附带网页案例)
- ⑥Security+oauth2 授权服务器(附带网页案例)
文章目录
- Security系列教程
- 简介
-
- 1. 环境
- 2. 生成验证码
- 3. 验证码校验
- 4. 最终效果演示
- 5. 源码分享
简介
本章主要实现Spring Security自定义用户认证功能,并使用thymeleaf
编写一个简单网页供测试使用。
- Springboot版本2.5.3
- 整合网页模板引擎
thymeleaf
hutool
工具包生成验证码
1. 环境
- 基础环境搭建请参考:②Security自定义账号密码验证+thymeleaf登录案例(附带网页案例
pom.xml
中新增hutool
工具包,用于生成验证码
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.2</version> </dependency>
2. 生成验证码
- 我们封装一个验证码校验工具类,我们的思路是:把验证码存入
session
会话中,需要校验时再从session
会话中取出进行校验。 - 首先封装一个
RequestUtils
工具,用于静态获取session
和request
。
/ * 请求工具类 * * @author ding */public class RequestUtils { / * 获取session */ public static HttpSession getHttpSession() { return getHttpRequest().getSession(); } / * 获取request */ public static HttpServletRequest getHttpRequest() { ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); assert servletRequestAttributes != null; return servletRequestAttributes.getRequest(); }}
- 验证码生成工具
CaptchaUtils
/ * 生成及校验图片验证码 * * @author ding */public class CaptchaUtils { private static final String CAPTCHA = "captcha"; / * CircleCaptcha 圆圈干扰验证码 * 定义图形验证码的长、宽、验证码字符数、干扰元素个数 */ public static void getCircleCaptcha(HttpSession session, HttpServletResponse response) throws IOException { LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100, 5, 20); session.setAttribute(CAPTCHA, lineCaptcha.getCode()); writeResp(lineCaptcha, response); } / * 验证码校验 * * @param code 验证码 */ public static boolean verify(String code) { HttpSession session = RequestUtils.getHttpSession(); String captcha = (String) session.getAttribute(CAPTCHA); return code.equals(captcha); } / * http图片响应 */ private static void writeResp(AbstractCaptcha abstractCaptcha, HttpServletResponse response) throws IOException { ServletOutputStream out = null; try { out = response.getOutputStream(); abstractCaptcha.write(out); } finally { if (Objects.nonNull(out)) { out.close(); } } }}
- 编写一个验证码获取接口
@GetMapping("/getCode") @ResponseBody public void getCode(HttpSession session, HttpServletResponse response) throws IOException { CaptchaUtils.getCircleCaptcha(session, response); }
- 验证码获取并不需要认证,所以我们在
WebSecurityConfig
中开放该请求。 - 以下为
WebSecurityConfig
部分截取
@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)@RequiredArgsConstructorpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.headers().frameOptions().disable();//开启运行iframe嵌套页面 // 1、配置权限认证 http.authorizeRequests() // 1. 配置不拦截路由 .antMatchers("/toLogin", "/getCode").permitAll() // 任何其它请求 .anyRequest() // 都需要身份认证 .authenticated() 略...
- 改造网页,新增验证码获取
<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <title>登陆页</title></head><body><div> <form action="/login" method="post"> <h2>登陆页</h2> <h6>账号:admin,密码:123456</h6> <img style="width: auto;height: 100px" src="/getCode"/> <br /> <label for="username">账号:</label><input style="margin-left: 17px" type="text" id="username" name="username" placeholder="admin"> <br /> <label for="password">密码:</label><input style="margin-left: 17px" type="password" id="password" name="password" placeholder="123456"> <br /> <label for="code">验证码:</label><input type="text" id="code" name="code" placeholder=""> <br /> <br /> <button type="submit">登陆</button> <div th:if="${param.captchaInvalid}" style="color: red;"> 验证码错误. </div> <div th:if="${param.error}" style="color: red;"> 账号或密码错误. </div> <div th:if="${param.logout}" style="color: red;"> 登录失效. </div> </form></div></body></html>
- 启动项目,效果如下
3. 验证码校验
- 我们定义一个异常类,用于处理验证校验错误,重写
AuthenticationException
,定义我们的验证码类异常。
/ * 验证码错误异常 * * @author ding */public class CaptchaInvalidException extends AuthenticationException { public CaptchaInvalidException(String msg, Throwable cause) { super(msg, cause); } public CaptchaInvalidException(String msg) { super(msg); }}
- 接下来我们需要在
UserDetailsServiceImpl
中添加我们的校验逻辑,我们定义了一个code参数用来接收登录的验证码,由于security的默认登录接口只有一个用户名参数,那么我们如何获取验证码呢?
- 其实很简单,还记得我们开头封装的
RequestUtils
工具类吗,没错,我们可以通过静态获取request
的方式获取请求参数,如下:
@Component@RequiredArgsConstructor@Slf4jpublic class UserDetailsServiceImpl implements UserDetailsService { / * 模拟一个数据库用户 * 账号 admin * 密码 123456 */ private final static HashMap<String, MyUser> USER_MAP = new LinkedHashMap<>() { { put("admin", new MyUser() .setUserId(1L) .setUsername("admin") .setPassword(new BCryptPasswordEncoder().encode("123456")) ); } }; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { HttpServletRequest request = RequestUtils.getHttpRequest(); String code = request.getParameter("code"); log.info("登录者账号为:{},验证码:code:{}", username, code); // 验证码是否正确 if (!CaptchaUtils.verify(code)) { throw new CaptchaInvalidException("验证码错误"); } // 通过userName获取用户信息,实战中把USER_MAP换成数据库获取即可 MyUser user = USER_MAP.get(username); if (user == null) { throw new UsernameNotFoundException("not found"); } // 角色和权限都在这里添加,角色以ROLE_前缀,不是ROLE_前缀的视为权限,这里添加了ROLE_ADMIN角色和read、write权限 List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN,read,write"); AuthUser authUser = new AuthUser(user.getUsername(), user.getPassword(), authorities); // 我们存放我们自定义的信息,如用户id,方便后续获取用户信息 authUser.setUserId(user.getUserId()); return authUser; }}
-
可以看见我们在校验完验证码后,抛出了一个
CaptchaInvalidException
异常,这是我们之前定义的一个专门处理验证码错误的异常,这么做的目的是为了在后续处理中,我们可以根据业务,给web端返回不同的呈现信息。 -
重新编写登录失败处理器
@Component@Slf4jpublic class LoginFailure extends SimpleUrlAuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException ex) throws IOException { log.info("登录失败"); ex.printStackTrace(); this.saveException(request, ex); if ("验证码错误".equals(ex.getMessage())) { this.getRedirectStrategy().sendRedirect(request, response, "/toLogin?captchaInvalid=true"); } else { this.getRedirectStrategy().sendRedirect(request, response, "/toLogin?error=true"); } }}
4. 最终效果演示
5. 源码分享
本系列项目已收录
Springboot、SpringCloud全家桶教程+源码,各种常用框架使用案例都有哦,具备完善的文档,致力于让开发者快速搭建基础环境并让应用跑起来,并提供丰富的使用示例供使用者参考,快来看看吧。
- 项目源码github地址
- 项目源码国内gitee地址
开发者涨薪指南
48位大咖的思考法则、工作方式、逻辑体系