SpringCloudAlibaba篇(九)SpringCloudGateWay整合Oauth2+Jwt实现认证中心

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
传统型负载均衡 CLB,每月750个小时 15LCU
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: SpringCloudAlibaba篇(九)SpringCloudGateWay整合Oauth2+Jwt实现认证中心

前言

通常微服务的认证和授权思路有两种:

  • 网关只负责转发请求,认证鉴权交给每个微服务控制
  • 统一在网关层面认证鉴权,微服务只负责业务
第二种方案的流程图
在这里插入图片描述

采用技术栈

在这里插入图片描述
在这里插入图片描述

父工程依赖及统一版本

附:父工程依赖
    <packaging>pom</packaging>

    <properties>
        <fate.project.version>1.0.0</fate.project.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <maven.plugin.version>3.8.1</maven.plugin.version>
        <spring.boot.version>2.6.3</spring.boot.version>
        <spring-cloud.version>2021.0.1</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
        <alibaba.nacos.version>1.4.2</alibaba.nacos.version>
        <alibaba.sentinel.version>1.8.3</alibaba.sentinel.version>
        <alibaba.dubbo.version>2.7.15</alibaba.dubbo.version>
        <alibaba.rocketMq.version>4.9.2</alibaba.rocketMq.version>
        <alibaba.seata.version>1.4.2</alibaba.seata.version>
        <mybatis.plus.version>3.5.1</mybatis.plus.version>
        <knife4j.version>3.0.2</knife4j.version>
        <swagger.version>3.0.0</swagger.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- springBoot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring.boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- springCloud -->
            <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>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>
    </dependencies>

1. 搭建Oauth2-server

在这里插入图片描述

1.1 oauth2-server 依赖

引用版本可查看上面的父工程依赖
<dependencies>
        <dependency>
            <groupId>top.fate</groupId>
            <artifactId>fate-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--排除logback-->
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--添加log4j2-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <!--SpringBoot2.4.x之后默认不加载bootstrap.yml文件,需要在pom里加上依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>

        <!-- Nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.alibaba.nacos</groupId>
                    <artifactId>nacos-client</artifactId>
                </exclusion>
            </exclusions>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.alibaba.nacos</groupId>
                    <artifactId>nacos-client</artifactId>
                </exclusion>
            </exclusions>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
            <version>${alibaba.nacos.version}</version>
        </dependency>

        <!-- Druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.22</version>
        </dependency>
        <!-- MySQL -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <!-- MyBatis-Plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis.plus.version}</version>
        </dependency>
        <!-- zipkin -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
            <version>2.2.8.RELEASE</version>
        </dependency>
        <!-- redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>9.9.3</version>
        </dependency>
        <!--security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.0</version>
        </dependency>
    </dependencies>

1.2 bootstrap.yml

server:
  port: 9999
url:
  nacos: localhost:8848
spring:
  application:
    name: oauth2-server #实例名
  profiles:
    active: dev
  cloud:
    nacos:
      discovery:
        #集群环境隔离
        cluster-name: shanghai
        #命名空间
        namespace: ${spring.profiles.active}
        #持久化实例 ture为临时实例 false为持久化实例  临时实例发生异常直接剔除, 而持久化实例等待恢复
        ephemeral: true
        #注册中心地址
        server-addr: ${url.nacos}
      config:
        namespace: ${spring.profiles.active}
        file-extension: yaml
        #配置中心地址
        server-addr: ${url.nacos}
        extension-configs[0]:
          data-id: mysql-oauth2.yaml
          group: DEFAULT_GROUP
          refresh: false
        extension-configs[1]:
          data-id: log.properties
          group: DEFAULT_GROUP
          refresh: false
        extension-configs[2]:
          data-id: zipkin.yaml
          group: DEFAULT_GROUP
          refresh: false
        extension-configs[3]:
          data-id: mybatis-plus.yaml
          group: DEFAULT_GROUP
          refresh: false
        extension-configs[4]:
          data-id: redis.yaml
          group: DEFAULT_GROUP
          refresh: false

