授权中心-Oauth2+JWT

0
字数 4.3k
阅读时间 8 分钟

Spring Security

Spring Security 的官网介绍对 Spring Security 描述如下:

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.
Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements.

Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于 Spring 的应用程序的实际标准。
Spring Security 是一个框架,致力于为 Java 应用程序提供身份验证和授权。与所有 Spring 项目一样,Spring Security 的真正强大之处在于可以轻松扩展以满足自定义要求。

简单来讲官方对 Spring Securuty 定位就是身份验证和授权框架。

Oauth2

Oauth2 的官网介绍对 Oauth2 描述如下:

OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 supersedes the work done on the original OAuth protocol created in 2006. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices.

OAuth 2.0 是用于授权的行业标准协议。OAuth 2.0 取代了在 2006 年创建的原始 OAuth 协议上所做的工作。OAuth2.0 专注于简化客户端开发人员,同时为 Web 应用程序,桌面应用程序,移动电话和客厅设备提供特定的授权流。

简单来讲 Oauth2 就是授权协议
我们这里用 Spring Security 和 Oauth2 协议完成权限框架的设计。

用户和客户端信息存在内存,token 存在 Redis

新建父项目 spring-cloud-oauth2-demo

pom 文件如下:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<packaging>pom</packaging>

<groupId>xyz.liuzhuoming</groupId>
<artifactId>spring-cloud-oauth2-demo</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<java.version>11</java.version>
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
</properties>

<modules>
<module>eureka</module>
<module>auth</module>
<module>client</module>
<module>gateway</module>
</modules>

<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>
</project>

其中 eureka 项目为注册中心,搭建方式不再赘述,自行创建,定义端口号为 8765。

创建网关 Gateway 并注册到 eureka

新建项目 gateway,pom 如下:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>xyz.liuzhuoming</groupId>
<artifactId>spring-cloud-oauth2-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<packaging>jar</packaging>

<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway</name>
<description>Demo project for Spring Cloud</description>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

application.yml 如下(配置参数前文以作讲解,不再赘述):

server:
port: 5432

spring:
application:
name: @pom.artifactId@
redis:
host: localhost
port: 6379
password:
timeout: 500
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*"
allowCredentials: true
routes:
- id: auth
uri: lb://AUTH
predicates:
- Path=/auth/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 3
key-resolver: "#{@hostnameKeyResolver}"
- id: client
uri: lb://CLIENT
predicates:
- Path=/client/**
filters:
- StripPrefix=1
- Authorization=true

eureka:
client:
service-url:
defaultZone: http://localhost:8765/eureka/

management:
endpoints:
web:
exposure:
include: httptrace,info,health

新建权限校验过滤器和过滤器工厂类:

package xyz.liuzhuoming.gateway.filter;

import java.util.Objects;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
* 请求权限局部过滤器
*
* @author liuzhuoming
*/
public class AuthorizationGatewayFilter implements GatewayFilter, Ordered {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
try {
String token = Objects
.requireNonNull(exchange.getRequest().getHeaders().get("Authorization")).get(0);
if (token == null) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
} catch (Exception e) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}

@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
package xyz.liuzhuoming.gateway.filter.factory;

import xyz.liuzhuoming.gateway.filter.AuthorizationGatewayFilter;
import java.util.Collections;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;

/**
* 请求权限局部过滤器工厂
*
* @author liuzhuoming
*/
public class AuthorizationGatewayFilterFactory extends
AbstractGatewayFilterFactory<AuthorizationGatewayFilterFactory.Config> {

public AuthorizationGatewayFilterFactory() {
super(Config.class);
}

@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("enabled");
}

@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
if (!config.isEnabled()) {
return chain.filter(exchange);
} else {
return new AuthorizationGatewayFilter().filter(exchange, chain);
}
};
}

public static class Config {

/**
* 是否开启鉴权header验证
*/
private boolean enabled;

public boolean isEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
}

网关配置类:

package xyz.liuzhuoming.gateway.config;

import xyz.liuzhuoming.gateway.filter.factory.AuthorizationGatewayFilterFactory;
import java.util.Objects;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;

