Spring Security实战(二)—— 实现图形验证码

news2025/3/12 23:49:33

目录

一. 使用过滤器实现图形验证码

1. 自定义过滤器

2. 图形验证码过滤器

(1)引入kaptcha依赖

(2)配置一个 kaptcha 实例

(3)创建一个CaptchaController,用于获取图形验证码

(4)用于校验验证码的过滤器

(5)spring security 配置过滤器链

(6)修改 login.html 

(7)debug调试:

二、使用自定义认证实现图形验证码

1. 认识 AuthenticationProvider

2. 自定义AuthenticationProvider

 2. 实现图形验证码的AuthenticationProvider


一. 使用过滤器实现图形验证码

        验证码是为了防止恶意用户暴力重试而设置的。

1. 自定义过滤器

        在Spring Security中,实现验证码校验的方式有很多种,最简单的方式就是自定义一个专门处理验证码逻辑的过滤器,将其添加到Spring Security过滤器链的合适位置。当匹配到登录请求时,立刻对验证码进行校验,成功则放行,失败则提前结束整个验证请求。

(1)自定义一个过滤器

在该过滤器执行的时候在控制台输出一句日志

public class CustomFilter implements Filter {
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 在这里添加自定义逻辑
        chain.doFilter(request, response);
        System.out.println("自定义的过滤器执行了!");
    }
 
    // 可以在这里实现 Filter 接口的其他方法
}

(2)把该过滤器加在UsernamePasswordAuthenticationFilter之后

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterAfter(new CustomFilter(), UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }

(3)测试

登录前后,可以看到控制台会多次打印该过滤器内容

这可能是因为在 CustomFilter 的 doFilter() 方法中打印了一条日志,而该方法在请求到达服务器时就会被调用,而不是只有在登录时才执行。因此,无论是在登录前还是登录后,只要有请求到达服务器,CustomFilter 的 doFilter() 方法都会被调用,并输出一条日志。

2023-04-14 23:57:00.362  INFO 35992 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
自定义的过滤器执行了!
自定义的过滤器执行了!
2023-04-14 23:57:07.250  INFO 35992 --- [nio-8080-exec-3] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-04-14 23:57:07.410  INFO 35992 --- [nio-8080-exec-3] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
自定义的过滤器执行了!
自定义的过滤器执行了!

2. 图形验证码过滤器

        想要实现图形验证码校验功能,首先应当有一个获取图形验证码的API,绘制图形验证码的方法有很多,使用开源的验证码组件即可。例如kaptcha

(1)引入kaptcha依赖

        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>

(2)配置一个 kaptcha 实例

    @Bean
    public Producer captcha() {
        Properties properties = new Properties();
        properties.setProperty("kaptcha.image.width","150");
        properties.setProperty("kaptcha.image.hright","150");
        properties.setProperty("kaptcha.textproducer.char.string","0123456789");
        properties.setProperty("kaptcha.textproducer.char.length","4");
        Config config = new Config(properties);
        //使用默认的图形验证码实现
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }

这个代码片段是一个Spring的Java配置方法。它使用Kaptcha库创建一个实现验证码服务的Java Bean。下面是代码块的各部分解释:

  1. @Bean - 这是Spring注解,用于告诉Spring容器,要将此方法返回的对象作为bean注册到容器中。

  2. Properties - 这是一个Java类,用于管理一组键值对。这里我们使用Properties来存储Kaptcha配置属性。

  3. Config - 这是Kaptcha库中的一个Java类,它需要从上述Properties对象中加载一组Kaptcha配置属性。

  4. DefaultKaptcha - 这是Kaptcha库中的一个Java类,它实现了默认的验证码生成算法。

  5. setConfig - 这是DefaultKaptcha类中的一个setter方法,用于将从Config对象中读取的属性设置到DefaultKaptcha实例中。

  6. return defaultKaptcha - 最后,这个方法返回一个DefaultKaptcha实例,它包含了我们所需的所有配置信息,可以生成图形验证码。

(3)创建一个CaptchaController,用于获取图形验证码

@Controller
public class CaptchaController {

    @Autowired
    protected Producer captchaProducer;

