Springboot+SpringSecurity怎么实现图片验证码登录

本文小编为大家详细介绍“Springboot+SpringSecurity怎么实现图片验证码登录”,内容详细,步骤清晰,细节处理妥当,希望这篇“Springboot+SpringSecurity怎么实现图片验证码登录”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

效果图

Springboot+SpringSecurity怎么实现图片验证码登录  springboot 第1张

网上大都是直接注入一个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>
        <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进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

评论

有免费节点资源,我们会通知你!加入纸飞机订阅群

×
天气预报查看日历分享网页手机扫码留言评论Telegram