【Spring】Spring Security学习笔记

news2024/12/28 18:00:18

基本概念

概念定义
认证判断一个用户身份是否合法的过程(登录过程)
会话为了避免用户的每次操作都进行认证, 将用户的信息保存在会话中. 常见的会话有基于session的模式和基于token的模式
授权校验用户是否有权限访问某个资源 认证是为了验证用户的身份; 授权是为了验证用户是否有权限访问某个资源

会话

基于Session的模式

在这里插入图片描述

  1. 用户登录后服务端产生一个SessionId返回给用户
  2. 用户每次请求都携带该SessionId; 服务端据此判断客服端是否认证过

基于Token的模式

在这里插入图片描述

  1. 服务端产生一个Token返回给用户
  2. 用户请求携带Token, 服务端校验该Token判断客户端是否认证过
  3. 相比于Session模式, 服务端无需存储Token 例如JWT令牌

授权

在这里插入图片描述

  1. 用户对应角色; 角色对应权限; 权限限定资源的访问

RBAC (Role(Resource)-Based Access Control)

  1. RBAC分为基于角色的权限控制和基于资源的权限控制

基于Session的认证方式

注: 基于Session的认证机制由Servlet规范定制, Servlet容器已实现

项目依赖

<!-->pom.xml</-->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.passnight</groupId>
    <artifactId>spring-note</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-note</name>
    <description>spring-note</description>
    <properties>
        <java.version>11</java.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!--
        https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </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>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

实现验证

Controller

package com.passnight.springboot.security.controller;

import com.passnight.springboot.security.service.AuthenticationService;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

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

    AuthenticationService authenticationService;

    @GetMapping("/login")
    public String login(@RequestParam String username, @RequestParam String password) {
        return String.valueOf(authenticationService.login(username, password));
    }

    @GetMapping("/version")
    public String version() {
        return "version";
    }

    @Autowired
    public LoginController(AuthenticationService authenticationService) {
        this.authenticationService = authenticationService;
    }
}

Service

package com.passnight.springboot.security.service;

import lombok.AllArgsConstructor;
import lombok.NonNull;
import org.springframework.stereotype.Service;

@Service
@AllArgsConstructor
public class AuthenticationService {
    public boolean login(@NonNull String username, @NonNull String password) {
        return username.equals("user") && password.equals("123456");
    }
}

Test

package com.passnight.springboot.security.web;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;

import java.util.Map;

@SpringBootTest
public class LoginWebTest {
    DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory("http://localhost:8080");
    RestTemplate restTemplate = new RestTemplate();

    @Test
    public void version() {
        String result = restTemplate.getForObject("/login/version", String.class);
        Assertions.assertEquals("version", result);
    }

    @Test
    public void failedToLogin() {
        Map<String, String> param = Map.of("username", "user", "password", "wrong password");
        String result = restTemplate.getForObject(uriBuilderFactory
                        .uriString("/login/login")
                        .queryParam("username", "user")
                        .queryParam("password", "wrong password")
                        .build(),
                String.class);
        Assertions.assertEquals("false", result);
    }
        @Test
    public void login() {
        String result = restTemplate.getForObject(uriBuilderFactory
                        .uriString("/login/login")
                        .queryParam("username", "user")
                        .queryParam("password", "123456")
                        .build(),
                String.class);
        Assertions.assertEquals("true", result);
    }
}

实现Session

    @GetMapping("/login-with-session")
    public String loginWithSession(@RequestParam String username, @RequestParam String password, HttpSession session) {
        if (authenticationService.login(username, password)) {
            session.setAttribute(PROTECTED_RESOURCE_TOKEN, true);
            return "login success";
        } else {
            return "failed to login";
        }
    }

    @GetMapping("/resource-protected-by-session")
    public String resourceProtectedBySession(HttpSession session) {
        if (session.getAttribute(PROTECTED_RESOURCE_TOKEN) != null) {
            return "resource protected by session";
        } else {
            return "you are not allowed to request  this resource";
        }
    }

    @GetMapping("/logout")
    public String logout(HttpSession session) {
        session.invalidate();
        return "logout success";
    }

直接访问受保护资源

    @Test
    public void requestResourceProtectedBySession() {
        String result = restTemplate.getForObject(uriBuilderFactory
                        .uriString("/login/resource-protected-by-session")
                        .build(),
                String.class);
        System.out.println(result);
    }
// you are not allowed to request  this resource

