Spring security之JWT

news2024/11/25 10:33:22

JWT

这里写目录标题

  • JWT
  • 一级目录
    • 二级目录
      • 三级目录
      • 1.什么是JWT
    • 2.JWT的组成部分
    • 3.编码/解码
    • 4.特点
    • 5. 为什么使用JWT
      • 5.1传统的验证方式
    • 5.2基于JWT的验证方式
    • 6.JWT进行登录验证
      • 6.1依赖安装
      • 6.2编写UserDetailServiceImpl类
      • 6.3编写UserDetailsImpl类
      • 6.4 实现config.SecurityConfig类
      • 6.5实现utils.JwtUtil类
      • 6.6实现config.filter.JwtAuthenticationTokenFilter类
      • 6.7配置config.SecurityConfig类
    • 7.常见的面试题

一级目录

二级目录

三级目录

1.什么是JWT

  • 全称 JSON Web Token,简称JWT,是一个基于RFC7519的爱看i发数据标准,定义了一种宽松且紧凑的数据组合方式
  • 是一种加密后的数据载体,可在各应用之间进行数据传输
  • 最常应用于授权认证,一旦用户登录,每个请求都将包含JWT,系统每次处理用户请求都会及逆行JWT安全检验,通过之后在进行处理

2.JWT的组成部分

JWT由3部分组成,使用.拼接

  • Header

    • 声明类型,默认JWT
    • 声明加密算法,HMAC,RSA,ECDSA等常用算法
    {
    "alg": "HS256",
    "typ": "JWT"
    }
    
    • alg:表示签名的算法,默认HMAC SHA256(写成HS256)
    • type:表示声明类型,默认JWT

    使用Base64加密后构成JWT,的第一部分

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    
  • Payload

    • 存放有效信息的地方

    标准载荷

    建议但不强制使用

    标准载荷介绍
    iss (issuer)签发人(谁签发的)
    exp (expiration time)过期时间,必须要大于签发时间
    sub (subject)主题(用来做什么)
    aud (audience)受众(给谁用的)比如:http://www.xxx.com
    nbf (Not Before)生效时间
    iat (Issued At)签发时间
    jti (JWT ID)编号,JWT 的唯一身份标识

    自定义载荷

    可以添加任何信息,一般添加用户的相关信息或业务所需要的必要信息。但不建议添加敏感信息

    {
     //第一部分用户信息
     "user_info": [
       {
         "id": "1"//id
       },
       {
         "name": "dafei"//姓名
       },
       {
         "age": "18"//年龄
       }
     ],
     "iat": 1681571257,//签发时间
     "exp": 1682783999,//过期时间
     "aud": "xiaofei",//受众
     "iss": "dafei",//签发人
     "sub": "alluser"//主题
    }
    

    使用Base64加密后构成了第二部分

    eyJ1c2VyX2luZm8iOlt7ImlkIjoiMSJ9LHsibmFtZSI6ImRhZmVpIn0seyJhZ2UiOiIxOCJ9XSwiaWF0IjoxNjgxNTcxMjU3LCJleHAiOjE2ODI3ODM5OTksImF1ZCI6InhpYW9mZWkiLCJpc3MiOiJkYWZlaSIsInN1YiI6ImFsbHVzZXIifQ
    
  • signature

    • 私钥:只有服务器才知道
    • 公钥:按照以下算法产生
    signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
    

    用Base64加密,构成第三部分

    将三部分拼接成一个字符串,便是jwt了

由于密钥的存在,即使调用方修改了前两部分内容,在验证不放呢会出现签名不一致的情况,保证了全性

3.编码/解码

编码工具

https://tooltt.com/jwt-encode/

解码工具

https://tool.box3.cn/jwt.html

4.特点

