java - SpringBoot3.x接入Security6.x实现JWT认证

java - SpringBoot3.x接入Security6.x实现JWT认证

文章目录

  • java - SpringBoot3.x接入Security6.x实现JWT认证
  • 一、引言
  • 二、环境
  • 三、Maven依赖
  • 四、认识JWT
    • 1. JWT组成
  • 五、认识Security6.x
    • 1. 和旧版本的区别(Security5.7以前的版本)
    • 2. Security6.x的默认筛选器
    • 3. 注册SecurityFilterChain
  • 六、基于OncePerRequestFilter自定义JWT认证筛选器
    • 1. 标记认证成功
  • 七、遇到的问题
    • 1. 加入Security6后,一直出现登录页
    • 2. 配置完匿名访问的URL后,仍然执行自定的筛选器
  • 八、完成JWT认证的主要代码
    • 1. JwtUtil
    • 2. JwtTokenFilter
    • 3. SecuritConfig
  • 总结

一、引言

SpringBoot3.x的安全默认依赖Security6.x,Security6.x于Security5.7以前的配置有了很大区别。我们将深入探讨这两个版本之间的差异,以及它们如何影响现代Web应用的安全架构。特别是,我们将重点分析JWT(JSON Web Tokens)过滤器的工作原理,以及它是如何与匿名访问相结合,为应用提供更加灵活的安全控制。

二、环境

  • JDK 17
  • SpringBoot 3.2
  • Security 6.3

三、Maven依赖

<!-- Security安全 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>3.2.2</version>
</dependency>
<!-- jwt接口认证 -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.4.0</version>
</dependency>      

四、认识JWT

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

1. JWT组成

JSON Web Token由三部分组成,它们之间用圆点(.)连接,一个典型的JWT看起来是这个样子的:

  • 第一部分:header典型的由两部分组成:token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等),然后,用Base64对这个JSON编码就得到JWT的第一部分。
  • 第二部分:payload它包含声明(要求),声明是关于实体(通常是用户)和其他数据的声明。
  • 第三部分:签名是用于验证消息在传递过程中有没有被更改,并且对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。

注意:不要在JWT的payload或header中放置敏感信息,除非它们是加密的。

{
 alg: "RS256"
}.
{
//存储自定义的用户信息,属性可以自定扩充
 login_name: "admin",
 user_id: "xxxxx",
 ...
}.
[signature]
  • 请求header应该是这样的:Authorization: Bearer

五、认识Security6.x

1. 和旧版本的区别(Security5.7以前的版本)

SpringBoot3中默认Security升级到了6.x写法上发生了很大的变化,最显著的变化之一就是对WebSecurityConfigurerAdapter类的使用方式的改变。这个类在 Spring Security 中被广泛用于自定义安全配置。以下是主要的差异和写法上的变化:

  • 废弃WebSecurityConfigurerAdapter:

在Security5.x 版本中,WebSecurityConfigurerAdapter 是实现安全配置的常用方法。用户通过继承这个类,并覆盖其方法来自定义安全配置。到了 Spring Security 6.x,WebSecurityConfigurerAdapter 被标记为过时(deprecated),意味着它可能在未来的版本中被移除。这一变化是为了推动使用更现代的配置方法,即使用组件式配置。

  • 新版本建议使用组件式配置:

在 Spring Security 6.x 中,推荐使用组件式配置。这意味着你可以创建一个配置类,该类不再需要继承 WebSecurityConfigurerAdapter。
你可以直接定义一个或多个 SecurityFilterChain Bean来配置安全规则。这种方式更加灵活,并且与 Spring Framework 的整体风格更加一致。

2. Security6.x的默认筛选器

支持的所有筛选器在spring-security-config-6.2.1.jar包的org.springframework.security.config.annotation.web.builders.FilterOrderRegistration类的构造函数中定义,并确定了执行顺序。