mysql-oauth2.yaml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/oauth?useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

log.properties

logging.level.root=info

zipkin.yaml

spring:
  zipkin:
    base-url: http://127.0.0.1:9411
    sender:
      type: web
    sleuth:
      sampler:
        probability: 1.0

mybatis-plus.yaml

mybatis-plus:
  mapper-locations: classpath:mapper/*/*.xml,mapper/*.xml
  global-config:
    db-config:
      id-type: auto
      field-strategy: NOT_EMPTY
      db-type: MYSQL
  configuration:
    map-underscore-to-camel-case: true
    call-setters-on-nulls: true
    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

redis.yaml

spring:
  redis:
    host: localhost
    port: 6379

1.3 keytool生成RSA证书

在jdk/bin目录下执行该命令,生成jks文件之后复制到项目中resources中
keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks -keypass 123456

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.4 SysUserServiceImpl 用户信息实现类

实现Spring Security的UserDetailsService接口,用于加载用户信息
package top.fate.service.impl;

import cn.hutool.core.util.ArrayUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import top.fate.domain.SecurityUser;

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

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author fate急速出击
 * @since 2022-05-13
 */
@Service
public class SysUserServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return SecurityUser.builder()
                .userId(UUID.randomUUID().toString().replaceAll("-",""))
                .username("admin")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .authorities(AuthorityUtils.createAuthorityList("user", "admin"))
                .build();
    }
}
SecurityUser 用户封装类
package top.fate.domain;

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

import java.util.Collection;

/**
 * 存储用户的详细信息,实现UserDetails,后续有定制的字段可以自己拓展
 * @auther:Wangxl
 * @Emile:18335844494@163.com
 * @Time:2022/5/13 11:36
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SecurityUser implements UserDetails {

    private String userId;

    //用户名
    private String username;

    //密码
    private String password;

    //权限+角色集合
    private Collection<? extends GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

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

    // 账户是否未过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // 账户是否未被锁
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

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

    @Override
    public boolean isEnabled() {
        return true;
    }
}
## 1.5 JWT内容增强器

package top.fate.component;

import org.s

pringframework.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.stereotype.Component;
import top.fate.domain.SecurityUser;

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

/**

  • JWT内容增强器
  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/13 11:32

*/
@Component
public class JwtTokenEnhancer implements TokenEnhancer {

@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
    SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
    Map<String, Object> info = new HashMap<>();
    //把用户ID设置到JWT中
    info.put("id", securityUser.getUserId());
    ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
    return accessToken;
}

}

## 1.6 Oauth2ServerConfig 认证服务器配置
> 加载用户信息的服务UserServiceImpl及RSA的钥匙对KeyPair

package top.fate.config;

import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import top.fate.component.JwtTokenEnhancer;
import top.fate.service.impl.SysUserServiceImpl;

import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;

/**

  • 认证服务器配置
  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/13 11:36

*/
@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {

private final PasswordEncoder passwordEncoder;
private final SysUserServiceImpl userDetailsService;
private final AuthenticationManager authenticationManager;
private final JwtTokenEnhancer jwtTokenEnhancer;

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
            .withClient("client-app")
            .secret(passwordEncoder.encode("123456"))
            .scopes("all")
            .authorizedGrantTypes("password", "refresh_token")
            .accessTokenValiditySeconds(3600)
            .refreshTokenValiditySeconds(86400);
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
    List<TokenEnhancer> delegates = new ArrayList<>();
    delegates.add(jwtTokenEnhancer);
    delegates.add(accessTokenConverter());
    enhancerChain.setTokenEnhancers(delegates); //配置JWT的内容增强器
    endpoints.authenticationManager(authenticationManager)
            .userDetailsService(userDetailsService) //配置加载用户信息的服务
            .accessTokenConverter(accessTokenConverter())
            .tokenEnhancer(enhancerChain);
}

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    security.allowFormAuthenticationForClients();
}

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
    jwtAccessTokenConverter.setKeyPair(keyPair());
    return jwtAccessTokenConverter;
}