优点

  • 无状态

    JWT 不需要在服务端存储任何状态,客户端可以携带 JWT 来访问服务端,从而使服务端变得无状态。这样,服务端就可以更轻松地实现扩展和负载均衡。

  • 自定义

    JWT 的载荷部分可以自定义,可以存储任何 JSON 格式的数据。这意味着我们可以使用 JWT 来实现一些自定义的功能,例如存储用户喜好、配置信息等等。

  • 扩展性强

    JWT 有一套标准规范,因此很容易在不同平台和语言之间共享和解析。此外,开发人员可以根据需要自定义声明(claims)来实现更加灵活的功能。

  • 调试性好

    由于 JWT 的内容是以 Base64 编码后的字符串形式存在的,因此非常容易进行调试和分析。

缺点

  • 安全性取决于密钥管理

    JWT 的安全性取决于密钥的管理。如果密钥被泄露或者被不当管理,那么 JWT 将会受到攻击。因此,在使用 JWT 时,一定要注意密钥的管理,包括生成、存储、更新、分发等等。

  • 无法撤销

    由于 JWT 是无状态的,一旦 JWT 被签发,就无法撤销。如果用户在使用 JWT 认证期间被注销或禁用,那么服务端就无法阻止该用户继续使用之前签发的 JWT。因此,开发人员需要设计额外的机制来撤销 JWT,例如使用黑名单或者设置短期有效期等等。

  • 需要缓存到客户端

    由于 JWT 包含了用户信息和授权信息,一般需要客户端缓存,这意味着 JWT 有被窃取的风险。

  • 载荷大小有限制

    由于 JWT 需要传输到客户端,因此载荷大小也有限制。一般不建议载荷超过 1KB,会影响性能。

5. 为什么使用JWT

5.1传统的验证方式

  • 传统的认证方式,是基于Session的认证

Cookie和Session

  • Cookie:一种让程序员在本地存储数据的能力,让数据在客户端这边更持久化
  • Cookie里面存的是键值对的格式数据,键值对用“;”分割,键和值之间用“,”分割

登录的认证

Session

  • session是在服务器的一种机制,因为cookie是客户端保存的数据,而这些数据又是跟用户强烈相关联的,显然保存在客户端这边就不太合适(太多,也占资源),所以把数据都保存在服务器这边就比较的合适;保存的方式就是通过session的方式来进行保存的。键值对结构

  • 使用之后,客户端只需要保存 sessionId就可以了,后续的请求带上 sessionId,服务器就会根据 sessionId 就会找到对应的用户数据详细的信息。

认证流程

image-20230808162008667

  1. 客户端:发起一个认证请求
  2. 服务器端:认证通过存储一个session的用户信息;把sessionid写入客户端cookie
  3. 客户端再次访问:自动携带sessionid,找到对应session

问题分析

  • 通常来说session保存在服务器的内存中,随着认证用户增多,会加大服务器的开销

  • 因为session保存在服务器内存中,所以对于分布式应用来说,限制了应用的扩展能力

  • cookie被截取,会容易受造跨站请求伪造攻击

  • 在前后端分离的系统中更加痛苦,比如后端是集群分布,需要设计共享机制、

5.2基于JWT的验证方式

image-20230808165551764

  1. 客户端: 发起一个请求认证,带有用户名,密码
  2. 服务器:认证通过,生成JWT,响应给客户端
  3. 客户端:将Token保存到本地

日后客户端再次认证

  1. 客户端:携带JWT发送给服务器
  2. 服务器:进行验证,通过,展示数据;否则返回错误信息

6.JWT进行登录验证

6.1依赖安装

  • spring-boot-starter-security
  • jjwt-api
  • jjwt-impl
  • jjwt-jackson

meaven仓库地址:Maven Repository: Search/Browse/Explore (mvnrepository.com)

6.2编写UserDetailServiceImpl类

  • 继承UserDetailsService接口
  • 作用:接入数据库信息,主要是用来根据用户名查找用户
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("username",username);
        User user=userMapper.selectOne(queryWrapper);
        if(user==null){
            throw new RuntimeException("用户不存在");
        }
        return new UserDetailsImpl(user);
    }
}

6.3编写UserDetailsImpl类

  • 使用户的一个接口类,用来判断用户的密码,权限是否失效,过期。
package com.kob.backend.service.impl.utils;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import  com.kob.backend.pojo.User;

@Data
@AllArgsConstructor
@NoArgsConstructor

