Spring 全家桶之 Spring Security(五)

简介: Spring 全家桶之 Spring Security(五)

一、自定义验证码

在Controller包中创建CaptchaController,用于生成验证码图像,返回验证码图片,并保存图片中的验证码在Session中,方便登录时校验验证码

@Controller
@RequestMapping("/captcha")
public class CaptchaController {
    // 定义一个值,用来生成验证码的图片
    // 宽度
    private int width = 120;
    // 高度
    private int height = 30;
    // 图片内容在图片的起始位置
    private int drawY = 22;
    // 文字的间隔
    private int space = 22;
    // 验证码有几个文字
    private int charCount = 4;
    // 验证码内容的数组
    private String[] chars = {"A", "B", "C", "D", "E", "F", "G", "0", "1", "2", "3", "4", "5", "6", "8"};
    // 生成验证码内容,在一个图片上写入文字
    @GetMapping("/create")
    public void createCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 需要在内存中绘制图片,向图片中写入文字,将绘制好的图片响应给请求
        // 创建一个背景透明的图片
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // 获取画笔
        Graphics graphics = image.getGraphics();
        // 设置背景色
        graphics.setColor(Color.white);
        // 给画板涂成白色
        graphics.fillRect(0, 0, width, height);
        // 画内容
        // 创建一个字体
        Font font = new Font("宋体",Font.BOLD,18);
        graphics.setFont(font);
        graphics.setColor(Color.black);
        // 在画布上写字
        // graphics.drawString("中",10,drawY);
        // 保存验证码的值,登录时校验
        StringBuffer buffer = new StringBuffer();
        int random = 0;
        int len = chars.length;
        for (int i = 0; i < charCount ; i++) {
            random = new Random().nextInt(len);
            buffer.append(chars[random]);
            graphics.setColor(makeColor());
            graphics.drawString(chars[random],(i+1)*space, drawY);
        }
        // 绘制干扰线
        for (int i = 0; i < 4; i++) {
            graphics.setColor(makeColor());
            int[] lines = makeLine();
            graphics.drawLine(lines[0],lines[1],lines[2],lines[3]);
        }
        // 设置取消缓存
        response.setHeader("Pragma","no-cache");
        response.setHeader("Cache-Control","no-cache");
        response.setDateHeader("Expires",0);
        response.setContentType("image/png");
        // 生成的验证码存在session中
        HttpSession session = request.getSession();
        session.setAttribute("captcha",buffer.toString());
        ServletOutputStream outputStream = response.getOutputStream();
        /**
         *
         */
        ImageIO.write(image, "png", outputStream);
        outputStream.flush();
        outputStream.close();
    }
    // 获取随机颜色
    private Color makeColor(){
        Random random = new Random();
        int r = random.nextInt(255);
        int g = random.nextInt(255);
        int b = random.nextInt(255);
        return new Color(r,g,b);
    }
    // 获取干扰线
    private int[] makeLine(){
        Random random = new Random();
        int x1 = random.nextInt(width);
        int y1 = random.nextInt(height);
        int x2 = random.nextInt(width);
        int y2 = random.nextInt(height);
        return new int[]{x1,x2,y1,y2};
    }
}
复制代码

创建验证码图片的步骤

  1. 创建图像类
  2. 获取画笔,在Image上画内容
  3. 设置图片背景色
  4. 创建Font,书写文字
  5. 文字内容保存在Session中
  6. 返回Image 在自定义的安全配置中添加验证码访问权限
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            // 增加js静态资源的访问权限,验证码访问权限,登录首页访问权限
            .antMatchers("/login.html","/index.html","/login","/js/**","/captcha/**").permitAll()
            // 给url配置角色访问权限
            .antMatchers("/access/user").hasRole("USER")
            .antMatchers("/access/read").hasRole("READ")
            .antMatchers("/access/admin").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .successHandler(custSuccessHandler) //执行验证成功的handler
            .failureHandler(custFailureHandler) // 执行验证失败后的handler
            // 指定使用自定义的登录界面
            .loginPage("/login.html")
            .loginProcessingUrl("/login")
            .and()
            .csrf().disable();
}
复制代码

