Security+Captcha验证码登录案例-带网页案例(附完整git项目源码)

Security系列教程



简介

本章主要实现Spring Security自定义用户认证功能,并使用thymeleaf编写一个简单网页供测试使用。
在这里插入图片描述

  • Springboot版本2.5.3
  • 整合网页模板引擎thymeleaf
  • hutool工具包生成验证码

1. 环境

        <!--     hutool工具包  -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.2</version>
        </dependency>

2. 生成验证码

  • 我们封装一个验证码校验工具类,我们的思路是:把验证码存入session会话中,需要校验时再从session会话中取出进行校验。
  • 首先封装一个RequestUtils工具,用于静态获取sessionrequest
/**
 * 请求工具类
 *
 * @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)
@RequiredArgsConstructor
public 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
@Slf4j
public 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
@Slf4j
public 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全家桶教程+源码,各种常用框架使用案例都有哦,具备完善的文档,致力于让开发者快速搭建基础环境并让应用跑起来,并提供丰富的使用示例供使用者参考,快来看看吧。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈小定

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值