原理、存在问题、解决思路
我们知道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 {
@Override
protected 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 {
/*注入 登录成功处理器*/
@Autowired
private AppAuthenticationSuccessHandler appAuthenticationSuccessHandler;
/*注入 登录 失败处理器*/
@Autowired
private AppAuthenticationFailureHandler appAuthenticationFailureHandler;
/*注入 没有权限处理器*/
@Autowired
private AppAccessDeniedHandler appAccessDeniedHandler;
/*注入 登出成功处理器*/
@Autowired
private AppLogoutSuccessHandler appLogoutSuccessHandler;
@Autowired
private AppUserDetailsService appUserDetailsService;
//验证码拦截器注入
@Autowired
private ValidateCodeFilter validateCodeFilter;
/*配置多用户*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(appUserDetailsService);
}
/*http请求配置*/
@Override
protected 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();
}
/*资源服务匹配放行:静态资源*/
@Override
public 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>
效果图