在登录页面中增加验证码图片和输入框

<div id="validatePanel" class="item" style="width: 137px;">
    <input type="text" id="captcha" placeholder="请输入验证码" maxlength="4">
    <a href="javascript:void(0)" onclick="refreshCaptcha()"><img id="refreshCaptcha" class="validateImg"  src="/captcha/create" ></a>
</div>
复制代码

在js中增加刷新验证码的函数,点击验证码图片即可刷新验证码

<script>
function refreshCaptcha(){
    // 刷新验证码
    var url = "/captcha/create?t=" + new Date();
    $("#refreshCaptcha").attr("src",url);
}
</script>
复制代码

fb5766120f564b4293dcfad0cd24ec4a_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.gif

修改ajax请求,增加发送验证码的内容

<script type="text/javascript">
    $(function(){
        //juqery的入口函数
        $("#btnLogin").click(function(){
            var uname = $("#username").val();
            var pwd = $("#password").val();
            // 获取输入的验证码
            var captcha = $("#captcha").val();
            $.ajax({
                // 发送ajax请求显示状态已取消,通过添加一下代码可以成功获取响应
                async: false,
                url:"/login",
                type:"POST",
                data:{
                    "username":uname,
                    "password":pwd,
                    "captcha":captcha
                },
                dataType:"json",
                success:function(resp){
                    alert(resp.code + " " + resp.msg)
                }
            })
        })
    });
</script>    
复制代码

过滤器验证验证码内容,应该在验证用户名密码之前验证发送的验证码内容,在Spring Security的用户名密码过滤器UsernamePasswordAuthenticationFilter之前自定义一个过滤器

先在common包中定义一个异常类VerificationException

public class VerificationException extends AuthenticationException {
    public VerificationException(String detail, Throwable ex) {
        super(detail, ex);
    }
    public VerificationException(String detail) {
        super(detail);
    }
    public VerificationException(){
        super("验证码错误,请重新输入");
    }
}
复制代码

修改CustFailureHandler,增加Result属性,在校验验证码失败时输出json格式的错误信息

@Component
public class CustFailureHandler implements AuthenticationFailureHandler {
    private Result result;
    public Result getResult() {
        return result;
    }
    public void setResult(Result result) {
        this.result = result;
    }
    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException e) throws IOException {
        //当框架验证用户信息失败时执行的方法
        response.setContentType("text/json;charset=utf-8");
        if( result == null){
            Result  localResult  = new Result();
            localResult.setCode(1);
            localResult.setError(1001);
            localResult.setMsg("登录失败");
            result = localResult;
        }
        OutputStream out = response.getOutputStream();
        ObjectMapper om = new ObjectMapper();
        om.writeValue(out,result );
        out.flush();
        out.close();
    }
}
复制代码

接着在common包中定义一个Filter

@Component
public class VerificationCaptchaFilter extends OncePerRequestFilter {
    @Resource
    private CustFailureHandler failureHandler;
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        // 只有login()才需要这个过滤器
        String requestURI = httpServletRequest.getRequestURI();
        if (!"/login".equals(requestURI)){
            // 过滤器正常执行,非登录操作不参与验证码操作
            filterChain.doFilter(httpServletRequest,httpServletResponse);
        } else {
            try {
                // 登录操作,需要验证码
                verificationCode(httpServletRequest);
                // 如果 验证通过
                filterChain.doFilter(httpServletRequest, httpServletResponse);
            } catch (VerificationException e) {
                Result result = new Result();
                result.setCode(1);
                result.setError(1002);
                result.setMsg("验证码错误");
                failureHandler.setResult(result);
                failureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
            }
        }
    }
    private void verificationCode(HttpServletRequest httpServletRequest) throws VerificationException {
        HttpSession session = httpServletRequest.getSession();
        String requestCode = httpServletRequest.getParameter("captcha");
        String sessionCode = "";
        String captcha = (String) session.getAttribute("captcha");
        if (captcha != null){
            sessionCode = captcha;
        }
        System.out.println(requestCode);
        if (!StringUtils.isEmpty(sessionCode)){
            session.removeAttribute("captcha");
        }
        if (StringUtils.isEmpty(requestCode) || StringUtils.isEmpty(sessionCode) || !requestCode.equalsIgnoreCase(sessionCode)){
            throw new VerificationException();
        }
    }
}
复制代码

