Java-Security-1

JWT详解​​​​​​​

1、SpringSecurity

1.1 简介
  • Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架 Shiro ,它提供了更丰富的功能,社区资源也比 Shiro 丰富。

  • 一般来说中大型的项目都是使用 SpringSecurity 来做安全框架。小项目有 Shiro 的比较多,因为相比与SpringSecurity ,Shiro 的上手更加的简单。

1.2 作用
  • 认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户

  • 授权:经过认证后判断当前用户是否有权限进行某个操作

  • jwt只能验证是否有登入,无法验证是否有权限访问某个接口

2、认证

mage-20221102141344515.png)
2.2 SpringSecurity认证流程

3、SpringSecurity的使用

3.1 环境搭建
  • 依赖导入

    本次用到的依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- jwt依赖-->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.10.3</version>
    </dependency>
    <!--redis依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--mybatisplus依赖-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    <!--mysql依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.29</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>

  • yml配置修改
    spring:
      redis:
        host: 127.0.0.1
        port: 6379
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
                                         这里写上自己的数据库
        url: jdbc:mysql://localhost:3306/springboot-mysql?userSSL=false;serverTimezone=Asia/Shanghai
        username: root
        password: 123456
    mybatis-plus:
      config-locations: classpath:mapper/*.xml
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • 启动类修改
    @MapperScan("com.example.demo.mapper")
  • 实体类
  • jwt工具类
  • Redis配置类
  • Redis工具类
  • 返回类

    这几个工具类都可以从以前的代码中获取,这里就不一一显示了

3.2 项目使用测试

这些配置好,security就可以直接使用了

但直接访问自己的接口,会被重定向到登入页的位置

打开项目地址会跳转到自带的登录页面

 

  • 接口不能直接使用
  • 需要先使用页面打开进行一个登录(用户名:user,密码会在控制台自动生成
  • 每次重启密码都不一样)
  • 登录之后才能够请求接口
  • 登入后可以发现请求头里会有JSESSIONID
  • 这就是默认的一个认证流程

测试成功则表示我们的项目创建成功

4、自定义登录

结合完整流程可以知道整个流程中从第5、6之间进行用户查询,所以我们只需要去重写一下这一部分就可以了

  • 创建 sys_user 表的Mapper和Service、entity、并集合plus
  • 实体类如果名字与数据库不一样,要用注解@TableName指定实体类名称
  • 新建实现类UserDetailsServiceImpl
  • package com.example.demo.service.impl;
    
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.example.demo.entity.LoginUser;
    import com.example.demo.entity.MsUser;
    import com.example.demo.service.IUserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;
    
    // 实现UserDetailsService,重写loadUserByUsername
    // 重写了UserDetailsService,控制台就没有打印生成的密码。因为我们自定义了登录流程
    
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
    
        @Autowired
        private IUserService userService;
    
        // UserDetails: security存放登录用户信息
        //loadUserByUsername是secruity对外开放的接口,让我们来实现(每个系统获取账号的逻辑不一样)
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            System.out.println("loadUserByUsername");
            LambdaQueryWrapper<MsUser> qw = new LambdaQueryWrapper<>();
            qw.eq(MsUser::getUsername, username);
            // 根据账号查询用户信息
            MsUser msUser = userService.getOne(qw);
    
            // TODO: 统一处理异常
            if(msUser == null) {
                throw new RuntimeException("账号不存在");
            }
    
            //要返回UserDetails对象,它是一个接口,不能直接new
            //所以可以创建一个类实现UserDetails,然后再创建这个类的对象返回
    
            LoginUser loginUser = new LoginUser();
            loginUser.setMsUser(msUser);
    
            //返回一个用户对象
            return loginUser;
        }
    
    }
    
    package com.example.demo.entity;
    
    import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
    import lombok.Data;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.Collection;
    
    // UserDetails:后续security用于用户验证使用的
    @Data
    // 解决后续redis读取数据时反序列化报错
    @JsonIgnoreProperties(ignoreUnknown = true)
    public class LoginUser implements UserDetails {
    
        //引入我们要返回的实体类
        private MsUser msUser;
    
    
        //重写底下这些方法
        @Override
        //这是用户的权限接口
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return null;
        }
    
        @Override
        //账号接口
        public String getPassword() {
            return msUser.getPassword();
        }
    
        @Override
        //密码接口
        public String getUsername() {
            return msUser.getUsername();
        }
    
        @Override
        //是否过期接口
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        //是否被锁定接口
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        //用于判断用户凭证是否过期
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        //用户判断用户是否可用
        public boolean isEnabled() {
            return true;
        }
    }
    

  • 重新登录,发现后台报错了
  • 默认要求密码前面加{noop}
  • 再次登录成功,一定一定一定要记得上面的两个get要重写

4.1 密码加密

第7步通过PasswordEncoder对比UserDetails中的密码和Authentication中的密码是否一样

但是开真实开发中我们的密码不可能以明文的形式保存在数据库中,会对其进行加密

  • 默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password 。它会根据id去判断密码的加密方式。但是我们一般不会采用这种方式。所以就需要替换PasswordEncoder

  • 我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder

创建配置类(替换加密方式)
/**
 * SpringSecurity的配置类要求继承WebSecurityConfigurerAdapter
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
测试类加密密码(生成BCryptPasswordEncoder加密的密码)
package com.example.demo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@SpringBootTest
class DemoApplicationTests {

    @Test
    void contextLoads() {
        // 生成加密的密码
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode("需要加密的密码");
        System.out.println(encode);
    }

}

 

启动后将控制台生成的加密后的密码,把数据库里要加密的密码替换掉,重新启动后就可以正常登入了

4.4 自定义登录接口

SpringSecurity会默认把接口进行一个认证,但是很多时候我们需要自己编写登录接口,但是这个时候登录接口就会被默认的配置拦截,所以需要放行登录接口

由于第3步,会在接口中通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在上面的SecurityConfig中配置把AuthenticationManager注入容器。

认证成功的话要生成一个jwt,放入响应中返回。并且为了让用户下回请求时能通过jwt识别出具体的是哪个用户,我们需要把用户信息存入redis,可以把用户id作为key。

配置类
package com.example.demo.config;

import com.example.demo.filter.JWTFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JWTFilter jwtFilter;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            //关闭csrf
            .csrf().disable()
            //不通过Session获取SecurityContext,这里写了之和就不会跳到登入系统自带那个页面
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            // 对于登录接口 允许匿名访问
            .antMatchers("/login").anonymous()
            // 除上面外的所有请求全部需要鉴权认证
            .anyRequest().authenticated();

        http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
    }

    //将权限管理器注入(默认没有注入),后面会使用到
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
 Service
public interface IUserService extends IService<MsUser> {

    String createToken(String username, String password);

}
ServiceImpl 
package com.example.demo.service.impl;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.LoginUser;
import com.example.demo.entity.MsUser;
import com.example.demo.mapper.UserMapper;
import com.example.demo.service.IUserService;
import com.example.demo.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, MsUser>
    implements IUserService {

    //将SecruityConfig的权限管理器注入
    @Autowired
    private AuthenticationManager authenticationManager;

    //将Redis的配置类也注入
    @Autowired
    private RedisUtils redisUtils;

    //账号密码在UserDetailsServiceImpl已经查过了,心啊在只需要验证有没有token就行
    @Override
    public String createToken(String username, String password) {

        // 封装账号密码
        UsernamePasswordAuthenticationToken authenticationToken
                = new UsernamePasswordAuthenticationToken(username, password);

        // 调用UserServiceImpl里面的loadUserByUsername方法
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);

        if(authenticate == null) {
            throw new RuntimeException("认证失败");
        }

        // getPrincipal()返回登录对象
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();

        //将用户名加密并生成token
        String token = JWT.create().withClaim("userId", loginUser.getMsUser().getId())
                .sign(Algorithm.HMAC256("asdghfgagdsfgds"));

        // user:2
        // 将用户信息存入redis,方便后期校验使用
        System.out.println("user:" + loginUser.getMsUser().getId().toString());
        redisUtils.setCacheObject("user:" + loginUser.getMsUser().getId().toString(), loginUser);

        return token;
    }
}
 controller
package com.example.demo.controller;

import com.example.demo.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/login")
public class LoginController {

    @Autowired
    private IUserService userService;

    @PostMapping
    public Map<String, Object> login(String username, String password) {
        String token = userService.createToken(username, password);

        Map<String, Object> map = new HashMap<>();
        map.put("code", 200);
        map.put("msg", "操作成功");
        map.put("data", token);

        return map;
    }

}

5.JWT验证

现在只有登入接口可以正常使用,要弄一个携带token可以访问其他接口的方法

继承OncePerRequestFilter过滤器,重写它的方法doFilterInternal

package com.example.demo.filter;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.example.demo.entity.LoginUser;
import com.example.demo.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/*
实现,除了登入接口,其他接口要有token才能访问
 */