@Bean
public KeyPair keyPair() {
    //从classpath下的证书中获取秘钥对
    KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
    return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
}

}

## 1.7 获取RSA公钥接口

package top.fate.controller;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
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.RestController;

import java.security.KeyPair;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;

/**

  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/13 17:48

*/
@RestController
@RequestMapping(value = "rsa")
public class KeyPairController {

@Autowired
private KeyPair keyPair;

@GetMapping(value = "publicKey")
public Map<String, Object> getKey(){
    RSAPublicKey aPublic = (RSAPublicKey) keyPair.getPublic();
    RSAKey key = new RSAKey.Builder(aPublic).build();
    return new JWKSet(key).toJSONObject();
}

}

## 1.8 配置Spring Security,允许获取公钥接口的访问

package top.fate.config;

import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**

  • SpringSecurity配置
  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/14 15:36

*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
            .antMatchers("/rsa/publicKey").permitAll()
            .anyRequest().authenticated();
}

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

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

}

## 1.9 初始化用户权限demo

package top.fate.service.impl;

import cn.hutool.core.collection.CollUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import top.fate.model.SysConstant;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**

  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/14 10:33

*/
@Service
public class ResourceServiceImpl {

private Map<String, List<String>> resourceRolesMap;
@Resource
private RedisTemplate<String,Object> redisTemplate;

@PostConstruct
public void initData() {
    resourceRolesMap = new TreeMap<>();
    resourceRolesMap.put("/user/tb-user/list", CollUtil.toList("ADMIN"));
    resourceRolesMap.put("/order/order/getUserService", CollUtil.toList("ADMIN", "ROOT"));
    redisTemplate.opsForHash().putAll("oauth2:oauth_urls", resourceRolesMap);
}

}

## 1.10 Redis相关配置

package top.fate.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**

  • Redis相关配置
  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/16 14:29

*/
@Configuration
@EnableRedisRepositories
public class RedisRepositoryConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(connectionFactory);
    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    redisTemplate.setKeySerializer(stringRedisSerializer);
    redisTemplate.setHashKeySerializer(stringRedisSerializer);
    Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
    redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
    redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
    redisTemplate.afterPropertiesSet();
    return redisTemplate;
}

}

# 2. gateway
## 2.1     网关依赖

    <!--网关依赖gateway-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    <!-- Sentinel -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        <version>${spring-cloud-alibaba.version}</version>
    </dependency>
    <!-- Nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        <exclusions>
            <exclusion>
                <groupId>com.alibaba.nacos</groupId>
                <artifactId>nacos-client</artifactId>
            </exclusion>
        </exclusions>
        <version>${spring-cloud-alibaba.version}</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        <exclusions>
            <exclusion>
                <groupId>com.alibaba.nacos</groupId>
                <artifactId>nacos-client</artifactId>
            </exclusion>
        </exclusions>
        <version>${spring-cloud-alibaba.version}</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-client</artifactId>
        <version>${alibaba.nacos.version}</version>
    </dependency>
    <!--SpringBoot2.4.x之后默认不加载bootstrap.yml文件,需要在pom里加上依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    <!-- 加入 log4j2 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-logging</artifactId>
        <exclusions>
            <exclusion>
                <groupId>*</groupId>
                <artifactId>*</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    <!-- zipkin -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zipkin</artifactId>
        <version>2.2.8.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>io.zipkin.brave</groupId>
        <artifactId>brave-instrumentation-dubbo</artifactId>
    </dependency>

    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-spring-boot-starter</artifactId>
        <version>3.0.3</version>
    </dependency>
    <!-- redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- oauth2 -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-resource-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-jose</artifactId>
    </dependency>
    <dependency>
        <groupId>com.nimbusds</groupId>
        <artifactId>nimbus-jose-jwt</artifactId>
        <version>9.9.3</version>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.0</version>
    </dependency>
    <dependency>
        <groupId>top.fate</groupId>
        <artifactId>fate-common</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>