    @GetMapping("/captcha.jpg")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //设置内容类型
        response.setContentType("image/jpeg");
        //创建验证码文本
        String capText = captchaProducer.createText();
        //将验证码文本设置到 session
        request.getSession().setAttribute("captcha",capText);
        //创建验证码图片
        BufferedImage bi = captchaProducer.createImage(capText);
        //获取响应输出流
        ServletOutputStream out = response.getOutputStream();
        //将图形验证码数据写道响应输出流
        ImageIO.write(bi,"jpg",out);
        try {
            out.flush();
        } finally {
            out.close();
        }
    }
}
  •  @GetMapping("/captcha.jpg") - 这是Spring MVC注解,用于将控制器方法映射到GET请求"/captcha.jpg"。
  • response.setContentType("image/jpeg"); - 这一行设置响应的内容类型为JPEG图像。
  • String capText = captchaProducer.createText(); - 这一行使用captchaProducer对象创建一个包含随机验证码文本的字符串capText。
  • request.getSession().setAttribute("captcha",capText); - 这一行将capText保存到当前HTTP会话的属性“captcha”中,以便稍后验证输入。
  • BufferedImage bi = captchaProducer.createImage(capText); - 这一行使用captchaProducer对象创建一个包含capText文本的验证码图片bi。
  • ServletOutputStream out = response.getOutputStream(); - 这一行获取HTTP响应输出流对象out,以便我们可以将验证码图像数据写入响应流。
  • ImageIO.write(bi,"jpg",out); - 这一行将bi对象的图像数据写入out输出流中。
  • out.flush(); - 这一行刷新输出流。
  • out.close(); - 最后,使用out.close()关闭输出流。

        当用户访问 /captcha.jpg时,即可得到一张携带验证码的图片,验证码文本则被存放到session中,用于后续校验。现在我们可以启动项目先看一下:

(4)用于校验验证码的过滤器

        虽然Spring Security 的过滤器链对过滤器没有特殊要求,只要继承了Filter即可,但是在Spring体系中,推荐使用OncePerRequestFilter来实现。它可以确保一次请求只会通过一次该过滤器(Filter实际上并不能保证这一点)

public class CaptchaFilter extends OncePerRequestFilter {

    private static final String CAPTCHA_SESSION_KEY = "captcha";
    private static final String CAPTCHA_PARAM_NAME = "captcha";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (request.getMethod().equalsIgnoreCase("POST")) { //只对POST请求进行验证码验证
            HttpSession session = request.getSession(false);
            if (session != null) {
                //拿到session中存放的 captcha 属性
                String captcha = (String) session.getAttribute(CAPTCHA_SESSION_KEY);
                if (captcha == null) {
                    response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "验证码已过期,请重新获取。");
                    return;
                }
                //获取输入的验证码信息
                String inputCaptcha = request.getParameter(CAPTCHA_PARAM_NAME);
                if (inputCaptcha == null || !captcha.equals(inputCaptcha.trim())) {
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "验证码错误,请重新输入。");
                    return;
                }
            } else {
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "无法验证验证码,因为HTTP会话不存在。");
                return;
            }
        }
        filterChain.doFilter(request, response);
    }
}

 这是一个验证码过滤器类,用于在用户登录等需要输入验证码的场景下,对用户输入的验证码进行验证。具体来说:

  • 在doFilterInternal()方法中,首先通过request对象获取用户请求的方法,如果是POST请求,则对验证码进行验证。
  • 然后通过request.getSession(false)获取当前的session对象,如果session对象不为null,则继续进行验证码验证;否则返回错误信息,提示无法验证验证码,因为HTTP会话不存在。
  • 验证码的具体验证流程是:先通过CAPTCHA_SESSION_KEY获取session中存放的验证码信息,如果获取到的验证码为null,则返回错误信息,提示验证码已过期,请重新获取;否则获取用户输入的验证码信息,如果用户输入的验证码为null或者与session中的验证码信息不一致,则返回错误信息,提示验证码错误,请重新输入。
  • 最后,如果验证码验证通过,则通过filterChain.doFilter()方法继续执行后续的过滤器或请求处理。 总之,这个验证码过滤器类的作用是保证用户输入的验证码正确,从而增强了系统的安全性和防范机制。