@Component
public class JWTFilter extends OncePerRequestFilter {

    @Autowired
    private RedisUtils redisUtils;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        System.out.println("jwtFilter");
        String token = request.getHeader("token");

          // 如果没有token进入下一个过滤器
          if(token == null) {
              //后面的过滤器也需要用到用户信息,这边先暂时放行
              filterChain.doFilter(request, response);
              //放行后,后面的内容就不能在执行了,需要return掉
              return;
          }

          //有token的话,要验证token的合法性
          try {
              JWT.require(Algorithm.HMAC256("asdghfgagdsfgds"))
                      .build().verify(token);
          } catch (Exception e) {
              e.printStackTrace();
              // todo: 异常捕捉
              throw new RuntimeException("token不合法");
          }

          //token合法的话,就把用户信息存起来然后放行
          // 获取用户ID
         //每个接口都会经过这里,如果每次都通过userId去数据库查用户信息的话,会对数据库的压力很大
         Long userId = JWT.decode(token).getClaim("userId").asLong();

        //所以建议放在缓存中拿,所以需要导入RedisUtils和RedisConfig
         LoginUser loginUser = redisUtils.getCacheObject("user:" + userId);

         //验证token没问题的话,要把用户信息存入到Secuirity的容器里,后面的过滤器还会用到
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, null);

        //把当前用户信息放入security容器里
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        filterChain.doFilter(request, response);
    }
}