/**
* spring cloud config
*
* @author liuzhuoming
*/
@Configuration
public class SpringCloudConfig {

@Bean
KeyResolver hostnameKeyResolver() {
return exchange -> Mono.just(
Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getHostName());
}

@Bean
AuthorizationGatewayFilterFactory authorizationGlobalFilterFactory() {
return new AuthorizationGatewayFilterFactory();
}
}

创建授权服务并注册到 eureka

新建项目 auth,pom 文件如下(其中 redis 为存储 token):

<?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>
<parent>
<groupId>xyz.liuzhuoming</groupId>
<artifactId>spring-cloud-oauth2-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>auth</name>
<description>Demo project for Spring Cloud</description>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

application.yml 为:

server:
port: 6543

spring:
application:
name: @pom.artifactId@
redis:
host: localhost
port: 6379
database: 0

eureka:
client:
service-url:
defaultZone: http://localhost:8765/eureka/

启动类添加@EnableResourceServer 和@EnableDiscoveryClient 注解,其中第一个注解是标明服务提供了资源服务,第二个注解就不解释了:

package xyz.liuzhuoming.auth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;

@SpringBootApplication
@EnableResourceServer
@EnableDiscoveryClient
public class AuthApplication {

public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}

}

然后新建 Bean 注册类 SpringCloudConfig:

package xyz.liuzhuoming.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
* spring cloud config
*
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@Configuration
public class SpringCloudConfig {

@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}

新建授权服务配置类 AuthorizationServerConfigurer:

package xyz.liuzhuoming.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
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.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