(5)spring security 配置过滤器链

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(new CaptchaFilter(),UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                //开放验证码的访问权限
                .antMatchers("/captcha.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }

(6)修改 login.html 

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login Page</title>
    <style>
        /* 样式可以自行修改 */
        body {
            background-color: cadetblue;
        }

        .login-form {
            width: 350px;
            margin: 150px auto;
            background-color: #fff;
            padding: 20px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
        }

        h1 {
            font-size: 24px;
            text-align: center;
            margin-bottom: 30px;
        }

        input[type="text"], input[type="password"], input[type="number"] {
            width: 100%;
            padding: 10px;
            margin-bottom: 20px;
            border: 2px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
        }

        button {
            background-color: darksalmon;
            color: white;
            padding: 12px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            width: 100%;
        }

        button:hover {
            background-color: #45a049;
        }

        .captcha-container {
            text-align: center;
            margin-bottom: 20px;
        }

        .captcha-img {
            width: 150px;
            height: 50px;
        }
    </style>
</head>
<body>
<div class="login-form">
    <h1>Login Page</h1>
    <form th:action="@{/login}" method="post">
        <label for="username">Username</label>
        <input type="text" id="username" name="username" placeholder="Enter username" required>

        <label for="password">Password</label>
        <input type="password" id="password" name="password" placeholder="Enter password" required>

        <div class="captcha-container">
            <img class="captcha-img" th:src="@{/captcha.jpg}" onclick="this.src='captcha.jpg?'+Math.random()">
        </div>

        <label for="captcha">Verification Code</label>
        <input type="number" id="captcha" name="captcha" placeholder="Enter verification code" required>

        <button type="submit">Login</button>
    </form>
</div>
</body>
</html>

(7)debug调试:

 

二、使用自定义认证实现图形验证码

        上面使用过滤器方式实现类带图形验证码的验证功能,属于Servlet层面,Spring Security还提供了一种更优雅的实现图形验证码的方式,即自定义认证。

1. 认识 AuthenticationProvider

        我们所面对的系统中的用户,在Spring Security中被称为主体(principal),主体包含了所有能够经过验证而获得系统访问权限的用户、设备或其他系统。Spring Security 通过一层将其定义为一个Authentication。

public interface Authentication extends Principal, Serializable {

    //获取主体权限列表
    Collection<? extends GrantedAuthority> getAuthorities();

    //获取主体凭据,通常为用户密码
    Object getCredentials();
    
    //获取主体详细信息
    Object getDetails();

    //获取主体,通常为一个用户名
    Object getPrincipal();

    //主体是否验证成功
    boolean isAuthenticated();

    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

        UsernamePasswordAuthenticationToken也是Authentication的一个实现类。         

        大部分情况下身份验证都是基于用户名和密码进行的,所以 Spring Security提供了一个UsernamePasswordAuthenticationToken 用于代指这一类证明,例如用SSH KEY也可以登录,但它不属于用户名和密码登录这个范畴。

        在前面使用的表单登录中,每一个登录用户被包装为一个UsernamePasswordAuthenticationToken,从而在Spring Security的各个AuthenticationProvider中流动。

        AuthenticationProvider 被Spring Security定义为一个验证过程,一次完整的认证可以包含多个AuthenticationProvider,一般由ProviderManager管理。

public interface AuthenticationProvider {
//验证过程,成功的话返回一个验证完成的Authentication
    Authentication authenticate(Authentication authentication) throws AuthenticationException;

    boolean supports(Class<?> authentication);
}

2. 自定义AuthenticationProvider

        Spring Security提供了多种常见的认证技术,包括但不限于以下几种:

  • HTTP层面的认证技术,包括HTTP基本认证和HTTP摘要认证两种
  • 基于LDAP的认证技术
  • 聚焦于证明用户身份的OpenID认证技术
  • 聚焦于授权的OAuth认证技术
  • 系统内维护的用户名和密码认证技术(最广泛)

         系统内维护的用户名和密码认证技术使用最为广泛,通常会涉及数据库访问。为了更好的按需定制,Spring Security并没有直接糅合整个认证过程,而是提供了一个抽象的AuthenticationProvider,那就是 AbstractUserDetailsAuthenticationProvider

 

AbstractUserDetailsAuthenticationProvider是Spring Security提供的抽象类,用于支持验证用户身份,并从用户详细信息中构建身份验证对象

该类实现了AuthenticationProvider接口,可以作为身份验证的提供者。同时,它还实现了UserDetailsService接口,用于从数据源中获取用户详细信息。

在具体的实现中,AbstractUserDetailsAuthenticationProvider的主要作用是将Authentication对象中的用户名和密码提取出来,然后通过UserDetailsService获取用户详细信息,并将其与用户名和密码进行比较,以确定用户的身份是否正确。

如果身份验证成功,AbstractUserDetailsAuthenticationProvider会将用户详细信息封装到一个新的身份验证对象中,并将其返回。如果身份验证失败,则会抛出一个AuthenticationException异常。

此外,AbstractUserDetailsAuthenticationProvider还可以处理密码加密和解密的逻辑,以及支持定制化的身份验证逻辑。

实现:自定义AuthenticationProvider示例

public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        // 在此处实现密码验证逻辑
        if (!authentication.getCredentials().equals(userDetails.getPassword())) {
            throw new BadCredentialsException("Invalid username or password");
        }
    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        // 在此处从自定义的用户详情服务中获取用户信息
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        if (userDetails == null) {
            throw new UsernameNotFoundException("User not found with username: " + username);
        }
        return userDetails;
    }

}

 2. 实现图形验证码的AuthenticationProvider

        现在重新回到自定义认证实现图形验证码登录的这个案例中,由于只是在常规的认证之上增加了图形验证码的校验,其他流程并没有变化,所以只需要继承DaoAuthenticationProvider并稍微修改即可。

public class MyAuthenticationProvider extends DaoAuthenticationProvider {

    public MyAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        this.setUserDetailsService(userDetailsService);
        this.setPasswordEncoder(passwordEncoder);
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        //实现图形验证码的校验逻辑

        //调用父类方法完成密码验证
        super.additionalAuthenticationChecks(userDetails, authentication);
    }
}

        用户提交的验证码和session存储的验证码都需要从用户的请求中获取,但是传入的对象只有userDetails 和 authentication。是否还需要一个HttpServletRequest对象呢?并非如此,Authentication实际上还可以携带账号信息之外的数据。