## 2.2 applicaion.yaml

spring:

security:
  oauth2:
    resourceserver:
      jwt:
        jwk-set-uri: 'http://localhost:9999/rsa/publicKey'

secure:
ignore:

urls: #配置白名单路径
  - "/actuator/**"
  - "/oauth2-server/oauth/token"
  - "/oauth2-server/**"
  - "/order/**"
## 2.3 bootstrap.yaml

logging:
file:

# 配置日志的路径,包含 spring.application.name
path: ${spring.application.name}

url:
nacos: localhost:8848
spring:
application:

name: gateway #实例名

profiles:

active: dev

cloud:

nacos:
  discovery:
    #集群环境隔离
    cluster-name: shanghai
    #命名空间
    namespace: ${spring.profiles.active}
    #持久化实例 ture为临时实例 false为持久化实例  临时实例发生异常直接剔除, 而持久化实例等待恢复
    ephemeral: true
    #注册中心地址
    server-addr: ${url.nacos}
  config:
    namespace: ${spring.profiles.active}
    file-extension: yaml
    #配置中心地址
    server-addr: ${url.nacos}
    extension-configs[0]:
      data-id: gateway.yaml
      group: DEFAULT_GROUP
      refresh: false
    extension-configs[1]:
      data-id: sentinel.yaml
      group: DEFAULT_GROUP
      refresh: false
    extension-configs[2]:
      data-id: log.properties
      group: DEFAULT_GROUP
      refresh: false
    extension-configs[3]:
      data-id: redis.yaml
      group: DEFAULT_GROUP
      refresh: false
### gateway.yaml

server:
port: 30001
spring:
cloud:

gateway:
  enabled: true
  discovery:
    locator:
      lower-case-service-id: true
  routes:
    - id: user-service
      uri: lb://user-service
      predicates:
        - Path=/user/**
      filters:
        - StripPrefix=1
    - id: order-service
      uri: lb://order-service
      predicates:
        - Path=/order/**
      filters:
        - StripPrefix=1
    - id: 
      uri: lb://oauth2-server
      predicates:
        - Path=/oauth2-server/**
      filters:
        - StripPrefix=1
### sentinel.yaml

spring:
cloud:

sentinel:
  transport:
    dashboard: localhost:8080
### log.properties

logging.level.root=info

### redis.yaml

spring:
redis:

host: localhost
port: 6379

## 2.4 鉴权管理器

package top.fate.authorization;

import cn.hutool.core.convert.Convert;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;

/**

  • 鉴权管理器,用于判断是否有资源的访问权限
  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/7 9:27

*/
@Component
public class AuthorizationManager implements ReactiveAuthorizationManager {

@Resource
private RedisTemplate<String, Object> redisTemplate;

@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
    //从Redis中获取当前路径可访问角色列表
    URI uri = authorizationContext.getExchange().getRequest().getURI();
    Object obj = redisTemplate.opsForHash().get("oauth2:oauth_urls", uri.getPath());
    List<String> authorities = Convert.toList(String.class, obj);
    authorities = authorities.stream().map(i -> i = "ROLE_" + i).collect(Collectors.toList());
    //认证通过且角色匹配的用户可访问当前路径
    return mono
            .filter(Authentication::isAuthenticated)
            .flatMapIterable(Authentication::getAuthorities)
            .map(GrantedAuthority::getAuthority)
            .any(authorities::contains)
            .map(AuthorizationDecision::new)
            .defaultIfEmpty(new AuthorizationDecision(false));
}

}

## 2.5 自定义返回结果:没有登录或token过期时

package top.fate.component;