/**
* 授权服务配置
*
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

@Autowired
AuthenticationManager authenticationManager;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Autowired
PasswordEncoder passwordEncoder;

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
String secret = passwordEncoder.encode("123456");

//在内存模拟生成两个Oauth2客户端
clients.inMemory()
//客户端id和密钥
.withClient("client_1").secret(secret)
//客户端授权类型
.authorizedGrantTypes("password", "client_credentials", "refresh_token")
//授权范围
.scopes("all")
.and()
.withClient("client_2").secret(secret)
.authorizedGrantTypes("password", "client_credentials", "refresh_token")
.scopes("all");
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
//token存储方式
.tokenStore(tokenStore())
//密码授权模式的授权管理器
.authenticationManager(authenticationManager)
//允许的token请求类型
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}

@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
//允许客户端表单授权
.allowFormAuthenticationForClients();
}

@Bean
public TokenStore tokenStore() {
//定义token存储方式为redis存储
return new RedisTokenStore(redisConnectionFactory);
}
}

新建用来添加模拟用户的类 WebSecurityConfigurer:

package xyz.liuzhuoming.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
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.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.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

@Autowired
PasswordEncoder passwordEncoder;

@Bean
@Override
protected UserDetailsService userDetailsService() {
String secret = passwordEncoder.encode("123456");

//在内存模拟生成两个用户信息
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User
//用户名
.withUsername("user_1")
//密码
.password(secret)
//权限
.authorities("a:get")
.build());
manager.createUser(User
.withUsername("user_2")
.password(secret)
.authorities("b:get")
.build());
return manager;
}

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

创建 Oauth2 客户端服务并注册到 eureka

新建项目 client,pom 如下:

<?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>
<parent>
<groupId>xyz.liuzhuoming</groupId>
<artifactId>spring-cloud-oauth2-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>client</name>
<description>Demo project for Spring Cloud</description>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

application.yml 配置如下:

server:
port: 7654

spring:
application:
name: client

eureka:
client:
service-url:
defaultZone: http://localhost:8765/eureka/

security:
oauth2:
resource:
#从oauth2服务获取user信息接口
user-info-uri: http://localhost:5432/auth/user/current
client:
#oauth2客户端id
id: client_2
#oauth2客户端密钥
client-secret: 123456
#oauth2服务端获取token接口
access-token-uri: http://localhost:5432/auth/oauth/token
#oauth2客户端请求授权类型
grant-type: client_credentials,password
#oauth2客户端授权范围
scope: all

在启动类添加注册服务注解:

package xyz.liuzhuoming.client;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ClientApplication {

public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}

}

oauth2 客户端配置类:

package xyz.liuzhuoming.client.config;

import feign.RequestInterceptor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.security.oauth2.client.feign.OAuth2FeignRequestInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;

/**
* Oauth2客户端配置
*
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@EnableOAuth2Client
@EnableConfigurationProperties
@Configuration
public class OAuth2ClientConfigurer {

@Bean
@ConfigurationProperties(prefix = "security.oauth2.client")
public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
return new ClientCredentialsResourceDetails();
}

@Bean
public RequestInterceptor oauth2FeignRequestInterceptor() {
return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(),
clientCredentialsResourceDetails());
}

@Bean
public OAuth2RestTemplate clientCredentialsRestTemplate() {
return new OAuth2RestTemplate(clientCredentialsResourceDetails());
}
}

资源服务配置类:

package xyz.liuzhuoming.client.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;

/**
* 资源服务配置
*
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {


@Override
public void configure(HttpSecurity http) throws Exception {
http
//配置访问控制
.authorizeRequests()
//不需要认证
.antMatchers("/actuator/**").permitAll()
//必须认证后才可以访问
.antMatchers("/a/**", "/b/**").authenticated();
}
}

测试资源接口:

package xyz.liuzhuoming.client.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* 测试
*
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@RestController
@Slf4j
public class TestController {

@GetMapping("/a")
@PreAuthorize("hasAuthority('a:get')")
public String a() {
return "a";
}

@GetMapping("/b")
@PreAuthorize("hasAuthority('b:get')")
public String b() {
return "b ";
}
}

然后按照顺序启动 eureka,gateway,auth,client,并用 Postman 请求http://localhost:5432/auth/oauth/token?username=user_2&password=123456&grant_type=password&scope=all,并在 Postman 的 Authorization 参数选择 Basic Auth 参数,并填写 oauth2 客户端 id 和密钥:
01.jpg
请求之后返回结果:

{
"access_token": "ae9baceb-c27a-4f9a-bf02-f13aa7c18f5f",
"token_type": "bearer",
"refresh_token": "994c5f1b-2732-452a-8f15-b92b62783deb",
"expires_in": 41220,
"scope": "all"
}

获取到 access_token 之后用 Postman 请求http://localhost:5432/client/b,并在 Postman 的 Authorization 参数选择 Bearer Token 参数,并填写刚才获得的 access_token:
02.jpg
获得结果:

b

之后用 Postman 请求http://localhost:5432/client/a,并在 Postman 的 Authorization 参数选择 Bearer Token 参数,并填写刚才获得的 access_token,得到结果:

{
"error": "access_denied",
"error_description": "不允许访问"
}

可以看到权限配置已经生效了,只有有对应 authorities 的用户才可以访问对应 authorities 的资源。

用户和客户端信息和 token 存在数据库

前面是把用户和客户端信息放在内存的,实际项目当然不会这样做,token 存在 redis 是可以的就不做修改了,把用户和客户端信息存在数据库。
eureka 和 gateway 项目不需要修改。
首先在 mysql 数据库建表名为 oauth2,下载 sql 文件并导入(已经插入了部分用户信息和客户端信息)(**点击下载**)。
其中 user,user_role,role,role_authority,authority 五张表可以自行定制,其他表是 Spring Security 官方推荐的表结构,没必要就尽量不做修改。
然后修改 auth 项目加入 mysql 驱动和 mybatis-plus 依赖,pom 文件修改如下“

<?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>
<parent>
<groupId>xyz.liuzhuoming</groupId>
<artifactId>spring-cloud-oauth2-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>auth</name>
<description>Demo project for Spring Cloud</description>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.2</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

application.yml 修改如下:

server:
port: 6543

spring:
application:
name: @pom.artifactId@
redis:
host: localhost
port: 6379
database: 0
datasource:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/oauth2?useUnicode=true&characterEncoding=utf8&useSSL=false&noAccessToProcedureBodies=true&serverTimezone=Asia/Shanghai
username: root
password: root
main:
#允许覆盖已定义的bean
allow-bean-definition-overriding: true

eureka:
client:
service-url:
defaultZone: http://localhost:8765/eureka/

再修改授权配置类,主要是修改 tokenStore:

package xyz.liuzhuoming.auth.configurer;

import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

/**
* 授权服务配置
*
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@Configuration
@EnableAuthorizationServer
@Slf4j
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

@Autowired
AuthenticationManager authenticationManager;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Autowired
private DataSource dataSource;
@Autowired
private TokenStore tokenStore;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private ClientDetailsService clientDetailsService;

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
DefaultTokenServices tokenServices = new DefaultTokenServices();
//token存储方式
tokenServices.setTokenStore(tokenStore);
//是否允许token刷新
tokenServices.setSupportRefreshToken(true);
//客户端Service
tokenServices.setClientDetailsService(clientDetailsService);
//token有效时间(秒)
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.MINUTES.toSeconds(30));

endpoints
//token存储方式
.tokenStore(tokenStore())
//密码授权模式的授权管理器
.authenticationManager(authenticationManager)
//用户Service
.userDetailsService(userDetailsService)
//tokenService
.tokenServices(tokenServices);
}

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

@Bean
public TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
//修改token存储方式为jdbc
// return new JdbcTokenStore(dataSource);
}

@Bean
public ClientDetailsService clientDetailsService() {
//设置客户端信息存储方式为jdbc
return new JdbcClientDetailsService(dataSource);
}
}

WebSecurityConfigurer 类删除模拟用户方法:

package xyz.liuzhuoming.auth.config;

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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

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

添加用户实体类:

package xyz.liuzhuoming.auth.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Collection;
import java.util.List;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

/**
* 用户
*
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@Data
@TableName("user")
public class User implements UserDetails {

private static final long serialVersionUID = -8630757683959992293L;
@TableId(type = IdType.AUTO)
private Integer id;
@TableField
private String username;
@TableField
private String password;
@TableField(exist = false)
private List<Authority> 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;
}
}

添加权限实体类:

package xyz.liuzhuoming.auth.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.security.core.GrantedAuthority;

/**
* 权限
*
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@Data
@EqualsAndHashCode
public class Authority implements GrantedAuthority {

private static final long serialVersionUID = 1179377491735761716L;
private Long id;
private String name;
private String authority;

@Override
public String getAuthority() {
return authority;
}
}

新建用户 Mapper:

package xyz.liuzhuoming.auth.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import xyz.liuzhuoming.auth.entity.Authority;
import xyz.liuzhuoming.auth.entity.User;

/**
* 用户
*
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {

/**
* 根据用户名获取权限集合
*
* @param username 用户名
* @return 权限集合
*/
List<Authority> selectAuthoritiesByUsername(String username);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.liuzhuoming.auth.mapper.UserMapper">