先登录再访问受保护资源1

# 使用浏览器访问; RestTemplate不会共享Session; 如果想共享资源可以在cookie中添加jssion
http://localhost:8080/login/login-with-session?username=user&password=123456
# login success
http://localhost:8080/login/resource-protected-by-session
# resource protected by session
http://localhost:8080/login/logout
# logout success
http://localhost:8080/login/resource-protected-by-session?username=user
# you are not allowed to request this resource

基于SpringSecurity实现

配置

依赖

<!-- 在pom.xml中添加该依赖 -->
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

配置

package com.passnight.springboot.security.config;

import org.springframework.context.annotation.Bean;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // 定义用户信息

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

        // 创建两个默认用户
        manager.createUser(User.withUsername("user1").password("123456").authorities("r3").build());
        manager.createUser(User.withUsername("user2").password("654321").authorities("r4").build());
        return manager;
    }

    // 密码编辑器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
    // 安全拦截器

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            // 限制资源的访问
                .antMatchers("/resource/**")
                .authenticated()
                .anyRequest()
                .permitAll()
                .and()
                .formLogin()
                .successForwardUrl("/index");
    }
}

资源

package com.passnight.springboot.security.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/resource")
public class ResourceController {

    @GetMapping("/r1")
    public String r1() {
        return "r1";
    }

    @GetMapping("/r2")
    public String r2() {
        return "r2";
    }

    @GetMapping("/r3")
    public String r3() {
        return "r3";
    }

    @GetMapping("/r4")
    public String r4() {
        return "r4";
    }
}

结果

在这里插入图片描述

  1. 访问资源自动调转到登录界面: Please sign in

授权

@Override
@Bean
protected UserDetailsService userDetailsService() {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

    // 创建两个默认用户; user1只有r3授权, user2只有r4授权
    manager.createUser(User.withUsername("user1").password("123456").authorities("r3").build());
    manager.createUser(User.withUsername("user2").password("654321").authorities("r4").build());
    return manager;
}

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            // 为r3添加鉴权
                .antMatchers("/resource/r3").hasAnyAuthority("r3")
            // 为r4添加鉴权
                .antMatchers("/resource/r4").hasAnyAuthority("r4")
            // 其他的只需要登录即可
                .antMatchers("/resource/**")
                .authenticated()
                .anyRequest()
                .permitAll()
                .and()
                .formLogin()
                .successForwardUrl("/index");
    }

user1访问r1结果

在这里插入图片描述

user1访问r3结果

在这里插入图片描述

user1访问r4结果

在这里插入图片描述

基于注解实现

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) // 开启基于注解的实现
@Security // 指定安全性, 如资源权限或角色等; 不支持EL表达式
@PreAuthority // 在方法前执行鉴权
@PostAuthority // 方法后执行鉴权

测试用例

// 只有拥有"p1"权限才能访问
@GetMapping("r5")
@PreAuthorize("hasAuthority('p1')")
public String r5() {
    return "r5";
}

// 只有同时拥有"p1", p2"权限才能访问
@GetMapping("r6")
@PostAuthorize("hasAuthority('p1') and hasAuthority('p2')")
public String r6() {
	return "r6";
}

SpringSecurity结构

在这里插入图片描述

  1. SpringSecurity主要解决的问题是安全访问控制
  2. SpringSecurity对Web资源的限制主要是通过Filter实现的

认证流程

在这里插入图片描述

授权流程

在这里插入图片描述

分布式系统认证方案

分布式系统的认证方案

需求:

  1. 尽管不同服务之间相互独立, 考虑到分布式系统开放性的特点, 不仅需要对服务的内部提供认证, 还要对第三方接口进行认证
  2. 统一授权认证: 提供独立的认证服务, 统一处理认证授权
  3. 应用接入认证: 提供扩展和开发机制, 提供安全的系统对接机制, 并可开放部分API给第三方使用

基于Session的认证

在这里插入图片描述

  1. 使用认证服务统一认证, 对Session有以下做法
  2. Session复制: 多台应用服务器之间同步Session, 使Session保持一致, 对外透明
  3. Session黏贴: 当用户访问集群中某台服务器之后, 强制指定后续所有请求均落到该机器上
  4. Session集中存储: 将所有的Session集中存储到某分布式缓存中, 所有应用实例统一从分布式缓存中取Session

基于Token的认证