FilterOrderRegistration() {
    Step order = new Step(INITIAL_ORDER, ORDER_STEP);
    put(DisableEncodeUrlFilter.class, order.next());
    put(ForceEagerSessionCreationFilter.class, order.next());
    put(ChannelProcessingFilter.class, order.next());
    order.next(); // gh-8105
    put(WebAsyncManagerIntegrationFilter.class, order.next());
    put(SecurityContextHolderFilter.class, order.next());
    put(SecurityContextPersistenceFilter.class, order.next());
    put(HeaderWriterFilter.class, order.next());
    put(CorsFilter.class, order.next());
    put(CsrfFilter.class, order.next());
    put(LogoutFilter.class, order.next());
    this.filterToOrder.put(
            "org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",
            order.next());
    this.filterToOrder.put(
            "org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter",
            order.next());
    put(X509AuthenticationFilter.class, order.next());
    put(AbstractPreAuthenticatedProcessingFilter.class, order.next());
    this.filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order.next());
    this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",
            order.next());
    this.filterToOrder.put(
            "org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter",
            order.next());
    put(UsernamePasswordAuthenticationFilter.class, order.next());
    order.next(); // gh-8105
    put(DefaultLoginPageGeneratingFilter.class, order.next());
    put(DefaultLogoutPageGeneratingFilter.class, order.next());
    put(ConcurrentSessionFilter.class, order.next());
    put(DigestAuthenticationFilter.class, order.next());
    this.filterToOrder.put(
            "org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter",
            order.next());
    put(BasicAuthenticationFilter.class, order.next());
    put(RequestCacheAwareFilter.class, order.next());
    put(SecurityContextHolderAwareRequestFilter.class, order.next());
    put(JaasApiIntegrationFilter.class, order.next());
    put(RememberMeAuthenticationFilter.class, order.next());
    put(AnonymousAuthenticationFilter.class, order.next());
    this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",
            order.next());
    put(SessionManagementFilter.class, order.next());
    put(ExceptionTranslationFilter.class, order.next());
    put(FilterSecurityInterceptor.class, order.next());
    put(AuthorizationFilter.class, order.next());
    put(SwitchUserFilter.class, order.next());
}