<select id="selectAuthoritiesByUsername" parameterType="string"
resultType="xyz.liuzhuoming.auth.entity.Authority">
SELECT a.id,a.`name`,a.authority
FROM authority a
LEFT JOIN role_authority ra ON ra.authority_id=a.id
LEFT JOIN user_role ur ON ra.role_id=ur.role_id
LEFT JOIN `user` u ON ur.user_id=u.id
WHERE u.username=#{username}
</select>
</mapper>

修改用户 service 为:

package xyz.liuzhuoming.auth.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
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 xyz.liuzhuoming.auth.entity.Authority;
import xyz.liuzhuoming.auth.entity.User;
import xyz.liuzhuoming.auth.mapper.UserMapper;

/**
* 用户
*
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired
private UserMapper userMapper;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
userLambdaQueryWrapper.eq(User::getUsername, username);
//根据用户名查询用户查询
User user = userMapper.selectOne(userLambdaQueryWrapper);
//根据用户名查询权限集合
List<Authority> authorityList = userMapper.selectAuthoritiesByUsername(username);
user.setAuthorities(new ArrayList<>(new HashSet<>(authorityList)));
return user;
}
}

重启 auth 项目,进行测试,测试方法同上,请自行尝试,不再赘述。

修改 token 类型为 JWT,并在 gateway 进行简单的权限校验

JWT 官网对 JWT 描述如下:

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

JSON Web 令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对对 JWT 进行签名。

简单来讲就是个令牌标准

使用 keytool 进行 oauth2 授权服务和客户端的非对称加密

openSsl 可能 windows 会提示没有这个命令,所以可以在 linux 环境下生成 jks 密钥库文件和公钥。
jks 即 Java KeyStore(Java 密钥库)。
首先运行keytool -genkeypair -alias jwt -keyalg RSA -dname "CN=jwt,OU=j,O=wt,L=hz,S=zj,C=CH" -keypass 654321 -keystore jwt.jks -storepass 654321生成 jks 文件,其中alias参数意思是密钥库别名,keyalg加密方式,dname发行者信息(按顺序分别为姓名、组织机构名、组织名、城市名、省份名、国家名,可以删除cname参数及值来按顺序填入),keypass密钥密码,keystore密钥库文件名,storepass密钥库密码。会在当前文件夹生成 jwt.jks 文件(可能会提示转换行业标准之类的,可以转换也可以不转换,不影响接下来的操作),再然后运行keytool -list -rfc --keystore jwt.jks | openssl x509 -inform pem -pubkey从 jwt.jks 提取出密钥信息,类似:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtNIqpyseTONuqBVJfGyN
guo74rDyxMHV8TAsLwrQpr/3YrW5ilETc9UjtA5L6ls9UctlgYf0dFwtLfiCdCei
MP5GsBZYzKpUTAxcly7NkR96eDUUBRivh0+Qo3MRwTfCucCoxCkUJ67SvYkcwDQC
H5d0CdfE12aFVNrdC/oG7J0S9N4YQwPVyEzNQdlN3SNfLKqQ6MmmrCHdxZ+IlN6f
68zMMFCfcXIRC90UNpqYRS6lhrZhBc1apqtJiMmiQBdF0n1+nTrugUjA1+W0Sx9e
hPwWSYY0GzU+e+yItGkUjRPMkB68/sX0tVgU2Z+rCWQm6Xcdy2Mq/ZWDiHrwAyuT
5QIDAQAB
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
MIIDOzCCAiOgAwIBAgIEXWTtbTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJD
SDELMAkGA1UECBMCemoxCzAJBgNVBAcTAmh6MQswCQYDVQQKEwJ3dDEKMAgGA1UE
CxMBajEMMAoGA1UEAxMDand0MB4XDTE5MTAyMjA1NDY0NloXDTIwMDEyMDA1NDY0
NlowTjELMAkGA1UEBhMCQ0gxCzAJBgNVBAgTAnpqMQswCQYDVQQHEwJoejELMAkG
A1UEChMCd3QxCjAIBgNVB0sTAWoxDDAKBgNVBAMTA2p3dDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBALTSKqcrHkzjbqgVSXxsjYLqO+Kw8sTB1fEwLC8K
0Ka/92K1uYpRE3PVI7QOS+pbPVHLZYGH9HRcLS34gnQnojD+RrAWWMyqVEwMXJcq
zZEfeng1FAUYr4dPkKNzEcE3wrnAqMQpFCeu0r2JHMA0Ah+XdAnXxNdmhVTa3Qv6
BuydEvTeGEMD1chMzUHZTd0jXyyqkOjJpqwh3cWfiJTen+vMzDBQn3FyEQvdFDaa
mEUupYa2YQXNWqarSYjJokAXRdJ9fp067oFIwNfltEsfXoT8FkmGNBs1PnvsiLRp
FI0TzJAevP7F9LVYFNmfqwlkJul3HctjKv2Vg4h68AMrk+UCAwEAAaMhMB8wHQYD
VR0OBBYEFG9KighOWWVXffMRor4Js/Q5IpwRMA0GCSqGSIb3DQEBCwUAA4IBAQCT
OYyEV7XAOaWfjGwEWBlTRD7x/HhNM0QiXdOmZFZ1CHl3PwzgFOG0urihM91WqreZ
inbCKwOX/UnUjPc7bF4ighccZpHKv5GanmBXov0t0KuHjE4+6UWgVeZLjnNQZ8Hd
GiY+Z8mj5PTQpikLVXtJOyCxDt2GW3BvBfzxB6ZPr7JaD1He6aApbb0WogjZh0M4
dk31Xixy+wCuYSVrrrh4iscHEkufySkliKk3sseZMmUb1iyUtlTxIbJebpD1Yal9
Qm8TCXI4vuslh4bi6HVBNHrlWf6J7ndCHV/barQ6uWVJtO7QAAgqLrFc8JP6Yxiv
h19yWioGyQYXBkDDSSgM
-----END CERTIFICATE-----

我们只需要其中的 public key 部分,即:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtNIqpyseTONuqBVJfGyN
guo74rDyxMHV8TAsLwrQpr/3YrW5ilETc9UjtA5L6ls9UctlgYf0dFwtLfiCdCei
MP5GsBZYzKpUTAxcly7NkR96eDUUBRivh0+Qo3MRwTfCucCoxCkUJ67SvYkcwDQC
H5d0CdfE12aFVNrdC/oG7J0S9N4YQwPVyEzNQdlN3SNfLKqQ6MmmrCHdxZ+IlN6f
68zMMFCfcXIRC90UNpqYRS6lhrZhBc1apqtJiMmiQBdF0n1+nTrugUjA1+W0Sx9e
hPwWSYY0GzU+e+yItGkUjRPMkB68/sX0tVgU2Z+rCWQm6Xcdy2Mq/ZWDiHrwAyuT
5QIDAQAB
-----END PUBLIC KEY-----

放到新建的 public.txt 文件中。
然后把 jwt.jks 放到 auth 项目的 resources 目录下,public.txt 放到 client 项目的 resources 目录下。

修改 auth 项目的授权服务配置文件

package xyz.liuzhuoming.auth.configurer;

import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

/**
* 授权服务配置
*
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@Configuration
@EnableAuthorizationServer
@Slf4j
public class AuthorizationServerConfigurer extends AuthorizationServerConfigurerAdapter {

@Autowired
AuthenticationManager authenticationManager;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Autowired
private DataSource dataSource;
@Autowired
private TokenStore tokenStore;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService());
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
//token生成器设置为jwt生成器
.accessTokenConverter(jwtAccessTokenConverter)
.tokenServices(tokenServices());
}

@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
.allowFormAuthenticationForClients()
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}

@Bean
public ClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
}

然后新建 JWT 配置文件:

package xyz.liuzhuoming.auth.configurer;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
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 org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

/**
* jwt配置
*
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@Configuration
public class JwtConfigurer {

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
//配置密钥库资源路径及密钥库密码
KeyStoreKeyFactory keyStoreKeyFactory =
new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "654321".toCharArray());
//配置密钥别名
accessTokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("jwt"));
return accessTokenConverter;
}

@Bean
public TokenStore tokenStore() {
//配置tokenStore为jwt
return new JwtTokenStore(jwtAccessTokenConverter());
}
}

修改 client 项目的授权客户端配置文件

新增 JWT 配置文件:

package xyz.liuzhuoming.client.config;

import java.io.IOException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.util.FileCopyUtils;

/**
* jwt配置
*
* @author liuzhuoming
* @version 1.0-SNAPSHOT
*/
@Configuration
public class JwtConfigurer {

@Bean
protected JwtAccessTokenConverter accessTokenConverter() throws IOException {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//配置公钥资源路径
Resource resource = new ClassPathResource("public.txt");
String publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
converter.setVerifierKey(publicKey);
return converter;
}
}