public interface Authentication extends Principal, Serializable {

    //允许携带任意对象 Object类型
    Object getDetails();

}

        前面提到过,一次完整的认证可以包含多个AuthenticationProvider,这些AuthenticationProvider都由ProviderManager管理,ProviderManager是由UsernamePasswordAuthenticationFilter调用的。

AuthenticationProviderProviderManager介绍:

        AuthenticationProvider和ProviderManager是Spring Security框架中用来实现认证的两个关键组件。

        AuthenticationProvider是一个接口,它定义了认证的核心逻辑,即对用户提供的认证信息进行验证。其中包含一个方法authenticate(),用于执行认证操作。在Spring Security中,一般情况下需要自定义实现AuthenticationProvider接口,并将其添加到ProviderManager中,以完成对认证请求的处理。

        ProviderManager是一个认证管理器,它负责协调多个AuthenticationProvider实现,实现对认证请求的分派、调度和结果处理。当认证请求到达ProviderManager时,它会遍历其内部持有的AuthenticationProvider实现列表,逐一尝试调用各个AuthenticationProvider的authenticate()方法,直到其中一个AuthenticationProvider能够成功认证该请求,或者所有AuthenticationProvider都无法认证成功。

        总体来说,AuthenticationProvider和ProviderManager是Spring Security框架中非常重要的两个组件,它们共同协作,实现了对用户身份的验证和认证。通过实现AuthenticationProvider接口,可以对认证逻辑进行个性化定制,而通过使用ProviderManager,可以方便地协调多个AuthenticationProvider实现,以实现更加复杂的认证方案。

AuthenticationProviderProviderManagerUsernamePasswordAuthenticationFilter的关系:

        AuthenticationProvider和ProviderManager都是与身份验证相关的组件,而UsernamePasswordAuthenticationFilter是处理身份验证请求的过滤器。

        当用户提交身份验证请求时,UsernamePasswordAuthenticationFilter拦截请求并从请求中获取用户名和密码等身份验证信息,然后创建一个UsernamePasswordAuthenticationToken对象来表示这些信息,并将其传递给ProviderManager的authenticate()方法进行身份验证。

         ProviderManager通过遍历已配置的AuthenticationProvider列表来查找可以处理该UsernamePasswordAuthenticationToken的AuthenticationProvider。如果找到了匹配的AuthenticationProvider,则该提供程序会对身份验证信息进行验证,并返回一个经过身份验证的Authentication对象,ProviderManager将该Authentication对象传递给UsernamePasswordAuthenticationFilter,以表明身份验证已成功。

        如果ProviderManager无法找到匹配的AuthenticationProvider,或者已找到但是无法验证身份验证信息,则ProviderManager将抛出一个AuthenticationException异常,UsernamePasswordAuthenticationFilter将捕获该异常并处理身份验证失败的情况。

如图:

 

         Authentication中有了HttpServletRequest之后,一切变得非常顺畅,基于图形验证码的场景,我们可以继承WebAuthenticationDetails,并扩展需要的信息。

public class MyWebAuthenticationDetails extends WebAuthenticationDetails {