3. 注册SecurityFilterChain

    private final String[] permitUrlArr = new String[]{"xxx"};
    /**
     * 配置Spring Security安全链。
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        //初始化jwt过滤器,并设置jwt公钥
        var jwtTokenFilter = new JwtTokenFilter();
        //Security6.x关闭默认登录页
        httpSecurity.removeConfigurers(DefaultLoginPageConfigurer.class);
        logger.info("注册JWT认证SecurityFilterChain");
        var chain = httpSecurity
                // 自定义权限拦截规则
                .authorizeHttpRequests((requests) -> {
                    //requests.anyRequest().permitAll(); //放行所有请求!!!
                    //允许匿名访问
                    requests
                            //自定可匿名访问地址,放到permitAllUrl中即可
                            .requestMatchers(permitUrlArr).permitAll()
                            //除上面声明的可匿名访问地址,其它所有请求全部需要进行认证
                            .anyRequest()
                            .authenticated();
                })
                // 禁用HTTP响应标头
                .headers(headersCustomizer -> {headersCustomizer
                            .cacheControl(cache -> cache.disable())
                            .frameOptions(options -> options.sameOrigin());})
                //会话设为无状态,基于token,所以不需要session
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                //添加自定义的JWT认证筛选器,验证header中jwt有效性,将插入到UsernamePasswordAuthenticationFilter之前 
                .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
                //禁用表单登录
                .formLogin(formLogin -> formLogin.disable())
                //禁用httpBasic登录
                .httpBasic(httpBasic -> httpBasic.disable())
                //禁用rememberMe
                .rememberMe(rememberMe -> rememberMe.disable())
                // 禁用CSRF,因为不使用session
                .csrf(csrf -> csrf.disable())
                //允许跨域请求
                .cors(Customizer.withDefaults())
                .build();
        return chain;
    }

六、基于OncePerRequestFilter自定义JWT认证筛选器

使用OncePerRequestFilter的优点是,能保证一个请求只过一次筛选器。可以在filter中实现对jwt的校验,验证成功后需要对Security上下文进行标注。标记认证已经通过,这点非常重要。如果认证完了不标注,后边的过滤器还是认为未认证导致无权限失败。

1. 标记认证成功

//接入Spring Security6.x上下文,标记为已认证状态
JwtAuthenticationToken jwtToken = new JwtAuthenticationToken(null);
jwtToken.setAuthenticated(true); //标记认证通过
SecurityContextHolder.getContext().setAuthentication(jwtToken);

七、遇到的问题

1. 加入Security6后,一直出现登录页

关闭默认登录页有两个设置可以完成,可以删除DefaultLoginPageConfigurer类的加载,或者调用formLogin()函数,具体如下:

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        //Security6.x关闭默认登录页
        httpSecurity.removeConfigurers(DefaultLoginPageConfigurer.class);
        var chain = httpSecurity
                //禁用表单登录
                .formLogin(formLogin -> formLogin.disable())
                .build();
        return chain;
    }

2. 配置完匿名访问的URL后,仍然执行自定的筛选器

如果出现配置完匿名访问的URL后,仍然执行自定的筛选器,的问题。那原因就在于这个自定义筛选器上了,
只通过requests.requestMatchers(…).permitAll(); 配置的匿名访问只能对默认筛选器起效,如果想
对自定义删除器起效,还需要构建WebSecurityCustomizer Bean对象,基于匿名函数配置要匿名访问的地址。
一下是官网推荐的一个写法,这里建议把两个位置,配置的匿名访问地址,使用一个公共数组进行管理,这样
能保证两个位置配置的一致性。

    /** 其它不需要认证的地址 */
    private final String[] permitUrlArr = new String[]{
            "/login"
            ,"/error"
            //静态资源
            ,"/static/**.ico"
            ,"/static/**.js"
            ,"/static/**.css"
            //匹配springdoc
            ,"/doc.html"
            ,"/webjars/**"
            //匹配swagger路径(默认)
            , "/swagger-ui.html"
            , "/swagger-ui/index.html"
            , "/v3/api-docs/**"
            , "/swagger-ui/**"
            //监控检测
            , "/actuator/**"
    };
    @Bean
    public WebSecurityCustomizer ignoringCustomize(){
        return (web) -> web.ignoring()
                .requestMatchers(permitUrlArr);
    }
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        //初始化jwt过滤器,并设置jwt公钥
        var jwtTokenFilter = new JwtTokenFilter();
        //Security6.x关闭默认登录页
        httpSecurity.removeConfigurers(DefaultLoginPageConfigurer.class);
        logger.info("注册JWT认证SecurityFilterChain");
        var chain = httpSecurity
                // 自定义权限拦截规则
                .authorizeHttpRequests((requests) -> {
                    //允许匿名访问
                    requests
                            //自定可匿名访问地址,放到permitAllUrl中即可
                            .requestMatchers(permitUrlArr).permitAll()
                            //除上面声明的可匿名访问地址,其它所有请求全部需要进行认证
                            .anyRequest()
                            .authenticated();
                }).build();
        return chain;
    }                    

八、完成JWT认证的主要代码

目前是对已有jwt的认证,下发的jwt是基于RSA加密的内容,需要使用公钥进行解密,公钥一般配置在yml文件里。关键逻辑设计3部分,SecuritConfig、JwtTokenFilter、JwtUtil。

1. JwtUtil

公钥是统一认证中心下发的,目前写在yml中,格式如下:

jwt.keyValue: |
          -----BEGIN PUBLIC KEY-----
          xxxxxxxx
          -----END PUBLIC KEY-----

JwtUtil类提供了验证方法,出于性能考虑使用了单例模式,验证器只需要实例化一次。

public class JwtUtil {
    private static JwtUtil instance = new JwtUtil();
    private static JWTVerifier jwtVerifier;
    //配置文件中公钥的key值
    private static final String jwtPublicKeyConfig="jwt.keyValue";

    private JwtUtil()  {}

