Spring Security 认证流程,长话简说

news2024/12/23 0:26:03

一、代码先行

1、设计模式

SpringSecurity 采用的是 责任链 的设计模式,是一堆过滤器链的组合,它有一条很长的过滤器链。

不过我们不需要去仔细了解每一个过滤器的含义和用法,只需要搞定以下几个问题即可:怎么登录、怎么校验账户、认证失败处理。

2、 POM依赖

没啥好说的,maven导入即可。
不写版本号,默认就会下载 最新的 版本。

<!--springSecurity-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency> 

3、登录

不管你用哪种权限框架,第一个要解决的问题就是登录。就是在我们的登录接口中,将账户密码委托给权限框架接管,让权限框架帮我们做校验和权限认证。

新建一个登录方法,目的是 验证用户的 用户名密码,并在验证成功后为该用户生成一个JWT。

@GetMapping("/login")
public String securityLogin(String username,String password){
    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username,password);
    // 使用authenticationManager调用loadUserByUsername获取数据库中的用户信息,
    Authentication authentication = authenticationManager.authenticate(authToken);
    if(authentication == null) {
        throw new RuntimeException("登录失败啦呀");
    }

    //获取符合security规范的User
    SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();

    String token = jwtUtil.createToken(securityUser.getUser());
    return token;
}

UsernamePasswordAuthenticationToken就是 我们委托 框架 帮我们托管 的 登录凭证

然后是:

单词重点说明: authenticate (v) 证明…是事实; authentication (n)证明

Authentication authentication = authenticationManager.authenticate(authToken);   

这就是spring security帮我们 执行 认证授权 的方法,最终返回一个 认证结果

大家思考一下,正常登录的逻辑无非是 4步走:

  1. 输入账号密码
  2. 根据账号从 DB数据库 中获取用户实体
  3. 校验密码是否正确
  4. 校验成功,将用户生成token后返回

我们再回过来看 上面的代码,第2步和第3步没见到。
肯定是spring security已经帮我们做了,但是,这并不代表我们可以省略这两步,只是需要我们写在别的地方,仅此而已。

4、根据账号从DB中获取用户实体

这个步骤是不可能不写的,只是写到了别处。
说明一点,spring security中的 用户概念,有自己的一套规则,不能直接用 我们系统里面的 User类。

因此如果我们要用spring security,就得 实现 他的 用户接口UserDetails
所以,我们新建一个SecurityUser类:

@Data
@NoArgsConstructor
public class SecurityUser implements UserDetails {

    // 使用聚合模式,将我们自己的User对象聚合到SecurityUser中
    // user字段存储了用户的一些信息,例如用户名和密码等
    private User user;

    // 覆盖UserDetails接口中的getAuthorities方法,返回用户的权限集合
    // 这里返回null,表示未实现获取权限的逻辑
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    // 覆盖UserDetails接口中的getPassword方法,返回用户的密码
    // 这里通过调用user对象的getPwd方法获取密码
    @Override
    public String getPassword() {
        return user.getPwd();
    }

    // 覆盖UserDetails接口中的getUsername方法,返回用户的用户名
    // 这里通过调用user对象的getUserName方法获取用户名,并注释说明有的地方可能会用email作为用户名,这里还是使用userName
    @Override
    public String getUsername() {
        // 用户名:有的地方可能会用email作为用户名,我们这还是userName
        return user.getUserName();
    }

    // 覆盖UserDetails接口中的isAccountNonExpired方法,判断账户是否过期
    // 这里直接返回true,表示账户不过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // 覆盖UserDetails接口中的isAccountNonLocked方法,判断账户是否被锁定
    // 这里直接返回true,表示账户未被锁定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // 覆盖UserDetails接口中的isCredentialsNonExpired方法,判断凭证是否过期
    // 这里直接返回true,表示凭证不过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    // 覆盖UserDetails接口中的isEnabled方法,判断用户是否启用
    // 这里直接返回true,表示用户启用状态为true
    @Override
    public boolean isEnabled() {
        return true;
    }
}
    

虽然我们系统里面有自己的 user 了,但是为了适配,所以就 聚合 进来。