import cn.hutool.json.JSONUtil;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import top.fate.api.R;
import top.fate.api.ResultCode;

import java.nio.charset.Charset;

/**

  • 自定义返回结果:没有登录或token过期时
  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/15 19:37

*/
@Component
public class RestAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {

@Override
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
    ServerHttpResponse response = exchange.getResponse();
    response.setStatusCode(HttpStatus.OK);
    response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    String body= JSONUtil.toJsonStr(R.fail(ResultCode.UN_AUTHORIZED.getCode(),ResultCode.UN_AUTHORIZED.getMessage()));
    DataBuffer buffer =  response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
    return response.writeWith(Mono.just(buffer));
}

}


## 2.6 自定义返回结果:没有权限访问时

package top.fate.component;

import cn.hutool.json.JSONUtil;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import top.fate.api.R;
import top.fate.api.ResultCode;

import java.nio.charset.Charset;

/**

  • 自定义返回结果:没有登录或token过期时
  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/15 19:37

*/
@Component
public class RestAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {

@Override
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
    ServerHttpResponse response = exchange.getResponse();
    response.setStatusCode(HttpStatus.OK);
    response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    String body= JSONUtil.toJsonStr(R.fail(ResultCode.UN_AUTHORIZED.getCode(),ResultCode.UN_AUTHORIZED.getMessage()));
    DataBuffer buffer =  response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
    return response.writeWith(Mono.just(buffer));
}

}

## 2.7 网关白名单配置

package top.fate.config;

import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

/**

  • 网关白名单配置
  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/16 14:27

*/
@Data
@EqualsAndHashCode(callSuper = false)
@Component
@ConfigurationProperties(prefix="secure.ignore")
public class IgnoreUrlsConfig {

private List<String> urls;

}



## 2.8 Redis相关配置

package top.fate.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**

  • Redis相关配置
  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/16 14:29

*/
@Configuration
@EnableRedisRepositories
public class RedisRepositoryConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(connectionFactory);
    StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
    redisTemplate.setKeySerializer(stringRedisSerializer);
    redisTemplate.setHashKeySerializer(stringRedisSerializer);
    Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
    redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
    redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
    redisTemplate.afterPropertiesSet();
    return redisTemplate;
}

}

## 2.9 资源服务器配置

package top.fate.config;

import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import reactor.core.publisher.Mono;
import top.fate.authorization.AuthorizationManager;
import top.fate.component.RestAuthenticationEntryPoint;
import top.fate.component.RestfulAccessDeniedHandler;
import top.fate.filter.IgnoreUrlsRemoveJwtFilter;
/**

  • 资源服务器配置
  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/16 14:31

*/
@AllArgsConstructor
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {

private final AuthorizationManager authorizationManager;
private final IgnoreUrlsConfig ignoreUrlsConfig;
private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
    http.oauth2ResourceServer().jwt()
            .jwtAuthenticationConverter(jwtAuthenticationConverter());
    //自定义处理JWT请求头过期或签名错误的结果
    http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);
    //对白名单路径,直接移除JWT请求头
    http.addFilterBefore(ignoreUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);
    http.authorizeExchange()
            .pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(),String.class)).permitAll()//白名单配置
            .anyExchange().access(authorizationManager)//鉴权管理器配置
            .and().exceptionHandling()
            .accessDeniedHandler(restfulAccessDeniedHandler)//处理未授权
            .authenticationEntryPoint(restAuthenticationEntryPoint)//处理未认证
            .and().csrf().disable();
    return http.build();
}

@Bean
public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
    JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
    jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
    jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
    JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
    jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
    return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
}

}


## 2.10 将登录用户的JWT转化成用户信息的全局过滤器

package top.fate.filter;

import cn.hutool.core.util.StrUtil;
import com.nimbusds.jose.JWSObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.text.ParseException;