    private boolean imageCodeIsRight;

    public boolean getImageCodeIsRight() {
        return this.imageCodeIsRight;
    }
    //补充用户提交的验证码和session保存的验证码
    public MyWebAuthenticationDetails(HttpServletRequest request) {
        super(request);
        String imageCode = request.getParameter("captcha");
        HttpSession session = request.getSession();
        String savedImageCode = (String)session.getAttribute("captcha");
        if (!StringUtils.isEmpty(savedImageCode)) {
            session.removeAttribute("captcha");
            //当验证码正确时设置状态
            if (!StringUtils.isEmpty(imageCode) && imageCode.equals(savedImageCode)) {
                this.imageCodeIsRight = true;
            }
        }
    }
}

将它提供给一个自定义的AuthenticationDetailsSource

public class MyWebAuthenticationDetailsSource implements 
        AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
    @Override
    public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new MyWebAuthenticationDetails(context);
    }
}

 接下来实现自定义的AuthenticationProvider

public class MyAuthenticationProvider extends DaoAuthenticationProvider {

    public MyAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
        this.setUserDetailsService(userDetailsService);
        this.setPasswordEncoder(passwordEncoder);
    }

    @SneakyThrows
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        //实现图形验证码的校验逻辑
        //获取详细信息
        MyWebAuthenticationDetails details = (MyWebAuthenticationDetails) authentication.getDetails();
        //一旦发现验证码不正确,就立刻抛出异常信息
        if (!details.getImageCodeIsRight()) {
            throw new VerificationCodeException();
        }
        //调用父类方法完成密码验证
        super.additionalAuthenticationChecks(userDetails, authentication);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/413487.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[论文速览] Sparks of Artificial General Intelligence: Early experiments with GPT-4

Sparks of Artificial General Intelligence: Early experiments with GPT-4 2023.3.22 微软官方发布了目前人类史上最强AI模型 GPT-4 的综合能力评估论文&#xff0c;总所周知&#xff0c;2023年是通用人工智能&#xff08;Artificial General Intelligence&#xff0c;AGI&a…

18从零开始学Java之switch分支语句中该怎么用?

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 在上一篇文章中&#xff0c;壹哥给大家介绍了Java里的顺序、分支、循环结构的概念&#xff0c;并且重点给大家讲解了分支结…

WebSocket和Socket编程

面试一般会问 socket原理 socket为啥要有端口&#xff1a;确定一个链路的是一个四元组 Socket编程 socket通信 你做过socket编程吗&#xff0c;socket是怎么实现知道有连接过来的 说说WebSocket与socket的区别 先谈谈Websocket与Socket的区别 首先Socket 不属于协议范畴 &a…

处理用户输入

shell脚本编程系列 传递参数 向shell脚本传递数据的最简单方法是使用命令行参数 比如 ./add 10 30读取参数 bash shell会将所有的命令行参数都指派给位置参数的特殊变量。其中$0对应脚本名、$1是第一个参数、$2是第二个参数&#xff0c;依次类推&#xff0c;直到$9 #!/bin/b…

Unity3D打包WebGL并使用MQTT(二):使用json

Unity3D打包WebGL并使用MQTT(二):使用json 1. 软件环境 Unity: 2021.3stomp.js 2.3.3: 下载地址:https://www.jsdelivr.com/package/npm/stompjs 2. 内容介绍 这篇博客的主要内容是记录将一个Unity项目打包成WebGL项目&#xff0c;并集成MQTT进行json数据传输的过程。 3. …

〖Python网络爬虫实战⑬〗- XPATH实战案例

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;目前专栏免费订阅&#xff0c;在转为付费专栏前订阅本专栏的&#xff0c;可以免费订阅付费…

一篇文章理解堆栈溢出

一篇文章理解堆栈溢出引言栈溢出ret2text答案ret2shellcode答案ret2syscall答案栈迁移答案堆溢出 unlink - UAF堆结构小提示向前合并/向后合并堆溢出题答案引言 让新手快速理解堆栈溢出&#xff0c;尽可能写的简单一些。 栈溢出 代码执行到进入函数之前都会记录返回地址到SP…

wsl区分和切换,安装NVIDIA驱动+cuda+ffmpeg

wsl区分和切换&#xff0c;安装NVIDIA驱动cudaffmpeg 安装Nvidia驱动 打开terminal wsl --update进入wsl nvidia-smi网上找了一些博客&#xff0c;获取信息&#xff1a; window安装好驱动即可wsl有1和2 我的win10已经安装了驱动 wsl1和wsl怎么区分&#xff1f;切换 区分…

