Springboot+SpringSecurity怎么实现图片验证码登录
本文小编为大家详细介绍“Springboot+SpringSecurity怎么实现图片验证码登录”,内容详细,步骤清晰,细节处理妥当,希望这篇“Springboot+SpringSecurity怎么实现图片验证码登录”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。
效果图
网上大都是直接注入一个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>
<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怎么实现图片验证码登录”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注蜗牛博客行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:niceseo99@gmail.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
评论