在这里插入图片描述

  1. 认证完成之后, 服务端向客户端返回Token, 客户端请求携带Token, 服务端使用算法认证是否可以访问
  2. 缺点
    1. Token包含的信息较Session大,
    2. 且需要消耗服务器的计算资源
  3. 优点:
    1. Token对于各种系统统一一致的认证机制
    2. 更加适用于第三方接入的场合 主流协议有Oauth2.0, JWT
    3. 无需服务端存储会话信息, 减轻了服务端的存储压力
  4. 实现: 在这里插入图片描述

OAuth2.0

介绍

OAuth是一个

  1. 开放标准
  2. 允许用户授权第三方应用访问他们存储在其他服务提供者上面的信息, 而不需要将用户名和密码提供给第三方应用或分享他们的数据内容 例如微信扫码后, 微信给某网站发送一个令牌, 该网站收到令牌后即获取微信中的信息, 即可登录成功

在这里插入图片描述

特点

  1. 优点:
    1. 更安全, 客户端不接触用户密码, 服务端更容易集中维护
    2. 广泛使用
    3. Token短寿命
    4. 集中授权, 资源服务器和授权服务器解耦
  2. 缺点
    1. 协议框架宽泛, 造成各种实现的兼容性和互操作性差
    2. 不是一个认证协议, 本身不能告诉你任何用户信息

SpringCloud Security OAuth2

OAuth2主要分为两个服务, 授权服务和资源服务:

  • Authorization server: 认证请求, 默认URL为/oauth/authorize
  • TokenEndpoint: 令牌请求, 默认URL为/oauth/token

在这里插入图片描述

相关概念

  1. 参与角色
    1. 授权服务器(AuthorizationServer): 在成功验证资源所有者且获得授权后颁发访问令牌给客户端的服务器,使得授权客户端应用能够访问资源拥有者所拥有的资源。
    2. .资源服务器(ResourceServer):托管受保护资源的服务器,能够接收和响应使用访问令牌对受保护资源的请求。需要注意的是授权服务器和资源服务器可以是同一个服务器,也可以不是同一个服务器。
    3. 资源所有者(ResourceOwner):指拥有共享数据的人或应用。比如微信的用户或者淘宝的用户就是是资源拥有者,他们拥有的资源就是他们的数据。需要注意的是资源拥有者也可以是一个应用,不一定必须是人。
    4. 客户端应用(client):指请求访问存储在资源服务器的资源的应用。 记住, 不是用户
  2. 认证角色
    1. 客户凭证(client credentials): 客户端的clientId和密码用于认证客户
    2. 令牌(token): 授权服务器在接收到客户请求和, 颁发的令牌
    3. 作用域(scope): 客户请求访问令牌时, 由资源拥有额外指定的细分权限
  3. 令牌类型
    1. 授权码: 仅用于授权码类型, 用于交换获取访问令牌和刷新令牌
    2. 访问令牌: 用于代表一个用户或服务直接去访问受保护的资源
    3. 刷新令牌: 用于去授权服务器获取一个刷新访问令牌
    4. BearerToken: 不管谁拿到该Token, 都可以访问资源
    5. Proof of Permission (Pop) Token: 可以校验client是否对Token由明确的拥有权

模式

授权码模式2

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=figures%2FS%5D(https%3A%2F%2Fimg-blog.csdnimg.cn%2F3d77716669494f33b7c1343278f52776.png&pos_id=img-F9sXKyte-1695448537072)

简化授权码模式

在这里插入图片描述

密码模式

在这里插入图片描述

客户端凭证模式

在这里插入图片描述

刷新令牌
 +--------+                                           +---------------+
  |        |--(A)------- Authorization Grant --------->|               |
  |        |                                           |               |
  |        |<-(B)----------- Access Token -------------|               |
  |        |               & Refresh Token             |               |
  |        |                                           |               |
  |        |                            +----------+   |               |
  |        |--(C)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(D)- Protected Resource --| Resource |   | Authorization |
  | Client |                            |  Server  |   |     Server    |
  |        |--(E)---- Access Token ---->|          |   |               |
  |        |                            |          |   |               |
  |        |<-(F)- Invalid Token Error -|          |   |               |
  |        |                            +----------+   |               |
  |        |                                           |               |
  |        |--(G)----------- Refresh Token ----------->|               |
  |        |                                           |               |
  |        |<-(H)----------- Access Token -------------|               |
  +--------+           & Optional Refresh Token        +---------------+