重启 auth 和 client 项目,用 Postman 请求http://localhost:5432/auth/oauth/token?username=user_2&password=123456&grant_type=password&scope=all,并在 Postman 的 Authorization 参数选择 Basic Auth 参数,并填写 oauth2 客户端 id 和密钥,请求之后返回结果:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzE3Mjc1MTIsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiYTpwb3N0IiwiYjpnZXQiLCJhOmdldCJdLCJqdGkiOiI0ZGI5NDVlMy04YWUyLTRiNmMtYWRmNi0zODM0MmUzNDQ4YWUiLCJjbGllbnRfaWQiOiJjbGllbnRfMiIsInNjb3BlIjpbInNlcnZlciJdfQ.OdeHQuS1IS_QTlYolBPPthXM3Y__X4i3f9r6Vh9BjMyiG_h6H8_wKERCRJuuWOAkIOYtOdwNXcYzpfq324IiZJlxUEQcwc3N9hQ_6xvJCyl2cvtv7RczQIZ8i1XcB2aWWY4jfYtFBgdrmS8zbYxWNghIKjiJPbuCE4uWyG52huNpRAC7K8G0tjO9CyEx6y0Q5Yckonb4AH9sr9ZMflr7mcp0wNVIwzsbj6YwwLfBybZiEwEKWOhpJJM8cp3FRosFzUN0amzoMmohTd5GW--Tdo3yf-_Z3WbMo0pXSSMeQwry7bDpzblv6IfkuwjNSr8SHFckcoj2xxYdog54vgG6dg",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInNlcnZlciJdLCJhdGkiOiI0ZGI5NDVlMy04YWUyLTRiNmMtYWRmNi0zODM0MmUzNDQ4YWUiLCJleHAiOjE1NzQzMTc3MTIsImF1dGhvcml0aWVzIjpbImE6cG9zdCIsImI6Z2V0IiwiYTpnZXQiXSwianRpIjoiMDE2MGU3ODItYzUwYS00ZjhkLTgzY2YtM2EyNGE5YWJiNWQzIiwiY2xpZW50X2lkIjoiY2xpZW50XzIifQ.TVfQciMDlyDRU_aVU4ENyAbp9Xy5T5IlkSJaPJ8D1Bpaq4SCxoMsVehvPOLSsMm2Z03UDn7td0f9M2dfiHhT2QLhWcjZo7n2KBKNm3h0zpB-3Qyf-sfo_FB0B9tgfJ4wCLhcvZalPlFtRXJNPVrTpRRsZW-e-BYoHqIxuId_B_vPkzlB-313DoWSRa93Sn39tTnsKQaemBdkD7irZOSa9YE-O8pMNADxOD0ci38ya_xtiloVveXcVSiuWKbcblh-61JcYT-780xLa3KEqdPBTfTcj50nD9Is9RTQ2B8SCFQrmsTrjnm7QmXfe0f9SphGxKBW0M_9p6OaFRPFZ9eL4g",
"expires_in": 1799,
"scope": "server",
"jti": "4db945e3-8ae2-4b6c-adf6-38342e3448ae"
}

可以看出 access_token 已经是标准的 JWT 格式。
用 Postman 请求http://localhost:5432/client/b,并在 Postman 的 Authorization 参数选择 Bearer Token 参数,并填写刚才获得的 access_token,获得结果:

b

也就是说 JWT 作为 Oauth2 的 token 配置成功。
有兴趣的话可以在 gateway 添加简单的 JWT 有效性校验(有效期等),可以筛选掉部分非法请求,缺点就是因为 JWT 有效性自包含在 token 串内,所以无法主动注销 token,只能等待自动过期。


系列文章 #Spring Cloud

(1)前言

(2)注册中心-Eureka

(3)服务间调用-Feign(🔒)

(4)路由中心-Gateway

(5)配置中心-Config

(6)配置中心-Bus

(7)监控中心-Admin

(8)监控中心-Sleuth+Zipkin(🔒)

(9)监控中心-Elasticsearch+Zipkin(🔒)

(10)监控中心-HystrixDashboard+Turbine(🔒)

(11)授权中心-Oauth2+JWT

(12)注册中心/配置中心-Nacos

(13)动态路由-Gateway

(14)授权中心-Oauth2+JWT补全


注册中心/配置中心-Nacos 监控中心-HystrixDashboard+Turbine