添加到整个过滤器链中

        在SecurityConfig重写的configure方法里加上我们的过滤器

@Autowired
private JWTFilter jwtFilter;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http// 将自己定义的过滤器加到UsernamePasswordAuthenticationFilter之前
        .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

金蝶EAS打印凭证,数据量多点的就会出错

金蝶EAS打印凭证&#xff0c;数据量多点的就会出错&#xff0c;约过100页&#xff0c;提示数据源有问题 经咨询工程师需修改java虚拟机内存。 打开eas客户端目录&#xff0c;运行set-url.bat 看到原来java虚拟机只配置了512M内存&#xff0c;把虚拟机内存修改为4096&#xff0…

基于SSM的学生在线考试系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

sql学习笔记(四)

1. 避免字段名与关键字冲突 1️⃣ 2️⃣ as 设置别名 2. 同比与环比 「同比」&#xff1a;与历史「同时期&#xff3d;比较&#xff0c;例如2011年3月份与2010年3月份相比&#xff0c;叫「同比」。 「环比」&#xff1a;与「上一个」统计周期比较&#xff0c;例如2011年4…

Postman测试文件上传接口

文章目录 一、Postman测试文件上传接口1、创建一个请求→选择请求方式→填写请求的URL→填写请求的参数值2、选择 "Body" → "form-data" → "Test" → "File"3、点击 "Select Files"4、选择要上传的文件5、点击 Send 进行测…

10 Vue3中v-html指令的用法

概述 v-html主要是用来渲染富文本内容&#xff0c;比如评论信息&#xff0c;新闻信息&#xff0c;文章信息等。 v-html是一个特别不安全的指令&#xff0c;因为它会将文本以HTML的显示进行渲染&#xff0c;一旦文本里面包含一些恶意的js代码&#xff0c;可能会导致整个网页发…

Unresolved plugin: ‘org.apache.maven.plugins‘解决报错

新建springboot项目报Unresolved plugin: ‘org.apache.maven.plugins:maven-surefire-plugin:3.1.2’ 缺什么插件 引入什么插件的依赖就行 <dependency><groupId>org.apache.maven.plugins</groupId><artifactId>maven-install-plugin</artifact…

设计模式03结构型模式

结构型模式 参考网课:黑马程序员Java设计模式详解 博客笔记 https://zgtsky.top/ 结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式&#xff0c;前者采用继承机制来组织接口和类&#xff0c;后者釆用组合或聚合来组合对象。 由于…

【Unity】【WebRTC】如何用Unity而不是浏览器接收远程画面

【背景】 之前几篇我们讨论了如何设置信令服务器&#xff0c;如何发送画面给远端以及如何用浏览器查看同步画面&#xff0c;今天来讨论如何实现Unity内部接收画面。 看本篇之前请先看过之前将web服务器设置和基本远程画面功能的几篇博文。&#xff08;同专栏下查看&#xff09…

链接未来:深入理解链表数据结构(一.c语言实现无头单向非循环链表)

在上一篇文章中&#xff0c;我们探索了顺序表这一基础的数据结构&#xff0c;它提供了一种有序存储数据的方法&#xff0c;使得数据的访 问和操作变得更加高效。想要进一步了解&#xff0c;大家可以移步于上一篇文章&#xff1a;探索顺序表&#xff1a;数据结构中的秩序之美 今…