实时聊天提示的最佳做法

本文将教您更多关于有效的实时聊天功能对您的品牌的重要性&#xff0c;以及您可以使用的一些最佳实践来确保您的实时聊天功能尽可能好。实时聊天提示是为您的网站访问者显示的自动聊天消息。在SaleSmartly&#xff08;ss客服&#xff09;中&#xff0c;如您将聊天插件安装到您的…

Win10 安装 MongoDB 5.0.16

一、MongoDB 的官方&#xff1a;http://www.mongodb.com/ 下载&#xff1a;​ ​https://www.mongodb.com/download-center/community​ 二、下载msi文件&#xff0c;双击该文件进行安装。 &#xff08;1&#xff09;打开对话框 ,单击“Next” &#xff08;2&#xff09;请勾…

总结815

4月&#xff08;复习完高数18讲内容&#xff0c;背诵21篇短文&#xff0c;熟词僻义300词基础词&#xff09; 4.8 英语&#xff1a;早上继续背昨天没背完的第15篇文章单词&#xff0c;感觉效率极高&#xff0c;可能是昨晚睡眠质量特别好。下午抄写第16篇文章每日一句长难句看《…

Meetup 直播预告|助力企业数字化转型,8 大微服务容器开源实践亮点抢先看

随着数字化、智能化发展趋势不断加快&#xff0c;大中小型企业纷纷将企业“上云”提上日程&#xff0c;推动企业数字化转型。云时代下&#xff0c;企业需要新技术架构&#xff0c;使之更好地利用云计算优势&#xff0c;让业务更敏捷、成本更低、可伸缩性更强&#xff0c;云原生…

进程概念详解

目录 进程是什么&#xff1f; 描述进程&#xff1a;进程控制块-PCB task_struct task_struct 是什么&#xff1f; task_struct内容分类 组织进程 查看进程 fork创建子进程 进程状态 僵尸进程 孤儿进程 进程优先级 其他概念 进程是什么&#xff1f; 一般书上…

【微信小程序】父子组件之间传值

微信小程序父子组件之间传值有两种&#xff1a; 1.父组件向子组件传值 2.子组件向父组件传值 区别&#xff1a; 父向子传值使用的是属性绑定&#xff0c;子组件中的properties对象进行接收父组件传递过来的值。子向父传值使用的是自定义事件&#xff0c;父组件通过自定义事件…

VUE 学习笔记(一)开发环境搭建

1、Visual Studio Code安装及使用 下载地址官网&#xff1a;https://code.visualstudio.com/ 直接点击下载按钮即可&#xff0c;会根据系统自动下载合适的版本&#xff0c;无需自行选择。 2、VSCode 上安装&#xff1a;JavaScript Debugger 目前 Debugger for Chrome 已经处…

基于html+css的盒子内容旋转

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

【目标检测论文阅读笔记】Reducing Label Noise in Anchor-Free Object Detection

&#xff08;Augmentation for small object detection&#xff09; Abstract 当前的 anchor-free无锚目标检测器 将空间上落在真值框预定义中心区域内的所有特征标记为正。这种方法会在训练过程中产生 标签噪声&#xff0c;因为这些 正标记的特征中的一些 可能位于背景或遮挡…

2023最新8个电脑必装软件,新电脑装完好用又高效

新买的笔记本电脑到手了&#xff0c;需要安装什么软件&#xff1f;不会真的有人这样问吧&#xff0c;万一真的有人不知道需要安装什么软件呢&#xff1f;好吧&#xff0c;提醒一下各位&#xff0c;新电脑不要乱安装软件啊&#xff0c;不然电脑很容易中病毒的。根据我多次换电脑…

MyBatis配置文件 —— 相关标签详解

目录 一、Mybatis配置文件 — properties标签 二、Mybatis配置文件 — settings标签 三、Mybatis配置文件 — plugins标签 四、Mybatis配置文件 — typeAliases标签 五、Mybatis配置文件 — environments标签 六、Mybatis配置文件 — mappers标签 一、Mybatis配置文件 —…

burp抓包https链接不安全解决方法

在浏览器已经导入Burpsuite的证书之后,抓包,浏览器仍然显示抓取https包提示不是私密链接解决方法 适用于没有继续访问的按钮。 方法一: 浏览器输入 chrome://flags 搜索 Allow invalid certificates for resources loaded from localhost.翻译过来就是 允许从本地主机加载资…