Springboot+SpringSecurity实现图片验证码登录问题
这个问题,网上找了好多,结果代码都不全,找了好多,要不是就自动注入的类注入不了,编译报错,要不异常捕获不了浪费好多时间,就觉得,框架不熟就不能随便用,全是坑,气死我了,最后改了两天.终于弄好啦;
问题主要是:
- 返回的验证码不知道在SpringSecurity的什么地方和存在内存里的比较?我用的方法是前置一个过滤器,插入到表单验证之前。
- 比较之后应该怎么处理,:比较之后要抛出一个继承了AuthenticationException的异常
- 其次是捕获验证码错误异常的处理? 捕获到的异常交给自定义验证失败处理器AuthenticationFailureHandler处理,这里,用的处理器要和表单验证失败的处理器是同一个处理器,不然会报异常,所以在需要写一个全局的AuthenticationFailureHandler的实现类,专门用来处理异常。表单验证有成功和失败两个处理器,我们一般直接以匿名内部类形似写。要是加了验证码,就必须使用统一的失败处理器。
效果图:

网上大都是直接注入一个AuthenticationFailureHandler,我当时就不明白这个咋注进去的,我这个一写就报错,注入不进去,后来就想自己new一个哇,可以是可以了,但是还报异常,java.lang.IllegalStateException: Cannot call sendError() after the response has been committed,后来想表单验证的时候,失败用的也是这个处理器,就想定义一个全局的处理器,
package com.liruilong.hros.config; import com.fasterxml.jackson.databind.ObjectMapper;
import com.liruilong.hros.Exception.ValidateCodeException;
import com.liruilong.hros.model.RespBean;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter; /**
* @Description :
* @Author: Liruilong
* @Date: 2020/2/11 23:08
*/
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
RespBean respBean = RespBean.error(e.getMessage());
// 验证码自定义异常的处理
if (e instanceof ValidateCodeException){
respBean.setMsg(e.getMessage());
//Security内置的异常处理
}else if (e instanceof LockedException) {
respBean.setMsg("账户被锁定请联系管理员!");
} else if (e instanceof CredentialsExpiredException) {
respBean.setMsg("密码过期请联系管理员!");
} else if (e instanceof AccountExpiredException) {
respBean.setMsg("账户过期请联系管理员!");
} else if (e instanceof DisabledException) {
respBean.setMsg("账户被禁用请联系管理员!");
} else if (e instanceof BadCredentialsException) {
respBean.setMsg("用户名密码输入错误,请重新输入!");
}
//将hr转化为Sting
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
@Bean
public MyAuthenticationFailureHandler getMyAuthenticationFailureHandler(){
return new MyAuthenticationFailureHandler();
}
}
流程
请求登录页,将验证码结果存到基于Servlet的session里,以JSON格式返回验证码,
之后前端发送登录请求,SpringSecurity中处理,自定义一个filter让它继承自OncePerRequestFilter,然后重写doFilterInternal方法,在这个方法中实现验证码验证的功能,如果验证码错误就抛出一个继承自AuthenticationException的验证吗错误的异常消息写入到响应消息中.
之后返回异常信息交给自定义验证失败处理器处理。
下面以这个顺序书写代码:
依赖大家照着import导一下吧,记得有这两个,验证码需要一个依赖,之后还使用了一个工具包
<!--图片验证-->
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
前端使用Vue+ElementUI
<div class="login-code">
<img :src="codeUrl"
@click="getCode">
</div>
后端代码:
获取验证码,将结果放到session里
package com.liruilong.hros.controller; import com.liruilong.hros.model.RespBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.wf.captcha.ArithmeticCaptcha;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map; /**
* @Description :
* @Author: Liruilong
* @Date: 2019/12/19 19:58
*/
@RestController
public class LoginController { @GetMapping(value = "/auth/code")
public Map getCode(HttpServletRequest request,HttpServletResponse response){
// 算术类型 https://gitee.com/whvse/EasyCaptcha
ArithmeticCaptcha captcha = new ArithmeticCaptcha(111, 36);
// 几位数运算,默认是两位
captcha.setLen(2);
// 获取运算的结果
String result = captcha.text();
System.err.println("生成的验证码:"+result);
// 保存
// 验证码信息
Map<String,Object> imgResult = new HashMap<String,Object>(2){{
put("img", captcha.toBase64()); }};
request.getSession().setAttribute("yanzhengma",result); return imgResult;
}
}
定义一个VerifyCodeFilter 过滤器
package com.liruilong.hros.filter; import com.liruilong.hros.Exception.ValidateCodeException;
import com.liruilong.hros.config.MyAuthenticationFailureHandler;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.AuthenticationException;
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 java.io.IOException; /**
* @Description :
* @Author: Liruilong
* @Date: 2020/2/7 19:39
*/
@Component
public class VerifyCodeFilter extends OncePerRequestFilter { @Bean
public VerifyCodeFilter getVerifyCodeFilter() {
return new VerifyCodeFilter();
}
@Autowired
MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (StringUtils.equals("/doLogin", request.getRequestURI())
&& StringUtils.equalsIgnoreCase(request.getMethod(), "post")) {
// 1. 进行验证码的校验
try {
String requestCaptcha = request.getParameter("code");
if (requestCaptcha == null) {
throw new ValidateCodeException("验证码不存在");
}
String code = (String) request.getSession().getAttribute("yanzhengma");
if (StringUtils.isBlank(code)) {
throw new ValidateCodeException("验证码过期!");
}
code = code.equals("0.0") ? "0" : code;
logger.info("开始校验验证码,生成的验证码为:" + code + " ,输入的验证码为:" + requestCaptcha);
if (!StringUtils.equals(code, requestCaptcha)) {
throw new ValidateCodeException("验证码不匹配");
}
} catch (AuthenticationException e) {
// 2. 捕获步骤1中校验出现异常,交给失败处理类进行进行处理
myAuthenticationFailureHandler.onAuthenticationFailure(request, response, e);
} finally {
filterChain.doFilter(request, response);
}
} else {
filterChain.doFilter(request, response);
} } }
定义一个自定义异常处理,继承AuthenticationException
package com.liruilong.hros.Exception; import org.springframework.security.core.AuthenticationException; /**
* @Description :
* @Author: Liruilong
* @Date: 2020/2/8 7:24
*/ public class ValidateCodeException extends AuthenticationException { public ValidateCodeException(String msg) {
super(msg);
} }
security配置.
在之前的基础上加filter的基础上加了
http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class),验证处理上,验证码和表单验证失败用同一个失败处理器,
package com.liruilong.hros.config; import com.fasterxml.jackson.databind.ObjectMapper;
import com.liruilong.hros.filter.VerifyCodeFilter;
import com.liruilong.hros.model.Hr;
import com.liruilong.hros.model.RespBean;
import com.liruilong.hros.service.HrService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter; /**
* @Description :
* @Author: Liruilong
* @Date: 2019/12/18 19:11
*/ @Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
HrService hrService;
@Autowired
CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;
@Autowired
CustomUrlDecisionManager customUrlDecisionManager;
@Autowired
VerifyCodeFilter verifyCodeFilter ;
@Autowired
MyAuthenticationFailureHandler myAuthenticationFailureHandler; @Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(hrService);
}
/**
* @Author Liruilong
* @Description 放行的请求路径
* @Date 19:25 2020/2/7
* @Param [web]
* @return void
**/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/auth/code","/login","/css/**","/js/**", "/index.html", "/img/**", "/fonts/**","/favicon.ico");
} @Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
//.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(customUrlDecisionManager);
object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
return object;
}
})
.and().formLogin().usernameParameter("username").passwordParameter("password") .loginProcessingUrl("/doLogin")
.loginPage("/login")
//登录成功回调
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
Hr hr = (Hr) authentication.getPrincipal();
//密码不回传
hr.setPassword(null);
RespBean ok = RespBean.ok("登录成功!", hr);
//将hr转化为Sting
String s = new ObjectMapper().writeValueAsString(ok);
out.write(s);
out.flush();
out.close();
}
})
//登失败回调
.failureHandler(myAuthenticationFailureHandler)
//相关的接口直接返回
.permitAll()
.and()
.logout()
//注销登录
// .logoutSuccessUrl("")
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write(new ObjectMapper().writeValueAsString(RespBean.ok("注销成功!")));
out.flush();
out.close();
}
})
.permitAll()
.and()
.csrf().disable().exceptionHandling()
//没有认证时,在这里处理结果,不要重定向
.authenticationEntryPoint(new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException authException) throws IOException, ServletException {
resp.setContentType("application/json;charset=utf-8");
resp.setStatus(401);
PrintWriter out = resp.getWriter();
RespBean respBean = RespBean.error("访问失败!");
if (authException instanceof InsufficientAuthenticationException) {
respBean.setMsg("请求失败,请联系管理员!");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
out.flush();
out.close();
}
});
}
}
--------------------------------------------------------------------------------------
Springboot+SpringSecurity实现图片验证码登录问题的更多相关文章
- Python 实现简单图片验证码登录
朋友说公司要在测试环境做接口测试,登录时需要传入正确的图片的验证码,本着懒省事的原则,推荐他把测试环境的图片验证码写死,我们公司也是这么做的^_^.劝说无果/(ㄒoㄒ)/~~,只能通过 OCR 技术来 ...
- springboot security+redis+jwt+验证码 登录验证
概述 基于jwt的token认证方案 验证码 框架的搭建,可以自己根据网上搭建,或者看我博客springboot相关的博客,这边就不做介绍了.验证码生成可以利用Java第三方组件,引入 <dep ...
- SpringBoot应用篇(二):SpringSecurity实现带验证码的登录认证 附代码
一.文章简介 本文简要介绍了spring security的基本原理和实现,并基于springboot整合了spring security实现了基于数据库管理的用户的登录和登出,登录过程实现了验证码的 ...
- SpringBoot + Spring Security 学习笔记(五)实现短信验证码+登录功能
在 Spring Security 中基于表单的认证模式,默认就是密码帐号登录认证,那么对于短信验证码+登录的方式,Spring Security 没有现成的接口可以使用,所以需要自己的封装一个类似的 ...
- SpringBoot + Spring Security 学习笔记(三)实现图片验证码认证
整体实现逻辑 前端在登录页面时,自动从后台获取最新的验证码图片 服务器接收获取生成验证码请求,生成验证码和对应的图片,图片响应回前端,验证码保存一份到服务器的 session 中 前端用户登录时携带当 ...
- Springboot +redis+⾕歌开源Kaptcha实现图片验证码功能
Springboot +redis+⾕歌开源Kaptcha实现图片验证码功能 背景 注册-登录-修改密码⼀般需要发送验证码,但是容易被 攻击恶意调⽤ 什么是短信-邮箱轰炸机 手机短信轰炸机是批.循环给 ...
- Springboot实现验证码登录
Springboot实现验证码登录 1.背景 本人近期正在完成毕业设计(旅游信息管理系统)的制作,采用的SpringBoot+Thymeleaf的模式.在登录网站时想要添加验证码验证,通过网上查找资料 ...
- springboot +spring security4 自定义手机号码+短信验证码登录
spring security 默认登录方式都是用户名+密码登录,项目中使用手机+ 短信验证码登录, 没办法,只能实现修改: 需要修改的地方: 1 .自定义 AuthenticationProvide ...
- 【python】带图片验证码的登录自动化实战
近期在跟进新项目的时候,整体的业务线非常之长,会一直重复登录退出不同账号的这个流程,所以想从登录开始实现部分的自动化.因为是B/S的架构,所以采用的是selenium的框架来实现.大致实现步骤如下: ...
随机推荐
- leetcode.1275找出井字棋的获胜者
A 和 B 在一个 3 x 3 的网格上玩井字棋. 井字棋游戏的规则如下: 玩家轮流将棋子放在空方格 (" ") 上.第一个玩家 A 总是用 "X" 作为棋子, ...
- Redis 中的数据持久化策略(RDB)
Redis 是一个内存数据库,所有的数据都直接保存在内存中,那么,一旦 Redis 进程异常退出,或服务器本身异常宕机,我们存储在 Redis 中的数据就凭空消失,再也找不到了. Redis 作为一个 ...
- Linux学习之路--简介
1 Linux简介 UNIX与Linux发展史 Unix在1969年,美国贝尔实验室的肯汤普森在DEC PDP-7机器上开发出了UNIX系统.Linux出现于1991年,是由芬兰赫尔辛基大学学生李纳斯 ...
- JAVA8学习——Stream底层的实现三(学习过程)
Stream的深入(三) 心得:之前学习流,深入了流的底层.但是学的这些东西在平时日常开发的过程中,是根本不会用到的.只是为了更好帮助自己去理解流的底层设施.用起来也更自信,能够确定用的东西非常正确. ...
- 开源API网关,你选对了么?
开源API网关,你选对了么? api网关的本质 不用扯那么多,也不用画图,一句话说清楚 api网关:流量总入口,得以集中控制! 就这么简单 api网关协议上最基本要支持HTTP 和 WebSocket ...
- cassandra中的ACID,与RDBMS中的事务有何不同?
Cassandra中的ACID标准 Apache Cassandra不遵循具有回滚或锁定机制的ACID(原子性,一致性,隔离性,持久性)事务,而是提供原子,隔离和持久的事务,并具有最终和可调的一致性, ...
- Java工程师 基础+实战 完整路线图(详解版)
Java工程师 基础+实战 完整路线图(详解版) Java 基础 Java 是一门纯粹的面向对象的编程语言,所以除了基础语法之外,必须得弄懂它的 oop 特性:封装.继承.多态.此外还有泛型.反射 ...
- floj 2264
2.公路建设(highway.c/cpp/pas) 在滨海市一共有n 个城市,编号依次为1到n,它们之间计划修建m条双向道路,其中修建第i条道路的费用为ci. 海霸王作为滨海市公路建设项目的总工程师, ...
- 解决a标签点击会出现虚框现象
1.解决a标签点击会出现虚框现象. 当a标签获得焦点的时候,a标签的周围就会出现虚框,它不同于border,不占任何宽度,a失去焦点的时候就会消失,就是outline. 在遨游,Firefox ,IE ...
- ELK学习实验018:filebeat收集docker日志
Filebeat收集Docker日志 1 安装docker [root@node4 ~]# yum install -y yum-utils device-mapper-persistent-data ...