    /**
     * 基于固定配置文件的公钥初始化JWT验证器
     * @return
     */
    public static JwtUtil getInstance(){
        if (jwtVerifier == null){
            String publicKey = SpringUtil.getConfig(jwtPublicKeyConfig);
            return getInstance(publicKey);
        }
        return instance;
    }
    /**
     * 基于自定义公钥初始化JWT验证器
     * @return
     */
    public static JwtUtil getInstance(String publicKey) {
        if (jwtVerifier == null){
            initVerifier(publicKey);
        }
        return instance;
    }

    // 静态的初始化函数
    private static synchronized void initVerifier(String publicKey) {
        if (jwtVerifier != null)
            return;

        //替换为实际的Base64编码的RSA公钥字符串
        String publicKeyStr = publicKey.replaceAll("\\s", "") // 去除所有空白字符,包括换行符
                .replace("-----BEGINPUBLICKEY-----", "")
                .replace("-----ENDPUBLICKEY-----", "");
        // 将Base64编码的公钥字符串转换为PublicKey对象
        byte[] encodedPublicKey = Base64.getDecoder().decode(publicKeyStr);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedPublicKey);
        KeyFactory keyFactory = null;
        try {
            keyFactory = KeyFactory.getInstance("RSA");
            PublicKey pubKey = keyFactory.generatePublic(keySpec);
            // 使用公钥创建一个Algorithm对象,用于验证token的签名
            Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) pubKey, null);
            // 解析和验证token
            jwtVerifier = JWT.require(algorithm).build();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException(e);
        }catch (Exception e){
            throw new RuntimeException(e);
        }

    }

    /**
     * 解析和验证JWT token。
     *
     * @param token JWT token字符串
     * @return 解码后的JWT对象
     * @throws Exception 如果解析或验证失败,抛出异常
     */
    public DecodedJWT verifyToken(String token) {
        return jwtVerifier.verify(token);
    }
}

2. JwtTokenFilter

该类是校验的主要逻辑,完成了jwt校验、已认证的标注。

public class JwtTokenFilter extends OncePerRequestFilter {
    private static Logger logger = LoggerFactory.getLogger(JwtTokenFilter.class);
    private JwtUtil jwtUtil;
    //获取yml中的配置
    public String getConfig(String configKey) {
        var bean = applicationContext.getBean(Environment.class);
        var val = bean.getProperty(configKey);
        return val;
    }
    public JwtTokenFilter() throws ServletException {
        String jwtPubliKey = getConfig("jwt.keyValue");
        initTokenFilter(jwtPubliKey);
    }

    public JwtTokenFilter(String jwtPubliKey) throws ServletException {
        initTokenFilter(jwtPubliKey);
    }

    @Override
    protected void initFilterBean() throws ServletException {
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        var pass = doTokenFilter(request,response,filterChain);
        if(!pass){
            return;
        }
        filterChain.doFilter(request,response);
    }

    /**
     * 初始化Token过滤器。
     * @throws ServletException 如果在初始化过程中发生错误,则抛出ServletException异常
     */
    public void  initTokenFilter(String publicKey) throws ServletException {
        logger.info("初始化TokenFilter");
        if(StringUtils.isBlank(publicKey)){
            throw new ServletException("jwtPublicKey is null");
        }
        logger.info("jwtPublicKey:{}",publicKey);
        jwtUtil = JwtUtil.getInstance(publicKey);
        logger.info("初始化JwtUtil完成");
    }

    protected Boolean doTokenFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 从请求头中获取token
        String token = request.getHeader("Authorization");
        if(StringUtils.isBlank(token)){
            logger.info("jwt token为空,{} {}",request.getMethod(),request.getRequestURI());
            // 验证失败,返回401状态码
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token");
            return false;
        }