然后是如何查询DB,肯定得有个 Service 去查询,我们已经有自己的UserService了,但是很可惜,spring security 这个地方也有自己的规范,我们自己写的user Service,人家不认,唉。

没办法,重新写个Service,自己写都写了,也不能丢了,所以 依照上面的操作,还是聚合进来。

@Service
@Slf4j
public class UserDetailService implements UserDetailsService {

    @Resource
    UserService userService;

    /**
     * 根据用户名直接从DB中查询用户数据,作为登录校验的依据
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userService.getByUsername(username);
        if (user == null) {
            log.info("username not found");
            throw new UsernameNotFoundException("username not found");
        }
        SecurityUser securityUser = new SecurityUser();
        securityUser.setUser(user);
        return securityUser;
    }
}

核心逻辑 是,我们还是用之前的方法拿到 User,但为了适配,就塞到 SecurityUser 里面去。

UserDetailsServicespring security认可的接口,我们得实现这个接口,并且实现loadUserByUsername方法,这个方法在spring security的认证逻辑里面会用到。目的就是拿到DB中真实的User,跟我们登录的账号密码进行比对。

5、校验密码是否正确

很多时候我们的密码是要进行加密的,但是我们登录肯定传的是明文密码,所以需要转换后再去比对。

这个 校验密码 的逻辑,需要写在spring security配置类 中。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Resource
  UserDetailService userService;
  
   /**
     * 新增security账户
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                return rawPassword.toString();
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return rawPassword.equals(encodedPassword);
            }
        });
    }

}

因为我们的项目并没有对密码加密,所以就直接比较了。

在 LoginController 中,我们用到了AuthenticationManager这个对象,需要在配置类中注册。

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
    // 身份验证管理器, 直接继承即可.
    return super.authenticationManagerBean();
} 

AuthenticationManager 是 Spring Security框架中的一个核心接口,它负责处理身份验证请求。
在认证过程中,用户提交身份验证信息(如用户名和密码),AuthenticationManager 会验证这些信息的有效性。

最后是 路由 的相关配置

    @Override  
    protected void configure(HttpSecurity http) throws Exception {  
        http  
                // 禁用跨站请求伪造保护  
                .csrf().disable()   
                // 设置会话管理策略为无会话,因为我们使用token作为信息传递介质  
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)   
                .and()  
                // 进行认证请求的配置  
                .authorizeRequests()   
                // 将所有登入和注册的接口放开,这些都是无需认证就访问的  
                .antMatchers("/security/login").anonymous()   
                // 除了上面的那些,剩下的任何接口请求都需要经过认证  
                .anyRequest().authenticated()   
                .and()  
                // 允许跨域请求  
                .cors()   
                ;  
        // 在UsernamePasswordAuthenticationFilter之前添加JWT认证过滤器  
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);  
    }

这段代码是Spring Security框架中用于配置HTTP安全的部分。它主要涉及到跨站请求伪造(CSRF)的禁用、会话管理策略的设置、认证请求的配置以及跨域请求的允许。同时,还添加了一个JWT(JSON Web Token)认证过滤器。

因为我们项目用到了jwt,所以在进行账号密码验证之前,要先走jwt的过滤器。

jwt过滤器代码如下:

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    JWTUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ServletException, IOException {
        //获取token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            //token为空的话, 就不管它, 让SpringSecurity中的其他过滤器处理请求
            //请求放行
            filterChain.doFilter(request, response);
            return;
        }
        //token不为空时, 解析token
        User user = null;
        try {
            user = jwtUtil.verify(token);
        } catch (Exception e) {
            // 过滤器中抛出的异常,无法被统一异常捕获,所以在这里直接返回
            e.printStackTrace();
            Result result = new Result();
            result.setCode(403);
            result.setMsg("Token无效:" + e.getMessage());
            WebUtils.response(response,result);
            return;
        }
        SecurityUser securityUser = new SecurityUser();
        securityUser.setUser(user);
        //将用户安全信息存入SecurityContextHolder, 在之后SpringSecurity的过滤器就不会拦截
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(securityUser, null, null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}  

流程参考这个图:
在这里插入图片描述

如果校验成功了,那么在登录方法中就会往下走,生成token返回,结束。

6、全局异常返回

认证过程中,难免出现各种异常,我们一般会做一个通用的返回

Result

@Data
public class Result implements Serializable {

    private int code;
    private String msg;
    private Object data;

    public static Result succ(Object data) {
        return success(200, "操作成功", data);
    }

    public static Result error(String msg) {
        return error(400, msg, null);
    }

    public static Result success (int code, String msg, Object data) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }

    public static Result error (int code, String msg, Object data) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
}

GlobalExceptionHandler

/**
 * 全局异常处理
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 400 错误:运行时异常
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = RuntimeException.class)
    public Result handler(RuntimeException e) {
        log.error("运行时异常:----------------{}", e.getMessage());
        return Result.error(e.getMessage());
    }

    /**
     * 403 错误:权限不足
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(value = AccessDeniedException.class)
    public Result handler(AccessDeniedException e) {
        log.info("security权限不足:----------------{}", e.getMessage());
        return Result.error("权限不足");
    }

    /**
     * 400 错误:异常请求-方法参数不匹配
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result handler(MethodArgumentNotValidException e) {
        log.info("实体校验异常:----------------{}", e.getMessage());
        BindingResult bindingResult = e.getBindingResult();
        ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();
        return Result.error(objectError.getDefaultMessage());
    }

    /**
     * 400 错误:异常请求-非法参数
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = IllegalArgumentException.class)
    public Result handler(IllegalArgumentException e) {
        log.error("Assert异常:----------------{}", e.getMessage());
        return Result.error(e.getMessage());
    }


}

二、原理亮明

Spring Security 它专注于身份验证和授权,并可根据需求进行配置和自定义。在实际应用中,了解身份验证组件的概念对于使用 Spring Security 进行实现和自定义非常有帮助。

1、没有使用 Spring Security

如果我们没有使用 Spring Security,那么请求将被 DispatcherServlet 拦截。

  • DispatcherServlet 服务分发器
    它拦截任何 HTTP 请求并将其转发到正确的控制器。
    在 Spring Boot 中,DispatcherServlet 会被自动配置。
    在这里插入图片描述

2、DispatcherServlet 是如何工作的

在深入了解 Spring Security 之前,让我们先了解一下 DispatcherServlet 如何分发请求。
当我们在一个端点(比如 /hello/world)发出请求时,DispatcherServlet 会如何处理它?

DispatcherServlet 创建了一个 IOC 容器,它是 Spring Framework 的核心组件之一,用于管理 bean 的创建和依赖关系。DispatcherServlet 创建的是 WebApplicationContext,这是一个专门用于 Web 应用程序的 IOC 容器。WebApplicationContext 是根据配置文件由 DispatcherServlet 进行配置的。

该 IOC 容器 会创建控制器 bean 的实例。当请求到达时,DispatcherServlet 会使用 IOC 容器 查找适当的 控制器 bean,并 委托 给它来 处理请求。

3、使用 Spring Security

当将 Spring Security 添加到 Spring Boot 应用程序中时,所有请求在到达 DispatcherServlet 之前 都会被 Spring Security 拦截。

在这里插入图片描述

4、认证 流程

身份验证请求和响应

虽然 Spring Security 可以与不同类型的身份验证方法一起使用,但在本文中,我们讨论的是 用户名和密码 方式的 身份验证,以便深入了解完整的身份验证流程。

  1. Filter Chain 在将请求转发给 Dispatcher Servlet 之前拦截传入请求。

  2. 请求进入认证过滤器(其中一个过滤器是 UsernamePasswordAuthenticationFilter)。

  3. 过滤器从请求(HttpServletRequest 对象)中提取用户名和密码。

  4. 然后使用凭据创建 UsernamePasswordAuthenticationToken

  5. 调用 AuthenticationManager 的 authenticate() 方法。

  6. AuthenticationManager 的 authenticate() 方法实现将尝试使用其拥有的 AuthenticationProvider 之一进行身份验证。

  7. 如果一个身份验证提供程序能够成功进行身份验证,它将返回一个包含凭据和权限的完整的 UsernamePasswordAuthenticationToken。

  8. 提供程序返回的此令牌用于在 Spring Security 上下文中将用户设置为已认证。

  9. 一旦用户通过身份验证,请求就会被转发到处理请求的 DispatcherServlet。

5、认证流程 中涉及到的 组件

5.1 什么是过滤器

Spring Filter 是一个组件,可以拦截任何传入请求,并在将其传递给 DispatcherServlet 之前执行某些操作。
过滤器可以处理请求,然后将其转发到 Filter Chain 中的下一个过滤器,或者可以停止并发送回 HTTP 响应。其中一个过滤器是存在于 FilterChain 中的 UsernamePasswordAuthenticationFilter。此过滤器尝试查找 HTTP Post 请求中传递的用户名和密码,如果找到,则尝试使用凭据对用户进行身份验证。
我们可以创建自己的 Filter 并将其添加到 SecurityFilterChain 中,以提供自己的逻辑来处理请求。

5.2 什么是 AuthenticationManager

AuthenticationManager 是一个接口,用于处理 身份验证 的过程。它只有一个方法 authenticate(Authentication authentication),该方法将一个身份验证对象作为参数,并返回 已经认证的 身份验证对象。身份验证对象通常是一个包含用户名和密码等凭据的 AuthenticationToken 对象。

Authentication authenticate(Authentication authentication) throws AuthenticationException;   

AuthenticationManager 的 实现类 是 ProviderManager 类,它提供了 authenticate() 方法的逻辑。
我们可以提供我们自己的 AuthenticationProvider 实现类 或 使用默认实现。

5.3 什么是 ProviderManager

实现 了 AuthenticationManager 接口 并覆盖了 authenticate() 方法。
它使用一组 AuthenticationProvider 来验证 Authentication 对象中发送的凭据。如果 AuthenticationProvider 支持身份验证类型,则将用于验证用户。如果没有提供者支持身份验证类型,则将抛 AuthenticationException。

每个身份验证提供程序的 supports() 方法用于检查它是否可以支持所需的身份验证类型。

5.4 什么是 AuthenticationProvider

AuthenticationProvider 是一个接口,用于定义验证用户的协议。它负责接收 Authentication 对象(表示用户凭据)并返回已经认证的 Authentication 对象,如果凭据有效。如果凭据无效,则 AuthenticationProvider 应该抛出 AuthenticationException。

AuthenticationProvider 接口有两个方法:

  • authenticate():此方法接受 Authentication 对象作为输入,并在凭据有效时返回已认证的 Authentication 对象。如果凭据无效,则 AuthenticationProvider 应该抛出 AuthenticationException。

  • supports():此方法接受 Authentication 对象作为输入,并且如果 AuthenticationProvider 可以验证该对象,则返回 true。如果 AuthenticationProvider 无法验证该对象,则应返回 false。

Spring Security 中的默认 AuthenticationProvider 是 DaoAuthenticationProvider。此提供程序使用 UserDetailsService 从数据库加载用户详细信息。如果用户凭据与数据库中的详细信息匹配,则 DaoAuthenticationProvider 将返回已经认证的 Authentication 对象。

我们可以添加我们自己的 AuthenticationProvider 实现来提供不同的身份验证方法。

5.5 身份验证和 UsernamePasswordAuthenticationToken

Authentication 和 UsernamePasswordAuthenticationToken 是什么?

  • Authentication

这是 Spring Security 中的一个接口,表示传入身份验证请求的令牌或已认证的主体(表示一个实体,比如个人)AuthenticationManager.authenticate() 方法。

提供的一些方法为:

 Collection<? extends GrantedAuthority> getAuthorities();
 Object getCredentials();
 boolean isAuthenticated();
 void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;  
  • UsernamePasswordAuthenticationToken

此类扩展了 AbstractAuthenticationToken 类(身份验证对象的基类),可用于用户名/密码身份验证请求。

此类有两个构造函数:

public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
    super((Collection)null);
    this.principal = principal;
    this.credentials = credentials;
    this.setAuthenticated(false);
}

public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
    super(authorities);
    this.principal = principal;
    this.credentials = credentials;
    super.setAuthenticated(true);
}

第一个构造函数可用于传入请求以创建未经身份验证的 Authentication 对象。

Authentication authentication = new UsernamePasswordAuthenticationToken(username,password);   

第二个构造函数可用于创建完全经过身份验证的 Authentication 对象。

Authentication authToken = new UsernamePasswordAuthenticationToken(username, password, userAuthorities);   

然后,此完全经过身份验证的 Authentication 对象从 AuthenticationProvider/AuthenticationManager 返回并表示已认证的用户。然后将此已认证的对象设置在 SecurityContext 中。Spring Security 中的 SecurityContext 是当前执行线程的安全上下文的表示。它包含有关当前已认证用户的信息,例如其用户名、权限和会话信息。

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

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

相关文章

API 接口进行多分支管理的方法

原文链接&#xff1a;API 接口进行多分支管理的方法

链表类算法【leetcode】

链表的定义 面试时&#xff0c;需要自己手写... // 单链表 struct ListNode {int val; // 节点上存储的元素ListNode *next; // 指向下一个节点的指针ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数 }; 【构造函数可以省略&#xff0c;C默认生成一个构造函数…

重构开发之道,Blackbox.AI为技术注入智能新动力

本文目录 一、引言二、Blackbox.AI实战体验2.1 基于网页界面生成前端代码进行应用开发2.2 与AI助手实现实时智能对话2.3 重塑大型文件交互方式2.4 链接Github仓库进行对话编程 三、总结 一、引言 在生产力工具加速进化的浪潮中&#xff0c;Blackbox.AI开始崭露头角&#xff0c…

【STM32F1】——9轴姿态传感器JY901与IIC通信

【STM32F1】——9轴姿态传感器JY901与IIC通信 一、简介 本篇主要对9轴姿态传感器JY901的调试过程进行总结,实现了以下功能。 IIC通信采集+串口收发:使用STM32F103C8T6的GPIO口模拟IIC,从JY901读取数据,并通过USART1串口发送到PC。二、JY901介绍 电压:3.3-5V量程:X/Z轴 …

Linux网络——自定义协议与序列化

一、协议 协议是一种 " 约定 ". socket api 的接口 , 在读写数据时 , 都是按 " 字符串 " 的方式来发送接收的。如 果我们要传输一些 " 结构化的数据 "&#xff0c;依然可以通过协议。 其实&#xff0c;协议就是双方约定好的结构化的数据。…

Windows,虚拟机Ubuntu和开发板三者之间的NFS服务器搭建

Windows,虚拟机Ubuntu和开发板三者之间的NFS服务器搭建 &#xff08;1&#xff09;虚拟机 ubuntu 要使用桥接模式&#xff0c;不能使用其他模式 &#xff08;2&#xff09;通过网线将PC和开发板网口直连:这样的连接&#xff0c;开发板是无法连接外网的 &#xff08;3&#xff…

更改Ubuntu22.04锁屏壁纸

更改Ubuntu22.04锁屏壁纸 sudo apt install gnome-shell-extensions gnome-shell-extension-manager安装Gnome Shell 扩展管理器后&#xff0c;打开“扩展管理器”并使用搜索栏找到“锁屏背景”扩展

大模型推理优化技术-KV Cache

近两年大模型火出天际&#xff1b;同时&#xff0c;也诞生了大量针对大模型的优化技术。本系列将针对一些常见大模型优化技术进行讲解。 大模型推理优化技术-KV Cache大模型推理服务调度优化技术-Continuous batching大模型底显存推理优化-Offload技术大模型推理优化技术-KV C…

力扣 LeetCode 24. 两两交换链表中的节点(Day2:链表)

解题思路&#xff1a; 暂存节点tmp和tmp1 注意&#xff1a;while (cur.next ! null && cur.next.next ! null)表示为偶数和奇数时的循环停止条件&#xff0c;并且while语句中的顺序不可交换&#xff0c;交换会报空指针异常 class Solution {public ListNode swapPai…

动态规划-背包问题——494.目标和

1.状态表示 题目来源 494.目标和——力扣 测试用例 2.算法原理 1.状态表示 首先我们需要将问题简化&#xff0c;这里需要找到能将数组组合计算成为指定数字target的添加方式&#xff0c;那么我们就可以将数字分为两类&#xff0c;一类是前面添加""的&#xff0c;另…

哪些因素会导致充电器的充电速度变慢?-纳米软件

充电器的充电速度变慢可能由多种原因引起。以下是一些常见的因素&#xff1a; 一、充电器本身的问题 充电头功率不足&#xff1a;不同的充电头有不同的输出功率&#xff0c;如果使用的充电头功率较低&#xff0c;那么充电速度就会变慢。例如&#xff0c;一些老旧的充电头可能…

刷题强训(day06) -- 大数加法、链表相加、大数乘法

目录 1、大数加法 1.1 题目 1.2 思路 1.3 代码实现 2、链表相加&#xff08;二&#xff09; 2.1 题目 2.2 思路 2.3 代码实现 3、大数乘法 3.1 题目 3.2 思路 3.3 代码实现 1、大数加法 1.1 题目 1.2 思路 这道题可以模拟列竖式相加解答&#xff0c; 将每一位都转…

数字后端教程之Innovus report_property和get_property使用方法及应用案例

数字IC后端实现Innovus中使用report_property可以报告出各种各样object的属性&#xff0c;主要有cell&#xff0c;net&#xff0c;PG Net&#xff0c;Pin&#xff0c;时钟clock&#xff0c;时序库lib属性&#xff0c;Design属性&#xff0c;timing path&#xff0c;timin arc等…

网络基础 - 网段划分篇

我们知道&#xff0c;IP 地址(IPv4 地址)由 “网络标识(网络地址)” 和 “主机标识(主机地址)” 两部分组成&#xff0c;例如 192.168.128.10/24&#xff0c;其中的 “/24” 表示从第 1 位开始到多少位属于网络标识&#xff0c;那么&#xff0c;剩余位就属于主机标识了&#xf…

python实战(八)——情感识别(多分类)

一、任务目标 本文使用的是来自Kaggle的一个情感识别数据集&#xff0c;这个数据集的总数据量是5934条&#xff0c;标签为anger、fear、joy三种情感的其中一种&#xff0c;很明显是一个多分类任务。这里&#xff0c;我们将使用微调技巧进行深度学习建模&#xff0c;同时我们会比…

23423234

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

opencv入门学习总结

opencv学习总结 不多bb&#xff0c;直接上代码&#xff01;&#xff01;&#xff01; 案例一&#xff1a; import cv2 # 返回当前安装的 OpenCV 库的版本信息 并且是字符串格式 print(cv2.getVersionString()) """ 作用&#xff1a;它可以读取不同格式的图像文…

MySQL 中的索引下推功能

看到索引&#xff0c;应该大家都可以联想到这个是和查询效率有关系的&#xff0c;既然有这个功能&#xff0c;那么那句古话说的好啊&#xff1a;存在即合理。那么这个就是说有了这个功能&#xff0c;可以提升查询效率。 什么是索引下推 我们先有一个大概的理解&#xff1a;在…

重拾CSS,前端样式精读-媒体查询

前言 本文收录于CSS系列文章中&#xff0c;欢迎阅读指正 说到媒体查询&#xff0c;大家首先想到的可能是有关响应式的知识点&#xff0c;除此之外&#xff0c;它还可以用于条件加载资源&#xff0c;字体大小&#xff0c;图像和视频的优化&#xff0c;用户界面调整等等方面&am…

物理设备命名规则(Linux网络服务器 15)

Linux系统中的一切都是文件&#xff0c;硬件设备也不例外。既然都是文件&#xff0c;就必须有文件名称。系统内核中udev设备管理器会自动把硬件名称规范化起来&#xff0c;目的是让用户通过设备文件的名字可以大致了解设备属性以及分区信息。这对于陌生的设备来说特别方便。另外…