修改自定义的安全配置,将自定义的过滤器加入到过滤器链中,放在校验用户名密码之前

@Configuration
@EnableWebSecurity
public class CustSecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private UserDetailsService userDetailsService;
    @Resource
    private CustSuccessHandler custSuccessHandler;
    @Resource
    private CustFailureHandler custFailureHandler;
    @Resource
    private VerificationCaptchaFilter captchaFilter;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 增加js静态资源的访问权限
                .antMatchers("/login.html","/index.html","/login","/js/**","/captcha/**").permitAll()
                // 给url配置角色访问权限
                .antMatchers("/access/user").hasRole("USER")
                .antMatchers("/access/read").hasRole("READ")
                .antMatchers("/access/admin").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .successHandler(custSuccessHandler) //执行验证成功的handler
                .failureHandler(custFailureHandler) // 执行验证失败后的handler
                // 指定使用自定义的登录界面
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .and()
                .csrf().disable();
        // 在框架的过滤器Chain中加入自定义的过滤器
        http.addFilterBefore(captchaFilter,UsernamePasswordAuthenticationFilter.class);
    }
}
复制代码

重新启动应用

5a13b05f7b224a9e857ef22f090c560a_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.gif

验证码生效

系列完结,撒花🎉!!!


相关文章
|
28天前
|
JSON 安全 Java
什么是JWT?如何使用Spring Boot Security实现它?
什么是JWT?如何使用Spring Boot Security实现它?
120 5
|
5月前
|
安全 Java 数据安全/隐私保护
使用Spring Security实现细粒度的权限控制
使用Spring Security实现细粒度的权限控制
|
5月前
|
安全 Java 数据库
实现基于Spring Security的权限管理系统
实现基于Spring Security的权限管理系统
|
5月前
|
安全 Java 数据安全/隐私保护
解析Spring Security中的权限控制策略
解析Spring Security中的权限控制策略
|
6月前
|
JSON 安全 Java
Spring Security 6.x 微信公众平台OAuth2授权实战
上一篇介绍了OAuth2协议的基本原理,以及Spring Security框架中自带的OAuth2客户端GitHub的实现细节,本篇以微信公众号网页授权登录为目的,介绍如何在原框架基础上定制开发OAuth2客户端。
239 4
Spring Security 6.x 微信公众平台OAuth2授权实战
|
6月前
|
存储 安全 Java
Spring Security 6.x OAuth2登录认证源码分析
上一篇介绍了Spring Security框架中身份认证的架构设计,本篇就OAuth2客户端登录认证的实现源码做一些分析。
277 2
Spring Security 6.x OAuth2登录认证源码分析
|
6月前
|
安全 Java 数据安全/隐私保护
Spring Security 6.x 一文快速搞懂配置原理
本文主要对整个Spring Security配置过程做一定的剖析,希望可以对学习Spring Sercurity框架的同学所有帮助。
330 5
Spring Security 6.x 一文快速搞懂配置原理
|
6月前
|
安全 Java API
Spring Security 6.x 图解身份认证的架构设计
【6月更文挑战第1天】本文主要介绍了Spring Security在身份认证方面的架构设计,以及主要业务流程,及核心代码的实现
99 1
Spring Security 6.x 图解身份认证的架构设计
|
5月前
|
安全 Java 数据安全/隐私保护
使用Spring Security实现细粒度的权限控制
使用Spring Security实现细粒度的权限控制
|
5月前
|
安全 Java 数据安全/隐私保护
使用Java和Spring Security实现身份验证与授权
使用Java和Spring Security实现身份验证与授权