        // 假设token是以"Bearer "开头的,需要去掉这个前缀
        if (token.startsWith("Bearer")) {
            token = token.replaceAll("Bearer\s+","");
        }
        logger.debug(request.getRequestURI());
        try {
            // 调用JwtUtils进行token验证
            DecodedJWT jwtDecode = jwtUtil.verifyToken(token);
            //接入Spring Security6.x上下文,标记为已认证状态
            JwtAuthenticationToken jwtToken = new JwtAuthenticationToken(null);
            jwtToken.setAuthenticated(true);
            SecurityContextHolder.getContext().setAuthentication(jwtToken);
            //将登录信息写入spring security上下文
        } catch (JWTVerificationException ex) {
            logger.info("jwt token 非法");
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "非法token:"+ex.getMessage());
            return false;
        } catch (Exception ex) {
            throw ex;
        }
        logger.debug("token验证通过");
        return true;
    }

    public static class JwtAuthenticationToken extends AbstractAuthenticationToken {
        private User userInfo;
        public JwtAuthenticationToken(User user) {
            super(null);
            this.userInfo =user;
        }
        @Override
        public User getPrincipal() {
            return userInfo;
        }
        @Override
        public Object getCredentials() {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean implies(Subject subject) {
            return super.implies(subject);
        }
    }

}

3. SecuritConfig

该类完成了对需要匿名访问的地址的配置,还有自定义filter的注入。

@Configuration
public class SecurityConfig {
    private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
    /** 其它不需要认证的地址 */
    private final String[] permitUrlArr = new String[]{
            "/login"
            ,"/error"
            //静态资源
            ,"/static/**.ico"
            ,"/static/**.js"
            ,"/static/**.css"
            //匹配springdoc
            ,"/doc.html"
            ,"/webjars/**"
            //匹配swagger路径(默认)
            , "/swagger-ui.html"
            , "/swagger-ui/index.html"
            , "/v3/api-docs/**"
            , "/swagger-ui/**"
            //监控检测
            , "/actuator/**"
    };
    @Bean
    public WebSecurityCustomizer ignoringCustomize(){
        return (web) -> web.ignoring()
                .requestMatchers(permitUrlArr);
    }
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        //初始化jwt过滤器,并设置jwt公钥
        var jwtTokenFilter = new JwtTokenFilter();
        //Security6.x关闭默认登录页
        httpSecurity.removeConfigurers(DefaultLoginPageConfigurer.class);
        logger.info("注册JWT认证SecurityFilterChain");
        var chain = httpSecurity
                // 自定义权限拦截规则
                .authorizeHttpRequests((requests) -> {
                    //requests.anyRequest().permitAll(); //放行所有请求!!!
                    //允许匿名访问
                    requests
                            //自定可匿名访问地址,放到permitAllUrl中即可
                            .requestMatchers(permitUrlArr).permitAll()
                            //除上面声明的可匿名访问地址,其它所有请求全部需要进行认证
                            .anyRequest()
                            .authenticated();
                })
                // 禁用HTTP响应标头
                .headers(headersCustomizer -> {headersCustomizer
                            .cacheControl(cache -> cache.disable())
                            .frameOptions(options -> options.sameOrigin());})
                //会话设为无状态,基于token,所以不需要session
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                //添加自定义的JWT认证筛选器,验证header中jwt有效性,将插入到UsernamePasswordAuthenticationFilter之前 
                .addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
                //禁用表单登录
                .formLogin(formLogin -> formLogin.disable())
                //禁用httpBasic登录
                .httpBasic(httpBasic -> httpBasic.disable())
                //禁用rememberMe
                .rememberMe(rememberMe -> rememberMe.disable())
                // 禁用CSRF,因为不使用session
                .csrf(csrf -> csrf.disable())
                //允许跨域请求
                .cors(Customizer.withDefaults())
                .build();
        return chain;
    }
    @Bean
    public FilterRegistrationBean disableSpringBootErrorFilter(ErrorPageFilter filter){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(filter);
        filterRegistrationBean.setEnabled(false);
        return filterRegistrationBean;
    }
}

总结

以上支持介绍了对于已有JWT统一认证系统的接入(JWT解析和认证),不涉及JWT生成和管理相关内容。
目前的用户信息是基于JWT动态解析的,所以暂时没有基于AbstractAuthenticationToken在Security上下文中存放用户信息,JwtAuthenticationToken已经支持自定义用户信息的存储,只需要按需传入即可。基于Security上下文获取用户信息使用SecurityContextHolder.getContext().getAuthentication().getPrincipal();方法。

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

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

相关文章

3D数学基础2