public class UserDetailsImpl implements UserDetails {
    private User user;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return null;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

6.4 实现config.SecurityConfig类

  • 用来实现用户密码的加密存储
@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

6.5实现utils.JwtUtil类

  • 是jwt工具类的一种
  • 作用:用来创建,解析jwt token
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

@Component
public class JwtUtil {
    public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;  // 有效期14天
    public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";

    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if (ttlMillis == null) {
            ttlMillis = JwtUtil.JWT_TTL;
        }

        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)
                .setSubject(subject)
                .setIssuer("sg")
                .setIssuedAt(now)
                .signWith(signatureAlgorithm, secretKey)
                .setExpiration(expDate);
    }

    public static SecretKey generalKey() {
        byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
    }

    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(jwt)
                .getBody();
    }
}

6.6实现config.filter.JwtAuthenticationTokenFilter类

  • 进行jwt token的验证,如果成功,将用户信息注入上下文中
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.jetbrains.annotations.NotNull;
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.util.StringUtils;
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;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private UserMapper userMapper;

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

        if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        token = token.substring(7);

        String userid;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        User user = userMapper.selectById(Integer.parseInt(userid));

        if (user == null) {
            throw new RuntimeException("用户名未登录");
        }

        UserDetailsImpl loginUser = new UserDetailsImpl(user);
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, null);

        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        filterChain.doFilter(request, response);
    }
}


6.7配置config.SecurityConfig类

  • 放行登录,注册页面
import com.kob.backend.config.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

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

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
            //在这里方形的登录注册页面,填写访问地址
                .antMatchers("/user/account/token/", "/user/account/register/").permitAll()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated();

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


7.常见的面试题

问:什么是JWT?解释一下它的结构。

JWT是一种开放标准,用于在网络上安全地传输信息。它由三部分组成:头部、载荷和签名。头部包含令牌的元数据,载荷包含实际的信息(例如用户ID、角色等),签名用于验证令牌是否被篡改。

问:JWT的优点是什么?它与传统的session-based身份验证相比有什么优缺点?

JWT的优点包括无状态、可扩展、跨语言、易于实现和良好的安全性。相比之下,传统的session-based身份验证需要在服务端维护会话状态,使得服务端的负载更高,并且不适用于分布式系统。

问:在JWT的结构中,分别有哪些部分?每个部分的作用是什么?

JWT的结构由三部分组成:头部、载荷和签名。头部包含令牌类型和算法,载荷包含实际的信息,签名由头部、载荷和密钥生成。

问:JWT如何工作?从开始到验证过程的完整流程是怎样的?

JWT的工作流程分为三个步骤:生成令牌、发送令牌、验证令牌。在生成令牌时,服务端使用密钥对头部和载荷进行签名。在发送令牌时,将令牌发送给客户端。在验证令牌时,客户端从令牌中解析出头部和载荷,并使用相同的密钥验证签名。

问:什么是JWT的签名?为什么需要对JWT进行签名?如何验证JWT的签名?

JWT的签名是由头部、载荷和密钥生成的,用于验证令牌是否被篡改。签名使用HMAC算法或RSA算法生成。在验证JWT的签名时,客户端使用相同的密钥和算法生成签名,并将生成的签名与令牌中的签名进行比较。

问:什么是JWT的令牌刷新?为什么需要这个功能?

令牌刷新是一种机制,用于解决JWT过期后需要重新登录的问题。在令牌刷新中,服务端生成新的JWT,并将其发送给客户端。客户端使用新的JWT替换旧的JWT,从而延长令牌的有效期。

问:JWT是否加密?如果是,加密的部分是哪些?如果不是,那么它如何保证数据安全性?

JWT本身并不加密,但可以在载荷中包含敏感信息。为了保护这些信息,可以使用JWE(JSON Web Encryption)对载荷进行加密。如果不加密,则需要在生成JWT时确保不在载荷中包含敏感信息。

问:在JWT中,如何处理Token过期的问题?有哪些方法可以处理?

JWT过期后,客户端需要重新获取新的JWT。可以通过在JWT中包含过期时间或使用refresh token等机制来解决过期问题。