/**

  • 将登录用户的JWT转化成用户信息的全局过滤器
  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/16 12:31

*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

private static Logger LOGGER = LoggerFactory.getLogger(AuthGlobalFilter.class);

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    String token = exchange.getRequest().getHeaders().getFirst("Authorization");
    if (StrUtil.isEmpty(token)) {
        return chain.filter(exchange);
    }
    try {
        //从token中解析用户信息并设置到Header中去
        String realToken = token.replace("Bearer ", "");
        JWSObject jwsObject = JWSObject.parse(realToken);
        String userStr = jwsObject.getPayload().toString();
        LOGGER.info("AuthGlobalFilter.filter() user:{}",userStr);
        ServerHttpRequest request = exchange.getRequest().mutate().header("user", userStr).build();
        exchange = exchange.mutate().request(request).build();
    } catch (ParseException e) {
        e.printStackTrace();
    }
    return chain.filter(exchange);
}

@Override
public int getOrder() {
    return 0;
}

}

## 2.11 白名单路径访问时需要移除JWT请求头

package top.fate.filter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import top.fate.config.IgnoreUrlsConfig;

import java.net.URI;
import java.util.List;

/**

  • 白名单路径访问时需要移除JWT请求头
  • @auther:Wangxl
  • @Emile:18335844494@163.com
  • @Time:2022/5/16 16:42

*/
@Component
public class IgnoreUrlsRemoveJwtFilter implements WebFilter {

@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    ServerHttpRequest request = exchange.getRequest();
    URI uri = request.getURI();
    PathMatcher pathMatcher = new AntPathMatcher();
    //白名单路径移除JWT请求头
    List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
    for (String ignoreUrl : ignoreUrls) {
        if (pathMatcher.match(ignoreUrl, uri.getPath())) {
            request = exchange.getRequest().mutate().header("Authorization", "").build();
            exchange = exchange.mutate().request(request).build();
            return chain.filter(exchange);
        }
    }
    return chain.filter(exchange);
}

}