矩阵的行列式 在任意方阵中都存在至少一个标量&#xff0c;称作该方阵的行列式。在线性代数中&#xff0c;行列式有很多有用的性质 线性运算法则 方阵 M M M的行列式记作 ∣ M ∣ |M| ∣M∣或“det M”。非方阵矩阵的行列式是未定义的。 注意&#xff0c;在书写行列式时&…

2024国产化信创产品名录

文章目录 一、JDK/OpenJDK替代品1、龙芯JDK2、阿里巴巴Dragonwell3、毕昇JDK4、腾讯 Kona 二、Tomcat替代品1、东方通TongWeb2、宝兰德Web服务器软件3、普元信息Primeton AppServer4、金蝶天燕AAS应用服务器 三、Nginx替代品1、宝兰德WebServer2、东方通TongHttpServer3、Tengi…

OpenCV-Python实战(11)——边缘检测

一、Sobel 算子 通过 X 梯度核与 Y 梯度核求得图像在&#xff0c;水平与垂直方向的梯度。 img cv2.Sobel(src*,ddepth*,dx*,dy*,ksize*,scale*,delta*,borderType*)img&#xff1a;目标图像。 src&#xff1a;原始图像。 ddepth&#xff1a;目标图像深度&#xff0c;-1 代表…

基于微博热搜评论的情感分析与热点主题挖掘研究

目录 1、绪论 1.1 研究背景与研究意义 1.2 数据来源 1.3 技术路线 2、数据预处理 2.1 数据清洗与准备 2.2 导入必要库与加载数据 2.3 加载停用词表与分词处理 2.4 统计词频与高频词分析 3、情感分析与主题建模 3.1 情感分析 3.2 主题建模 3.3 热点主题识别 4、数据可视…

【毕业设计选题】目标检测方向毕业设计选题推荐 2025

目录 前言 毕设选题 开题指导建议 更多精选选题 选题帮助 最后 前言 大家好,这里是海浪学长毕设专题! 大四是整个大学期间最忙碌的时光&#xff0c;一边要忙着准备考研、考公、考教资或者实习为毕业后面临的升学就业做准备,一边要为毕业设计耗费大量精力。学长给大家整…

Windows onnxruntime编译openvino

理论上来说&#xff0c;可以直接访问 ONNXRuntime Releases 下载 dll 文件&#xff0c;然后从官方文档中下载缺少的头文件以直接调用&#xff0c;但我没有尝试过。 1. 下载 OpenVINO 包 从官网下载 OpenVINO 的安装包并放置在 C:\Program Files (x86) 路径下&#xff0c;例如…

PCB设计检查助手——焊盘中间丝印检查

焊盘中间丝印检查功能用于检查双引脚元件中间是否有垂直两个焊盘连线的元件&#xff0c;由于较大双贴片引脚不用考虑中间的丝印&#xff0c;在检查时会自动过滤两个引脚大于1mm以上的元件&#xff0c;此功能在检查时&#xff0c;两个引脚的间距小于等于1mm&#xff0c;且元件的…

LiteFlow 流程引擎引入Spring boot项目集成pg数据库

文章目录 官网地址简要项目引入maven 所需jar包配置 PostgreSQL 数据库表使用LiteFlow配置 yml 文件通过 代码方式使用 liteflow数据库sql 数据在流程中周转 官网地址 https://liteflow.cc/ 简要 如果你要对复杂业务逻辑进行新写或者重构&#xff0c;用LiteFlow最合适不过。…

MATLAB学习-1 蚁群算法解决TSP问题

matlab安装可以在网上找一个安装教程,我安装的是R2020a版本的。 学习解决该问题 利用蚁群算法对给定的TSP问题进行求解,求出一条最短路径。 用的是194个城市的TSPLIB数据集,如果需要TSPLIB数据集可点击 tsp、添加了可视化,迭代400次,目前最短路径为10576.5454,实现效果如…

IP寻址映射与网络通信互联

IP寻址映射 IP寻址映射能够让数据准确传输的重要部分。在网络之中&#xff0c;所有联网的设备都具有一个IP地址&#xff0c;而IP寻址映射就是负责将IP地址与设备位置或其他相关标识相联系起来&#xff0c;确保数据找到正确的路径传输&#xff0c;保障网络能够畅通。 动态主机配…

