原理、存在问题、解决思路
我们知道Spring Security是通过过滤器链来完成了,所以它的解决方案是创建一个过滤器放到Security的过滤器链中,在自定义的过滤器中比较验证码
1 添加依赖(生成验证码)
<!--引入hutool-->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.3.9</version>
</dependency>
2 生产验证码
CodeController
@Controller
@RequestMapping("/code")
public class CodeController {@RequestMapping("/img")public void code(HttpServletRequest request, HttpServletResponse response) {//创建验证码长,宽,字符数,干扰元素个数CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20);// 放在session里面System.out.println("生成的验证码" + circleCaptcha.getCode());request.getSession().setAttribute("circleCaptcha", circleCaptcha.getCode());// 用流写出去try {ImageIO.write(circleCaptcha.getImage(), "JPEG", response.getOutputStream());} catch (IOException e) {e.printStackTrace();}}
}
3 创建验证码过滤器
ValidateCodeFilter
@Component
public class ValidateCodeFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//得到请求地址String requestURI = request.getRequestURI();System.out.println("requestURL" + requestURI);//判断是否是登录请求if (requestURI.equals("/login/doLogin")) {//说明当前请求为登陆//1,得到登陆时用户输入的验证码String code1 = request.getSession().getAttribute("circleCaptcha").toString();String code = request.getParameter("code");System.out.println("用户输入的验证码:" + code);if (StringUtils.hasText(code)) {if (code.equalsIgnoreCase(code1)) {//说明验证码正确 直接放行request.getSession().removeAttribute("errorMSg");filterChain.doFilter(request, response);return;} else {//说明验证码不正确,返回登陆页面request.getSession().setAttribute("errorMsg", "验证码错误");response.sendRedirect("/index/toLogin");return;}} else {//用户没有输出验证码重定向到登陆页面request.getSession().setAttribute("errorMsg", "验证码不能为空");response.sendRedirect("/index/toLogin");return;}} else {//说明不是登陆 直接放行到下一个过滤器filterChain.doFilter(request, response);return;}}
}
4 配置类放行
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*注入 登录成功处理器*/@Autowiredprivate AppAuthenticationSuccessHandler appAuthenticationSuccessHandler;/*注入 登录 失败处理器*/@Autowiredprivate AppAuthenticationFailureHandler appAuthenticationFailureHandler;/*注入 没有权限处理器*/@Autowiredprivate AppAccessDeniedHandler appAccessDeniedHandler;/*注入 登出成功处理器*/@Autowiredprivate AppLogoutSuccessHandler appLogoutSuccessHandler;@Autowiredprivate AppUserDetailsService appUserDetailsService;//验证码拦截器注入@Autowiredprivate ValidateCodeFilter validateCodeFilter;/*配置多用户*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(appUserDetailsService);}/*http请求配置*/@Overrideprotected void configure(HttpSecurity http) throws Exception {
// super.configure(http);// 不使用父类的 方法, 需要 提供登录配置/*没权权限的处理*/
// http.exceptionHandling().accessDeniedHandler(appAccessDeniedHandler);/*登录*/// 配置登录之前添加一个验证码的过滤器http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);http.formLogin().usernameParameter("uname")//页面表单账号的参数名 默认 为 username.passwordParameter("pwd")//页面表单密码的参数名 默认 为 password.loginPage("/index/toLogin")//定义登录页面的 请求 地址(转发到登录页面).loginProcessingUrl("/login/doLogin")// 表单提交的 地址(不需要提供),登录验证......successForwardUrl("/index/toIndex")//登录成功 跳转的路径.failureForwardUrl("/index/toLogin")//登录失败 跳转的路径
// .successHandler(appAuthenticationSuccessHandler)//登录成功处理器
// .failureHandler(appAuthenticationFailureHandler)//登录失败处理器.permitAll();;/*登出*/http.logout().logoutUrl("/logout")//登出的 请求地址.logoutSuccessUrl("/index/toLogin")//登出成功后 访问的路径
// .logoutSuccessHandler(appLogoutSuccessHandler)//登出成功处理器.permitAll();/*设置 资源所需要的 权限 (好比 门上锁)*/http.authorizeRequests()
// .mvcMatchers("/index/toLogin", "/index.html","/code/img").permitAll()//不需要认证就可以访问.antMatchers("/code/img") // 放行验证码的路径.permitAll().anyRequest().authenticated()//所有请求都需要登录认证 才能进行;/*禁用csrf跨域请求攻击 如果不禁用 自定义的登录页面无法登录*/http.csrf().disable();}/*资源服务匹配放行:静态资源*/@Overridepublic void configure(WebSecurity web) throws Exception {
// super.configure(web);web.ignoring().antMatchers("/css/**");}/*强制要求配置 密码加密器*/@Bean// 将对象 交给 spring容器 管理public PasswordEncoder passwordEncoder() {
// return NoOpPasswordEncoder.getInstance();//不加密return new BCryptPasswordEncoder();}}
5 前端template
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>用户登陆</title>
</head>
<body>
<h2>登录页面</h2>
<!--${param.error}这个如果有值,就显示帐号或密码错误-->
<h4 th:if="${session.errorMsgs}" style="color: #c41f1f;">帐号或密码错误,请重新输入</h4>
<form action="/login/doLogin" method="post"><table><tr><td>用户名:</td><td><input type="text" name="uname" value="zhangsan"></td></tr><tr><td>密码:</td><td><input type="password" name="pwd" value="123456"></td></tr><td>验证码:</td><td><input type="text" name="code"> <img src="/code/img" style="height:33px;cursor:pointer;"onclick="this.src=this.src"><span th:text="${session.errorMsg}" style="color: #FF0000;"></span></td><tr><td colspan="2"><button type="submit">登录</button></td></tr></table>
</form>
</body>
效果图