# 测试
> 访问 [http://localhost:30001/oauth2-server/oauth/token](http://localhost:30001/oauth2-server/oauth/token)
> 密码模式
![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/d5ee4cef0f1b481081eb2aa2f2f51674.png)
> 刷新token
> ![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/9fe1bb39a9a841e385b0740f0a96fb51.png)
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
SQL Java 测试技术
在Spring boot中 使用JWT和过滤器实现登录认证
在Spring boot中 使用JWT和过滤器实现登录认证
233 0
|
1月前
|
JSON 安全 数据安全/隐私保护
Python认证新风尚:OAuth遇上JWT,安全界的时尚Icon👗
【10月更文挑战第2天】在当今互联网世界中,数据安全与隐私保护日益重要。Python 作为广泛应用于 Web 开发的语言,其认证机制也不断进化。OAuth 2.0 和 JSON Web Tokens (JWT) 成为当前最热门的安全认证方案,不仅保障数据安全传输,还简化了用户认证流程。本文将介绍 Python 如何结合 OAuth 2.0 和 JWT 打造安全高效的认证体系。
39 3
|
3月前
|
存储 JSON 安全
从入门到精通:Python中的OAuth与JWT,打造无懈可击的认证体系🔒
【8月更文挑战第4天】构建现代Web和移动应用时,用户认证与授权至关重要。Python集成OAuth和JWT技术,能轻松实现安全认证。本文从OAuth基础入手,介绍如何使用`requests-oauthlib`库简化流程,再到JWT进阶应用,利用`PyJWT`库生成及验证令牌。最后,探讨如何结合两者,创建无缝认证体验。通过代码示例,由浅入深地引导读者掌握构建坚固应用认证体系的方法。
99 2
|
18天前
|
JSON 安全 数据安全/隐私保护
Python认证新风尚:OAuth遇上JWT,安全界的时尚Icon👗
在当今互联网世界中,数据安全和隐私保护至关重要。Python 作为 Web 开发的主流语言,其认证机制也在不断进步。OAuth 2.0 和 JSON Web Tokens (JWT) 是当前最热门的安全认证方案,不仅保障数据安全传输,还简化用户认证流程。本文介绍如何在 Python 中结合 OAuth 2.0 和 JWT,打造一套既安全又高效的认证体系。通过 Flask-HTTPAuth 和 PyJWT 等库,实现授权和验证功能,确保每次请求的安全性和便捷性。
31 3
|
25天前
|
JSON 算法 安全
JWT Bearer 认证在 .NET Core 中的应用
【10月更文挑战第30天】JWT(JSON Web Token)是一种开放标准,用于在各方之间安全传输信息。它由头部、载荷和签名三部分组成,用于在用户和服务器之间传递声明。JWT Bearer 认证是一种基于令牌的认证方式,客户端在请求头中包含 JWT 令牌,服务器验证令牌的有效性后授权用户访问资源。在 .NET Core 中,通过安装 `Microsoft.AspNetCore.Authentication.JwtBearer` 包并配置认证服务,可以实现 JWT Bearer 认证。具体步骤包括安装 NuGet 包、配置认证服务、启用认证中间件、生成 JWT 令牌以及在控制器中使用认证信息
|
2月前
|
安全 Java 数据安全/隐私保护
|
2月前
|
JSON 安全 数据安全/隐私保护
Python 安全性大揭秘:OAuth 与 JWT,不只是认证,更是信任的传递
【9月更文挑战第4天】在数字化时代,确保应用安全至关重要。Python 作为广泛使用的编程语言,提供了强大的安全认证工具,如 OAuth 和 JWT。OAuth 是一种授权框架,允许第三方应用在有限权限下访问用户资源;JWT 则是一种自包含的数据传输格式,用于安全地传递声明。通过合理配置和使用这些技术,可以有效提升应用安全性,保障用户数据安全。正确管理和定期更新密钥、严格测试 JWT 的生成与验证等最佳实践,对于构建安全可靠的应用至关重要。不断学习新威胁,是维护应用安全的永恒课题。
53 2
|
3月前
|
JSON 安全 数据安全/隐私保护
Python 安全性大揭秘:OAuth 与 JWT,不只是认证,更是信任的传递
【8月更文挑战第6天】在数字化时代,Python 通过 OAuth 和 JWT 筑牢应用安全防线。OAuth 是一种授权框架,允许第三方应用在用户授权下安全访问资源;JWT 则是一种自包含的声明传输格式,确保通信安全。两者结合使用,能有效进行身份验证及授权管理。然而,密钥管理和 JWT 有效期设置等仍是挑战,需谨慎处理以保障整体安全性。正确配置这些工具和技术,可为用户提供既安全又便捷的服务体验。
44 7
|
3月前
|
JSON 安全 数据安全/隐私保护
Python安全新篇章:OAuth与JWT携手,开启认证与授权的新时代
【8月更文挑战第6天】随着互联网应用的发展,安全认证与授权变得至关重要。本文介绍OAuth与JWT两种关键技术,并展示如何结合它们构建安全系统。OAuth允许用户授权第三方应用访问特定信息,无需分享登录凭证。JWT是一种自包含的信息传输格式,用于安全地传递信息。通过OAuth认证用户并获取JWT,可以验证用户身份并保护数据安全,为用户提供可靠的身份验证体验。
48 6
|
4月前
|
存储 JSON 安全
OAuth2与JWT在API安全中的角色:技术深度解析
【7月更文挑战第20天】OAuth2和JWT作为两种重要的安全协议,在API安全中发挥着不可或缺的作用。OAuth2通过提供灵活的授权框架,实现了对资源的细粒度访问控制;而JWT则通过其紧凑性和自包含性,确保了身份验证和信息传输的安全性。在实际应用中,将OAuth2和JWT结合使用,可以构建出既强大又安全的API服务,为用户提供更加安全、可靠和便捷的数字体验。

热门文章

最新文章