springboot整合kafka附源码

前提&#xff1a;确保kafka环境 我使用的方案是docker 我使用的镜像为&#xff1a;wurstmeister/kafka 我使用的镜像为&#xff1a;wurstmeister/zookeeper docker安装kafka和zk教程&#xff1a;点这里手把手教你使用Docker搭建kafka【详细教程】 使用kafka前&#xff0c;要确…

字符串函数内存函数(从零到一)【C语言】

长度不受限制的字符串函数&#xff1a;strcpy,strcat,strcmp 长度受限制的字符串函数&#xff1a;strncpy,strncat,strncmp strlen strlen函数是库函数中我们最常使用的函数&#xff0c;它可以求出字符串的长度(不包含‘\0’) 使用方法 通过前面对strlen函数的模拟实现我们知…

考研小白助力宝典(2)

前言 考研&#xff0c;是一场耗时长久的脑力之战&#xff0c;刻苦勤奋的态度和披荆斩棘的精神外&#xff0c;往往取决于谁抓好了信息利剑&#xff01;合理得当利用好信息平台&#xff0c;就已经快人一步战胜了大部分的竞争对手了&#xff01; 目录 着重学习练习 考研相关简介 …

小白向攻略简单易懂,怎么用DomoAI将手机里面的视频转换成丝滑流畅高帧数的动画

Domo AI是一款强大的人工智能工具&#xff0c;支持图像和视频的重新创作。它拥有超过10个多样化的预设模型&#xff0c;使用户能够轻松实现一致且统一的艺术风格。 在图像生成方面&#xff0c;Domo AI能够快速将照片转换成动漫或现实风格&#xff0c;同时还支持将素描或线稿重…

关于“Python”的核心知识点整理大全33

目录 12.8.3 将子弹存储到编组中 alien_invasion.py 注意 12.8.4 开火 game_functions.py 12.8.5 删除已消失的子弹 alien_invasion.py 12.8.6 限制子弹数量 settings.py game_functions.py 12.8.7 创建函数 update_bullets() game_functions.py alien_invasion.py…

redhawk中short引起的ir drop为0的情况

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 问题如图&#xff0c;顶层在做redhawk分析时读了top及block的def&#xff0c;但top def中并没有把block pg pin写到top 的pg net下&#xff0c;导致redhawk认为有short存在&…

获投1050万欧元!德国量子公司Kipu Quantum成功研发特定压缩算法

​&#xff08;图片来源&#xff1a;网络&#xff09; 近日&#xff0c;德国量子软件公司Kipu Quantum宣布成功完成种子轮融资&#xff0c;融资总额达1050万欧元&#xff08;约合8000万人民币&#xff09;。该初创公司目前已开发出运行高性能量子计算机所需的压缩算法。该算法…

迎接新技术挑战,JFrog发布与升级系列黑科技技术产品

作为开发者&#xff0c;我们十分关注软件开发的全生命周期&#xff0c;有一家企业也同样关注软件交付和流式软件&#xff0c;致力创造从开发人员到设备之间畅通无阻的软件交付世界。它便是 JFrog&#xff0c;自2008年成立以来&#xff0c;目前已在全球范围内拥有7200家客户&…

2024年天津理工大学中环信息学院专升本报名考务费网上缴费说明

2024年天津理工大学中环信息学院高职升本考试报名考务费网上缴费说明 通过资格审核的考生&#xff0c;须在2023年12月22日至12月25日每天8:00-17:00完成考务费缴费&#xff0c;网上缴费参照津发改价费【2020】371号文件&#xff0c;左右考试考务费&#xff1a;80元&#xff08…

Apache ShenYu 网关JWT认证绕过漏洞 CVE-2021-37580

Apache ShenYu 网关JWT认证绕过漏洞 CVE-2021-37580 已亲自复现 漏洞名称漏洞描述影响版本 漏洞复现环境搭建漏洞利用 修复建议总结 Apache ShenYu 网关JWT认证绕过漏洞 CVE-2021-37580 已亲自复现) 漏洞名称 漏洞描述 Apache ShenYu是一个异步的&#xff0c;高性能的&#x…

如何在 FastAPI 中设置定时任务:完全指南

Web 应用程序开发中&#xff0c;及时高效处理常规任务至关重要&#xff0c;包括定时收集数据或管理任务计划。针对强大且性能卓越的 FastAPI 框架&#xff0c;我们可以通过几种策略来管理这些必要的定时任务。 实现 FastAPI 中的定时任务 本指南将探讨在 FastAPI 环境中管理定…