问:JWT和OAuth2有什么关系?它们之间有什么区别?

JWT和OAuth2都是用于身份验证和授权的开放标准。JWT是一种身份验证机制,而OAuth2是一种授权机制。JWT用于在不同的系统中安全地传输信息,OAuth2用于授权第三方应用程序访问受保护的资源。

问:JWT在什么场景下使用较为合适?它的局限性是什么?

JWT在单体应用或微服务架构中的使用比较合适。它的局限性包括无法撤销、令牌较大、无法处理并发等问题。在需要针对每次请求进行访问控制或需要撤销令牌的情况下,JWT可能不是最佳选择。

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

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

相关文章

简单的员工工资管理 ssm人事考勤jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 简单的员工工资管理 ssm 系统有1权限&#xff1a;管理…

8年软件测试工程师感悟——写给还在迷茫中的朋友

去年还在全网声讨互联网企业996呢&#xff0c;今年突然没声音了&#xff0c;也不用讨论在哪个路灯上吊死互联网资本家了&#xff0c;因为都被裁了。 继教育培训领域大幅度裁员之后&#xff0c;大厂裁员消息也开始陆续传出&#xff0c;百度AIG,MEG多条业务线进行精简&#xff0…

1、for循环

①for循环 在程序中&#xff0c;一组被重复执行的语句被称之为循环体&#xff0c; 能否继续重复执行&#xff0c;取决于循环的终止条件 &#xff0c; 由循环体及循环的终止条件组成语句&#xff0c;被称之为循环语句 1.1语法结构 for循环主要用于把某些代码循环若干次&#xf…

Protues如何安装下载使用:STM32利用Protues进行仿真

文章目录&#xff1a; 一&#xff1a;Proteus仿真的使用步骤 第一步&#xff1a;Proteus新建项目 第二步&#xff1a;Proteus设计电路图&#xff08;选取元器件、摆放元器件、编辑元器件属性、原理图布线&#xff09; 第三步&#xff1a;程序代码编写 第四步&#xff1a;…

el-tree-select那些事

下拉菜单树形选择器 用于记录工作及日常学习涉及到的一些需求和问题 vue3 el-tree-select那些事 1、获取el-tree-select选中的任意层级的节点对象 1、获取el-tree-select选中的任意层级的节点对象 1-1数据集 1-2画面 1-3代码 1-3-1画面代码 <el-tree-selectv-model"s…

gitlab-Runner搭建

root wget https://packages.gitlab.com/runner/gitlab-runner/packages/fedora/29/gitlab-runner-12.6.0-1.x86_64.rpm/download.rpm rpm -ivh download.rpm ---- 安装 rpm -Uvh download.rpm -----更新升级 然后运行&#xff1a; gitlab-runner register --url https://git…

vue3项目中引入dialog插件,支持最大最小化、还原、拖拽

效果图&#xff1a; 上图是layui-vue组件库中的layer插件&#xff0c;我的项目使用的是element-plus组件库&#xff0c;在用不上layui组件库的情况下&#xff0c;就单独引入layui/layer-vue这个弹层插件就可以了 npm地址&#xff1a;layui/layer-vue - npm layui-vue组件库地址…

mysql_docker主从复制_实战_binlog混合模式_天座著

步骤1&#xff1a;拉取镜像 docker pull mariadb:latest 步骤2.1&#xff1a;创建两个文件夹用于放置挂载mysql的my.cnf /tianzuomysqlconf/master /tianzuomysqlconf/slave mkdir /tianzuomysqlconf cd /tianzuomysqlconf mkdir master mkdir slave 步骤2.2&#xff1a;创…

ssh 连接断开,正在执行的shell脚本也被中断了

背景 最近在训练chatGLM&#xff0c;一次训练经常要花掉近2个小时&#xff0c;但是由于网络不稳定&#xff0c;经常ssh莫名的断开&#xff0c;导致训练不得不重新开启&#xff0c;这就很浪费时间了 解决方案 下面教大家一种在后台执行命令的方案&#xff0c;即使你ssh连接断…

层状介质一维大地电磁数值模拟