授权码模式实现

依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>	

授权服务器配置

package com.passnight.cloud.security.authserver.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client") // 客户端Id
                .secret("654321") // 秘钥
                .redirectUris("https://www.baidu.com") // 重定向地址
                .scopes("all") // 授权范围
                .authorizedGrantTypes("authorization_code"); // 授权类型(授权码)
    }
}

资源服务器配置

package com.passnight.cloud.security.authserver.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .requestMatchers()
                .antMatchers("/user/**")
                .and()
                .csrf().disable();
    }
}

权限配置

package com.passnight.cloud.security.authserver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/oauth/**", "/login/**", "logout/**")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .and()
                .csrf().disable();
    }

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

鉴权服务

package com.passnight.cloud.security.authserver.service;

import com.passnight.cloud.security.authserver.config.SecurityConfig;
import lombok.AllArgsConstructor;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
@AllArgsConstructor
public class UserService implements UserDetailsService {

    private final List<UserDetails> users = new ArrayList<>();

    {
        PasswordEncoder passwordEncoder = new SecurityConfig().passwordEncoder();
        users.add(User.builder().username("user1").password(passwordEncoder.encode("123456")).authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("admin")).build());
        users.add(User.builder().username("user2").password(passwordEncoder.encode("123456")).authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("user")).build());
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return users.stream()
                .filter(user -> user.getUsername().equals(username))
                .findAny()
                .orElseThrow(() -> new UsernameNotFoundException(String.format("Can't find user: %s", username)));
    }
}

资源配置

package com.passnight.cloud.security.authserver.controller;

import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("getCurrentUser")
    public Object getCurrentUser(Authentication authentication) {
        return authentication.getPrincipal();
    }
}

访问

http://server.passnight.local:8101/oauth/authorize?client_id=client&response_type=code

询问你是否授权客户端权限访问受保护的资源

在这里插入图片描述

并拿到授权码

在这里插入图片描述

# 请求, 并获得token
passnight@passnight-s600:~$ curl --location 'http://server.passnight.local:8101/oauth/token' --header 'Content-Type: application/x-www-form-urlencoded' --header 'Authorization: Basic Y2xpZW50OjY1NDMyMQ==' --header 'Cookie: JSESSIONID=DE362C8D16D0FA00EB979D1D8D4D9A7B' --data-urlencode 'grant_type=authorization_code' --data-urlencode 'client_id=client' --data-urlencode 'redirect_uri=https://www.baidu.com' --data-urlencode 'code=iT2Zzx' --data-urlencode 'scope=all'
{"access_token":"ae37e184-ec54-422f-ad40-17d0e420f03a","token_type":"bearer","expires_in":43155,"scope":"all"}

密码模式

    @Bean
    @SneakyThrows
    public AuthenticationManager authenticationManager() {
        return super.authenticationManager();
    }
// 添加密码模式配置
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService);
    }
// 显示声明
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client") 
                .secret(passwordEncoder.encode("654321")) 
                .redirectUris("https://www.baidu.com") 
                .scopes("all")
                .authorizedGrantTypes("authorization_code", "password"); // 添加密码模式
    }

访问

passnight@passnight-s600:~$ curl --location 'http://server.passnight.local:8101/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic Y2xpZW50OjY1NDMyMQ==' \ # username=client & password=654321 (请求去掉注释)
--data-urlencode 'grant_type=password' \
--data-urlencode 'scope=all' \
--data-urlencode 'username=user1' \
--data-urlencode 'password=123456'
{"access_token":"3a354527-d25e-4940-9dc0-4ccedbe9f0a8","token_type":"bearer","expires_in":43199,"scope":"all"}

JWT

常见的认证机制

Http Basic Auth

每次请求的时候都要携带用户名和密码

Cookie Auth

  1. 客户端创建Cookie对象
  2. 服务端创建Session对象
  3. 比对CookieSession来鉴权
  4. 一般来说, 关闭浏览器的时候Cookie也会被删除

OAuth

  1. 允许用户第三方应用访问某一服务器上存储的私密资源

Token Auth

  1. 客户端使用用户名和密码登录
  2. 幅度按验证用户名密码, 并返回一个Token给客户端
  3. 客户端存储Token, 请求携带该Token, 服务端通过验证Token鉴权

JWT

JSON Web Tokens - jwt.io

特点

  1. 优点
    1. 基于JSON, 抑郁解析
    2. 可以在令牌中自定义内容, 易扩展
    3. 通过非对称加密算法及数字签名技术, 可防止篡改, 安全性高
    4. 资源服务使用JWT可以不依赖认证服务完成授权
  2. 缺点
    1. JWT令牌较长, 占用存储空间较大

组成

// 头部; 可以使用base64进行编码
{
    "alg": "HS265",
    "typ": "JWT"
}
// 负载(payload), 即存放有效信息的地方
// 可以分类为: 标准中注册的声明, 公共的声明, 私有的声明; 私有的声明需要通知验证规则, 公有声明不需要
// 一般不添加敏感信息, 因为不加密
{
    "sub": "123456", // 标准声明
    "name": "User name", // 公共声明
    "iat": 123456 // 私有声明
}

// 签证, 签名(signature): 由头部, 负载, 和盐(保密)组成

JJWT

JJWT是一个提供对JWT端到端验证的Java

集成到Oauth

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

配置Jwt

package com.passnight.cloud.security.authserver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

@Configuration
public class JwtConfig {

    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        final JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setSigningKey("secret_key");
        return jwtAccessTokenConverter;
    }
}

集成到OAuth

package com.passnight.cloud.security.authserver.config;

import com.passnight.cloud.security.authserver.service.UserService;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;

@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private PasswordEncoder passwordEncoder;
    private AuthenticationManager authenticationManager;
    private UserService userService;
    private TokenStore tokenStore;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService)
                .tokenStore(tokenStore); // 设置tokenStore为JWT TokenStore
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client") // 客户端Id
                .secret(passwordEncoder.encode("654321")) // 秘钥
                .redirectUris("https://www.baidu.com") // 重定向地址
                .scopes("all") // 授权范围
                .authorizedGrantTypes("authorization_code", "password"); // 授权类型(授权码)
    }
}

测试

passnight@passnight-s600:~$ curl --location 'http://server.passnight.local:8101/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic Y2xpZW50OjY1NDMyMQ==' \ # username=client & password=654321 (请求去掉注释)
--data-urlencode 'grant_type=password' \
--data-urlencode 'scope=all' \
--data-urlencode 'username=user1' \
--data-urlencode 'password=123456'
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTEyNjU0NzQsInVzZXJfbmFtZSI6InVzZXIxIiwiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiYWNlZTViYTYtMDU2My00ZjliLTgwMTEtZWVhYWQzZGI0YzZiIiwiY2xpZW50X2lkIjoiY2xpZW50Iiwic2NvcGUiOlsiYWxsIl19.avWqGdBfclj5wPkNOjSMmUBY3h0NCc1EkxAPAjhWZms","token_type":"bearer","expires_in":43199,"scope":"all","jti":"acee5ba6-0563-4f9b-8011-eeaad3db4c6b"}

添加自定义声明

声明Enhancer

package com.passnight.cloud.security.authserver.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

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

@Configuration
public class JwtConfig {

    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setSigningKey("secret_key");
        return jwtAccessTokenConverter;
    }
// 注入Enhancer
    @Bean
    public TokenEnhancer jwtTokenEnhancer() {
        return new JwtTokenEnhancer();
    }


}
// 实现TokenEnhancer
class JwtTokenEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Map<String, Object> map = new HashMap<>();
        map.put("enhance", "enhance info");

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map);
        return accessToken;
    }
}

配置到鉴权服务器中

package com.passnight.cloud.security.authserver.config;

import com.passnight.cloud.security.authserver.service.UserService;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private PasswordEncoder passwordEncoder;
    private AuthenticationManager authenticationManager;
    private UserService userService;
    private TokenStore tokenStore;
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    private TokenEnhancer jwtTokenEnhancer;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        // 设置增强内容
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancerDelegates = new ArrayList<>();
        tokenEnhancerDelegates.add(jwtTokenEnhancer);
        tokenEnhancerDelegates.add(jwtAccessTokenConverter);
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancerDelegates);

        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userService)
                .tokenStore(tokenStore)
                .accessTokenConverter(jwtAccessTokenConverter)
            // 添加TokenEnhancer
                .tokenEnhancer(tokenEnhancerChain);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client") // 客户端Id
                .secret(passwordEncoder.encode("654321")) // 秘钥
                .redirectUris("https://www.baidu.com") // 重定向地址
                .scopes("all") // 授权范围
                .authorizedGrantTypes("authorization_code", "password"); // 授权类型(授权码)
    }
}

测试

passnight@passnight-s600:~$ curl --location 'http://server.passnight.local:8101/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic Y2xpZW50OjY1NDMyMQ==' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'scope=all' \
--data-urlencode 'username=user1' \
--data-urlencode 'password=123456'
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2OTEyNjc3ODMsImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6ImYzNWQ3ZjQwLWM2NjgtNGY5Yi1hZDhjLWExNTRmNDA5Mjc5MyIsImNsaWVudF9pZCI6ImNsaWVudCIsImVuaGFuY2UiOiJlbmhhbmNlIGluZm8ifQ.k6lhmr1VTvKxAPO8As9u1popZKpUiexUXTaK884XELc","token_type":"bearer","expires_in":43199,"scope":"all","enhance":"enhance info","jti":"f35d7f40-c668-4f9b-ad8c-a154f4092793"}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

设置过期时间

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
            .withClient("client")
            .secret(passwordEncoder.encode("654321"))
            .redirectUris("https://www.baidu.com"
            .scopes("all")
            .accessTokenValiditySeconds(5) // 添加失效时间
            .refreshTokenValiditySeconds(86400) // 刷新令牌的失效时间
            .authorizedGrantTypes("authorization_code", "password", "refresh_token"); // 添加刷新令牌
}
passnight@passnight-s600:~$ curl --location 'http://server.passnight.local:8101/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic Y2xpZW50OjY1NDMyMQ==' \
--header 'Cookie: JSESSIONID=3BC47F5E2D70C4204D5CCE8F522E58F7' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'scope=all' \
--data-urlencode 'username=user1' \
--data-urlencode 'password=123456'
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2OTEyMjYwNTgsImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6ImZkYjQwNjIxLTg1YzktNDU4NS1iNDQ0LTRiMDc3YTNiZGE1YSIsImNsaWVudF9pZCI6ImNsaWVudCIsImVuaGFuY2UiOiJlbmhhbmNlIGluZm8ifQ.ZTJStxlFuTdH8hjNyVG0OofQc6Fcv-pgQVSqVoTMQ5g","token_type":"bearer","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiJmZGI0MDYyMS04NWM5LTQ1ODUtYjQ0NC00YjA3N2EzYmRhNWEiLCJleHAiOjE2OTEzMTI0NTMsImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6IjRmMGUyZTZiLTY3YzUtNGYyZi1hYTZmLWMyYWEwNWI5NzI0OSIsImNsaWVudF9pZCI6ImNsaWVudCIsImVuaGFuY2UiOiJlbmhhbmNlIGluZm8ifQ.SHMe8Mc3YfYA94D2t3vY7bHXSpN26FKlrWNAzxxANo4","expires_in":4,"scope":"all","enhance":"enhance info","jti":"fdb40621-85c9-4585-b444-4b077a3bda5a"}

# 刷新令牌

passnight@passnight-s600:~$ curl --curl --location 'http://server.passnight.local:8101/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic Y2xpZW50OjY1NDMyMQ==' \
--header 'Cookie: JSESSIONID=3BC47F5E2D70C4204D5CCE8F522E58F7' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI2MDU0ZmJlMi1mMWRiLTQ0ZmYtODE5Ni1iNThkYjM5ZmRhOWIiLCJleHAiOjE2OTEzMTI0NDMsImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6ImVjODQyNjcxLTNmYmYtNDk1Yy04Njg5LWQxYjJlMmZjZGRkMiIsImNsaWVudF9pZCI6ImNsaWVudCIsImVuaGFuY2UiOiJlbmhhbmNlIGluZm8ifQ.2jUBXVniKeQ77KjxWy_kzqX9FtfZrd9efDod8jT6jNU'
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2OTEyMjYxNjksImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6IjhiZWM5NDUyLThkMDktNDRhYy05ZmI3LWMyODcxYmZiNmIxMyIsImNsaWVudF9pZCI6ImNsaWVudCIsImVuaGFuY2UiOiJlbmhhbmNlIGluZm8ifQ.MOwEWrOfwif7BGYgtJqDk1OEh7_HKny-eQo5hScX97w","token_type":"bearer","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI4YmVjOTQ1Mi04ZDA5LTQ0YWMtOWZiNy1jMjg3MWJmYjZiMTMiLCJleHAiOjE2OTEzMTI0NDMsImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6ImVjODQyNjcxLTNmYmYtNDk1Yy04Njg5LWQxYjJlMmZjZGRkMiIsImNsaWVudF9pZCI6ImNsaWVudCIsImVuaGFuY2UiOiJlbmhhbmNlIGluZm8ifQ.RMLjKFrYGhPsSSXT1TroLx3jRMSZyz7duVv4_63B590","expires_in":4,"scope":"all","enhance":"enhance info","jti":"8bec9452-8d09-44ac-9fb7-c2871bfb6b13"}

引用


  1. 关于JSESSIONID - 简书 (jianshu.com) ↩︎

  2. SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用-开源基础软件社区-51CTO.COM ↩︎

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

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

相关文章

生长刺激表达基因2蛋白(ST2)介绍

生长刺激表达基因2蛋白&#xff08;Growth stimulation expressed gene 2&#xff0c;ST2&#xff09;&#xff0c;也称为T1&#xff0c;IL1RL1或Fit1&#xff0c;编码基因定位于染色体2q12位点上&#xff0c;约40kb。属于白细胞介素-1&#xff08;interleukin-1&#xff0c;IL…

2023华为杯数学建模研赛E题全解析

2023华为杯数学建模研赛E题解析&#xff0c;完整版已出!!! 包含所有模型、代码、结果&#xff0c;39页技术文档&#xff0c;详细内容如下! 免费版链接已放在下面&#xff0c;需要的同学可以直接自取~ 【云顶数模】2023研究生数学建模免费链接&#xff1a; https://pan.baid…

Python爬虫教程:解析网页中的元素

前言&#xff1a; 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 在我们理解了网页中标签是如何嵌套&#xff0c;以及网页的构成之后&#xff0c; 我们就是可以开始学习使用python中的第三方库BeautifulSoup筛…

194、SpringBoot -- 下载和安装 Erlang 、 RabbitMQ

本节要点&#xff1a; 一些命令&#xff1a; 小黑窗输入&#xff1a; rabbitmq-plugins enable rabbitmq_management 启动控制台插件 rabbitmq-server 启动rabbitMQ服务器 管理员启动小黑窗&#xff1a; rabbitmq-service install 添加rabbitMQ为本地服务 启动浏览器访问“h…

Goby 漏洞发布|Revive Adserver 广告管理系统 adxmlrpc.php 文件远程代码执行漏洞(CVE-2019-5434)

漏洞名称&#xff1a;Revive Adserver 广告管理系统 adxmlrpc.php 文件远程代码执行漏洞&#xff08;CVE-2019-5434&#xff09; English Name&#xff1a; Revive Adserver adxmlrpc.php Remote Code Execution Vulnerability (CVE-2019-5434) CVSS core: 9.0 影响资产数&a…

Go sync.Cond 原理

文章目录 前言newCondWaitSignalBroadcast和 channel 比较的优势 前言 sync.Cond 基本很少使用&#xff0c;应为大部分都能使用 channel 代替sync.Cond 通常是基于 sync.Mutex 扩展的主要就四个方法 newCond(l locker) 创建CondWait() 阻塞等待Signal() 唤醒其中一个Broadcast…

AI 编码助手 Codewhisperer 安装步骤和使用初体验

文章作者&#xff1a;为了自己加油 最近亚⻢逊云科技推出了一款基于机器学习的AI编程助手 Amazon Code Whisperer&#xff0c;可以实时提供代码建议。在编写代码时&#xff0c;它会自动根据现有的代码和注释给出建议。Amazon Code Whisperer与 GitHub Copilot 类似&#xff0c;…

Klotski: Efficient Obfuscated Execution against Controlled-Channel Attacks

目录 摘要引言贡献新的基于ORAM的防御控制信道攻击优化技术开源实现 背景Intel SGXORAMRing ORAM的组成ORAM树Position Map (位置图)Stash Ring ORAM的访问步骤1、Position Map Lookup 位置映射查找2、ReadPath3、EvictPath4、Early Reshuffles 早期的重新洗牌 相关工作和动机S…

vue3硅谷甄选01 | 使用vite创建vue3项目及项目的配置 环境准备 ESLint配置 prettier配置 husky配置 项目集成

文章目录 使用vite创建vue3项目及项目的配置1.环境准备2.项目配置ESLint校验代码工具配置 - js代码检测工具1.安装ESLint到开发环境 devDependencies2.生成配置文件:.eslint.cjs**3.安装vue3环境代码校验插件**4. 修改.eslintrc.cjs配置文件5.生成ESLint忽略文件6.在package.js…

gym_unity学习笔记

最近学了一段时间gym_unity&#xff0c;把一些资料留在这里 实例 实例gym_unity训练RollerBall&#xff1a;https://blog.csdn.net/alibutter/article/details/120908687实例gyn_unity训练3DBall&#xff1a;https://zhuanlan.zhihu.com/p/554927641?utm_id0 源码&#xff1…

大数据学习技术栈及书籍推荐

作为一名开发人员&#xff0c;特别是后端开发人员&#xff0c;随着网络数据量的持续增长&#xff0c;拥有强大的大数据处理能力已经成为每个公司或产品&#xff08;尤其是2C业务&#xff09;的必备条件。以下是我在网络上搜集和自身研究的基础上&#xff0c;为您推荐的技术栈和…

【测试开发】用例篇 · 熟悉黑盒测试用例设计方法(2)· 正交表 · 场景设计 · 常见案例练习

【测试开发】用例篇&#xff08;2&#xff09; 文章目录 【测试开发】用例篇&#xff08;2&#xff09;1. 正交表法1.1 什么是正交表1.2 两个重要概念1.3 如何通过正交表设计测试用例1.3.1 充分理解需求1.3.2 确定因素、确定水平1.3.3 allpairs画正交表1.3.4 补充正交表1.3.5 将…

2024年浙江工业大学MPA项目适不适合报考?几点建议

浙江工业大学公共管理硕士&#xff08;MPA&#xff09;项目的学习是在浙工大小和山校区&#xff0c;近两年的发展速度相对比较快&#xff0c;目前每年都可以招到100的生源&#xff0c;而报考的考生也越来越多。究竟这个项目的综合竞争力怎么样&#xff0c;适不适合报考&#xf…

1791_树莓派bash入门杂志_Essentials_Bash_v1

全部学习汇总&#xff1a; GreyZhang/little_bits_of_raspberry_pi: my hacking trip about raspberry pi. (github.com) 拿到一份树莓派早期的宣传电子杂志资料&#xff0c;看了一下感觉还是有一些帮助。针对里面多少有一些共鸣的地方&#xff0c;做一个简单的整理。 1. 命令行…

原来,C语言操作Mysql这么简单

迷途小书童 读完需要 10分钟 速读仅需 4 分钟 1 简介 MySQL 是一种流行的关系型数据库管理系统&#xff0c;而 C 语言是一种强大的编程语言&#xff0c;可以与 MySQL 进行交互。本篇博文将介绍如何使用 C 语言来连接、查询和操作 MySQL 数据库。我们将涵盖原理实现、安装步骤、…

windows/ubuntu怎么修改hosts文件

windows系统修改方法&#xff1a; 第一步&#xff1a;用管理员权限打开记事本&#xff0c;或者visual studio。 第二步&#xff1a;用记事本或者vs打开地址C:\Windows\System32\drivers\etc\hosts文件&#xff0c;这个时候就可以直接修改了 Ubuntu22 LTS系统修改方法&#xf…

c++STL案列一评委打分

案例描述 有5名选手:选手ABCDE&#xff0c;10个评委分别对每一名选手打分&#xff0c;去除最高分&#xff0c;去除评委中最低分&#xff0c;取平均分 实现步骤 1.创建五名选手&#xff0c;放到vector中 2.遍历vector容器&#xff0c;取出来每一个选手&#xff0c;执行for循环…

【自然语言处理】【大模型】MPT模型结构源码解析(单机版)

相关博客 【自然语言处理】【大模型】MPT模型结构源码解析(单机版) 【自然语言处理】【大模型】ChatGLM-6B模型结构代码解析(单机版) 【自然语言处理】【大模型】BLOOM模型结构源码解析(单机版) 【自然语言处理】【大模型】极低资源微调大模型方法LoRA以及BLOOM-LORA实现代码 【…

【云原生】Kubernetes学习笔记

部署 在部署前强调几点 不要使用IPv6, 很多组件都不支持IPv6不要使用最新版本, 最新版本非常不稳定, 甚至可能存在无法运行的bug不要版本更新, 安装后就将版本固定下来, 新的版本可能会引入新功能, 或移除旧功能, 导致Kubernetes无法运行 Kubeadm介绍 K8s是由多个模块构成的…