Springboot+SpringSecurity实现图片验证码登录的示例

网友投稿 612 2022-10-12

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(http://"用户名密码输入错误,请重新输入!");

}

//将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导一下吧,记得有这两个,验证码需要一个依赖,之后还使用了一个工具依赖包,之后是前端代码

com.github.whvcse

easy-captcha

1.6.2

org.apache.commons

commons-lang3

@click="getCode">

后端代码:

获取验证码,将结果放到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 imgResult = new HashMap(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() {

@Override

public 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();

}

})

.permhttp://itAll()

.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();

}

});

}

}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java入门 | 3_数组
下一篇:论坛小项目-注意:本项目没有用框架!
相关文章

 发表评论

暂时没有评论,来抢沙发吧~