• Springboot+SpringSecurity实现图片验证码登录问题


    这个问题,网上找了好多,结果代码都不全,找了好多,要不是就自动注入的类注入不了,编译报错,要不异常捕获不了浪费好多时间,就觉得,框架不熟就不能随便用,全是坑,气死我了,最后改了两天.终于弄好啦;

    问题主要是:

    1. 返回的验证码不知道在SpringSecurity的什么地方和存在内存里的比较?我用的方法是前置一个过滤器,插入到表单验证之前。
    2. 比较之后应该怎么处理,:比较之后要抛出一个继承了AuthenticationException的异常
    3. 其次是捕获验证码错误异常的处理? 捕获到的异常交给自定义验证失败处理器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();
        }
    }

    流程 

    1.  请求登录页,将验证码结果存到基于Servlet的session里,以JSON格式返回验证码,

    2. 之后前端发送登录请求,SpringSecurity中处理,自定义一个filter让它继承自OncePerRequestFilter,然后重写doFilterInternal方法,在这个方法中实现验证码验证的功能,如果验证码错误就抛出一个继承自AuthenticationException的验证吗错误的异常消息写入到响应消息中.

    3. 之后返回异常信息交给自定义验证失败处理器处理。

    下面以这个顺序书写代码:

    依赖大家照着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(); } }); } }

    --------------------------------------------------------------------------------------

     

  • 相关阅读:
    ios专题 - CocoaPods - 初次体验
    ios专题 - CocoaPods - 安装
    Objective-C浅拷贝和深拷贝
    支付宝交互流程
    UITabBar的隐藏
    iOS-容易造成循环引用的三种场景
    FMDB 的基本操作
    Swap file ".Podfile.swp" already exists!
    将UIImage保存成JPG或PNG格式存储在本地
    UI常用控件的一些属性
  • 原文地址:https://www.cnblogs.com/liruilong/p/12283728.html
Copyright © 2020-2023  润新知