层状介质一维大地电磁数值模拟 前言 大地电磁测深法&#xff08; MT&#xff09;是根据电磁感应原理研究天然场源在地下激励产生的交变电场或者磁场&#xff0c;通过地表观测到的电磁场或者通过电磁场计算出视电阻率或者相位等量来进行地下构造研究的一种电磁方法。由于它不需…

★动态规划(DP算法)详解

什么是动态规划&#xff1a;动态规划_百度百科 内容太多了不作介绍&#xff0c;重点部分是无后效性&#xff0c;重叠子问题&#xff0c;最优子结构。 问S->P1和S->P2有多少种路径数&#xff0c;毫无疑问可以先从S开始深搜两次&#xff0c;S->P1和S->P2找出所有路…

【Linux】深入理解进程概念

个人主页&#xff1a;&#x1f35d;在肯德基吃麻辣烫 我的gitee&#xff1a;Linux仓库 个人专栏&#xff1a;Linux专栏 分享一句喜欢的话&#xff1a;热烈的火焰&#xff0c;冰封在最沉默的火山深处 文章目录 前言浅谈进程概念1. 进程和操作系统的联系2.描述进程的对象——PCB …

MongoDB:Linux环境全套安装指南

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; MongoDB&#xff1a;Linux环境全套安装指南 ⏱️ 创作时间&#xff1a…

Hybrid App 可以从哪些技术路径实现性能优化

说到 Hybrid App&#xff08;混合应用&#xff09;大家都不陌生&#xff0c;因为这种开发模式大行其道发展的这些年取代了很多原生和 Web 应用&#xff0c;为什么大家对这种「Native HTML5」的开发模式额外偏爱呢&#xff1f; 因为一方面在一定程度上兼顾了原生应用的优质体验…

compile_and_runtime_not_namespaced_r_class_jar\debug\R.jar: 另一个程序正在使用

问题情况&#xff1a; run App的时候&#xff0c;提示该文件被占用 想要clean Project&#xff0c;还是提示该文件被占用&#xff0c;这个文件和连带的文件夹都无法被删除。 方法1&#xff1a; AndroidStudio下方的terminal&#xff08;没有这个窗口的话&#xff0c;从上面的…

Java源码-Context源码解析

您好&#xff0c;我们来一起了解一下Java源码中的Context源码解析。 Context是Android中的一个重要的概念&#xff0c;在Android开发中可以用来获取应用程序的各种信息&#xff0c;如Activity、Service、Application等等。在Android中&#xff0c;Context是一个抽象类&#xf…

虚拟机centos7配置网络

虚拟机centos7配置网络 centos7克隆之后需要配置网络才能联网。 实验环境&#xff1a; VMware Workstation Pro 16CentOS 7系统虚拟机主机Windows 11系统 1.VMware网络模式设置为NAT模式 虚拟机–设置–网络适配器– ​​ ‍ 2.查看虚拟机 子网IP和网关IP 编辑–虚拟网…

AcWing 129:火车进栈 ← DFS

【题目来源】https://www.acwing.com/problem/content/131/【题目描述】 这里有 n 列火车将要进站再出站&#xff0c;但是&#xff0c;每列火车只有 1 节&#xff0c;那就是车头。 这 n 列火车按 1 到 n 的顺序从东方左转进站&#xff0c;这个车站是南北方向的&#xff0c;它虽…

【vue3】基础知识点-组合式api-recative和ref函数

学习vue3&#xff0c;都会从基础知识点学起。了解setup函数&#xff0c;ref&#xff0c;recative&#xff0c;watch、computed、pinia等如何使用 今天说vue3组合式api&#xff0c;响应式函数recative和ref函数 recative函数&#xff1a;接收对象类型数据的参数传入&#xff0…

激光雷达测距和摄像头联合棋盘格反射率标定板

目前&#xff0c;激光雷达不仅在军事上起到了重要的作用&#xff0c;而且发挥其测程大、精度高、反应速度快、可靠性高等优点&#xff0c;在商业领域应用越来越广&#xff0c;发展越来越迅速&#xff0c;具有较高的实用价值和商业价值。车载三维成像激光雷达采用脉冲式测量原理…