IO Virtualization with Virtio.part 1 [十二]

久等了各位&#xff01; 本篇开始讲解 IO 虚拟化中的 virtio&#xff0c;我会以 Linux 的 IIC 驱动为例&#xff0c;从 IIC 驱动的非虚拟化实现&#xff0c;到 IIC 驱动的半虚拟化实现&#xff0c;再到最后 X-Hyper 中如何通过 virtio 来实现前后端联系&#xff0c;一步步把 v…

ITOM系统在IT运维中的作用

在企业IT运维中&#xff0c;ITOM系统&#xff08;IT运营管理&#xff09;通过整合、自动化和监控&#xff0c;能够帮助企业有效管理IT基础设施&#xff0c;提高运维效率和系统的可靠性。本文将从多个角度探讨ITOM系统在IT运维中的作用及其功能。 提升运维效率&#xff1a;统一平…

水上救命稻草,充气救生板的关键救援效能|深圳鼎跃

当今社会&#xff0c;突发事件如影随形&#xff0c;其诱因常隐匿于生活细微之处。就像我们在户外游玩时&#xff0c;水域周边看似风平浪静&#xff0c;实则暗藏危机&#xff0c;稍有疏忽便可能失足落水。此类意外的发生毫无征兆&#xff0c;往往令人在瞬间陷入危险境地&#xf…

Mac M2 Pro安装MySQL 8.4.3

絮絮叨叨 MacBook Pro&#xff0c;芯片&#xff1a;Apple M2 Pro, macOS: Sonoma 14.0一直知道很多软件对Mac M1或M2的支持不好&#xff0c;但没想到在安装MySQL 8.x上也让我吃尽了苦头本文除了介绍如何安装MySQL 8.4.3外&#xff0c;还会记录笔者遇到的一些问题以及解决方法 …

Leecode刷题C语言之切蛋糕的最小总开销②

执行结果:通过 执行用时和内存消耗如下&#xff1a; int compare(const void* a, const void* b) {return (*(int*)b - *(int*)a); }long long minimumCost(int m, int n, int* horizontalCut, int horizontalCutSize, int* verticalCut, int verticalCutSize) {qsort(horizon…

FreeRTOS的内存管理(选择heap4.c文件的理由)

目录 1. 了解FreeRTOS内存管理 2. 了解内存碎片 3.了解各个heap.c的内存分配方法 1.heap1.c 2.heap2.c 3.heap3.c 4.heap4.c 5.heap5.c 总结&#xff1a; 内存管理是一个系统基本组成部分&#xff0c;FreeRTOS 中大量使用到了内存管理&#xff0c;比如创建任务、信号量…

[服务器][教程]Ubuntu24.04 Server开机自动挂载硬盘教程

1. 查看硬盘ID ls -l /dev/disk/by-uuid可以看到对应的UUID所对应的分区 2. 创建挂载文件夹 创建好文件夹即可 3. 修改配置文件 sudo vim /etc/fstab把对应的UUID和创建的挂载目录对应即可 其中# Personal mount points下面的是自己新添加的 &#xff1a;分区定位&#xff…

Python用K-Means均值聚类、LRFMC模型对航空公司客户数据价值可视化分析指标应用|数据分享...

全文链接&#xff1a;https://tecdat.cn/?p38708 分析师&#xff1a;Yuling Fang 信息时代的来临使得企业营销焦点从产品中心转向客户中心&#xff0c;客户关系管理成为企业的核心问题&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。 客户关系管理的关键是客…

HTML——46.制作课程表

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>课程表</title></head><body><h3>课程表</h3><table border"1" cellspacing"0"><tr><th colspan"…

强化学习(1)

Reinforcement Learning Goal-directed learing from ineraction with the environment. 1. Basic Element 基本元素 1.1 Agent 玩家 1.2 Environment 1.3 Goal 2. Main Element 主要元素 2.1 State 2.2 Action 状态与行为往复 2.3 Reward 目标&#xff1a;最大化总…