微服务安全:OAuth 2.0 与 JWT 实战指南
1. OAuth 2.0 授权服务器搭建
授权服务器的主要职责是向客户端发放令牌,并响应下游微服务的验证请求,同时它也充当安全令牌服务(STS)。目前有许多开源的 OAuth 2.0 授权服务器,如 WSO2 Identity Server、Keycloak、Gluu 等。在生产环境中可以选择使用这些开源服务器,但为了方便开发者测试,这里使用 Spring Boot 搭建一个简单的 OAuth 2.0 授权服务器。
1.1 添加 Maven 依赖
在
ch12/sample01/pom.xml
文件中添加以下依赖,这些依赖引入了新的注解,可将 Spring Boot 应用转换为 OAuth 2.0 授权服务器。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
1.2 配置授权服务器类
在
sample01/src/main/java/com/apress/ch12/sample01/TokenServiceApp.java
类中添加
@EnableAuthorizationServer
注解,将项目转换为 OAuth 2.0 授权服务器。同时添加
@EnableResourceServer
注解,使其作为资源服务器来验证访问令牌并返回用户信息。
// TokenServiceApp.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
@SpringBootApplication
@EnableAuthorizationServer
@EnableResourceServer
public class TokenServiceApp {
public static void main(String[] args) {
SpringApplication.run(TokenServiceApp.class, args);
}
}
1.3 注册客户端
在
sample01/src/main/java/com/apress/ch12/sample01/config/AuthorizationServerConfig.java
文件中注册客户端,设置客户端 ID、客户端密钥、可用范围、授权类型和访问令牌的有效期。
// AuthorizationServerConfig.java
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;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("10101010")
.secret("11110000")
.scopes("foo", "bar")
.authorizedGrantTypes("client_credentials", "password", "refresh_token")
.accessTokenValiditySeconds((int) TimeUnit.MINUTES.toSeconds(1));
}
}
1.4 配置用户存储
为了支持密码授权类型,授权服务器需要连接到用户存储。这里使用内存用户存储,在
sample01/src/main/java/com/apress/ch12/sample01/config/WebSecurityConguration.java
文件中添加用户。
// WebSecurityConguration.java
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class WebSecurityConguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("peter")
.password("peter123")
.roles("USER");
}
}
1.5 关联用户存储与授权流程
在
sample01/src/main/java/com/apress/ch12/sample01/config/AuthorizationServerConfig.java
文件中关联内存用户存储与 OAuth 2.0 授权流程。
// AuthorizationServerConfig.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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 java.util.concurrent.TimeUnit;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("10101010")
.secret("11110000")
.scopes("foo", "bar")
.authorizedGrantTypes("client_credentials", "password", "refresh_token")
.accessTokenValiditySeconds((int) TimeUnit.MINUTES.toSeconds(1));
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
}
}
1.6 启动授权服务器
在
ch12/sample01/
目录下执行以下命令启动授权服务器:
mvn spring-boot:run
1.7 获取访问令牌
-
客户端凭证授权类型
:使用以下命令获取访问令牌,需将
$CLIENTID和$CLIENTSECRET替换为实际值。
curl -v -X POST --basic -u $CLIENTID:$CLIENTSECRET -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" -k -d "grant_type=client_credentials&scope=foo" https://localhost:8443/oauth/token
-
密码授权类型
:使用以下命令获取访问令牌,需将
$CLIENTID、$CLIENTSECRET、$USERNAME和$PASSWORD替换为实际值。
curl -v -X POST --basic -u $CLIENTID:$CLIENTSECRET -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" -k -d "grant_type=password&username=$USERNAME&password=$PASSWORD&scope=foo" https://localhost:8443/oauth/token
1.8 验证访问令牌
使用以下命令验证访问令牌,需将
$TOKEN
替换为实际的访问令牌。
curl -k -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" https://localhost:8443/user
2. 使用 OAuth 2.0 保护微服务
在 OAuth 术语中,受保护的微服务被称为资源服务器。这里以订单处理微服务为例,展示如何使用 OAuth 2.0 保护 Spring Boot 微服务。
2.1 添加注解和配置文件
在
sample02/src/main/java/com/apress/ch12/sample02/OrderProcessingApp.java
类中添加
@EnableResourceServer
注解,并在
sample02/src/main/resources/application.properties
文件中配置授权服务器的用户信息端点。
# application.properties
server.port=9443
server.ssl.key-store: keystore.jks
server.ssl.key-store-password: springboot
server.ssl.keyAlias: spring
security.oauth2.resource.user-info-uri=https://localhost:8443/user
#security.oauth2.resource.jwt.keyUri: https://localhost:8443/oauth/token_key
2.2 处理证书信任问题
由于订单处理微服务通过 HTTPS 调用授权服务器的用户信息端点,而使用的是自签名证书,会导致信任验证错误。可以通过以下步骤解决:
1. 导出授权服务器的公共证书:
keytool -export -keystore keystore.jks -alias spring -file cert.crt
- 创建新的信任存储:
keytool -import -file cert.crt -alias authserver -keystore trust-store.jks
- 在代码中设置信任存储的位置和密码:
// OrderProcessingApp.java
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import java.io.File;
public class OrderProcessingApp {
static {
String path = System.getProperty("user.dir");
System.setProperty("javax.net.ssl.trustStore", path + File.separator + "trust-store.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "springboot");
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
}
// 其他代码
}
2.3 启动订单处理微服务
在
ch12/sample02/
目录下执行以下命令启动订单处理微服务:
mvn spring-boot:run
2.4 测试服务
- 无有效令牌调用 :
curl -k https://localhost:9443/order/11
- 有有效令牌调用 :
curl -k -H "Authorization: Bearer $TOKEN" https://localhost:9443/order/11
3. 微服务安全流程
graph LR
A[客户端] -->|请求令牌| B(授权服务器)
B -->|发放令牌| A
A -->|携带令牌请求资源| C(资源服务器)
C -->|验证令牌| B
B -->|返回验证结果| C
C -->|根据结果响应请求| A
4. 访问令牌获取与验证对比
| 操作类型 | 命令示例 | 说明 |
|---|---|---|
| 客户端凭证授权获取令牌 |
curl -v -X POST --basic -u $CLIENTID:$CLIENTSECRET -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" -k -d "grant_type=client_credentials&scope=foo" https://localhost:8443/oauth/token
| 适用于无用户交互场景,使用客户端 ID 和密钥获取令牌 |
| 密码授权获取令牌 |
curl -v -X POST --basic -u $CLIENTID:$CLIENTSECRET -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" -k -d "grant_type=password&username=$USERNAME&password=$PASSWORD&scope=foo" https://localhost:8443/oauth/token
| 需要用户提供用户名和密码获取令牌 |
| 验证令牌 |
curl -k -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" https://localhost:8443/user
| 验证令牌的有效性并返回相关元数据 |
微服务安全:OAuth 2.0 与 JWT 实战指南
5. 使用自包含访问令牌(JWT)保护微服务
在前面的内容中,我们已经了解了如何使用 OAuth 2.0 来保护微服务。接下来,我们将探讨如何使用自包含访问令牌(JWT)来进一步增强微服务的安全性。
5.1 设置授权服务器以颁发 JWT
为了让授权服务器能够颁发 JWT,我们需要完成以下几个步骤:
-
创建密钥对和密钥库
:使用
keytool命令创建一个新的密钥库和密钥对,用于签署 JWT。
keytool -genkey -alias jwtkey -keyalg RSA -keysize 2048 -dname "CN=localhost" -keypass springboot -keystore jwt.jks -storepass springboot
将生成的
jwt.jks
密钥库复制到
sample01/src/main/resources/
目录下。
-
配置应用属性
:在
sample01/src/main/resources/application.properties文件中设置相关属性,以启用 JWT 颁发功能。
spring.security.oauth.jwt: true
spring.security.oauth.jwt.keystore.password: springboot
spring.security.oauth.jwt.keystore.alias: jwtkey
spring.security.oauth.jwt.keystore.name: jwt.jks
-
添加 Maven 依赖
:在
pom.xml文件中添加spring-security-jwt依赖,用于构建 JWT。
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
-
配置授权服务器代码
:在
sample01/src/main/java/com/apress/ch12/sample01/config/AuthorizationServerConfig.java类中添加相关方法,以支持 JWT 的生成和存储。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AuthenticationManager;
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;
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;
import java.io.File;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private Environment environment;
@Bean
protected JwtAccessTokenConverter jwtConeverter() {
String pwd = environment.getProperty("spring.security.oauth.jwt.keystore.password");
String alias = environment.getProperty("spring.security.oauth.jwt.keystore.alias");
String keystore = environment.getProperty("spring.security.oauth.jwt.keystore.name");
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
new File(keystore), pwd.toCharArray());
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyStoreKeyFactory.getKeyPair(alias));
return converter;
}
@Bean
public TokenStore tokenStore() {
String useJwt = environment.getProperty("spring.security.oauth.jwt");
if (useJwt != null && "true".equalsIgnoreCase(useJwt.trim())) {
return new JwtTokenStore(jwtConeverter());
} else {
return new InMemoryTokenStore();
}
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("10101010")
.secret("11110000")
.scopes("foo", "bar")
.authorizedGrantTypes("client_credentials", "password", "refresh_token")
.accessTokenValiditySeconds((int) TimeUnit.MINUTES.toSeconds(1));
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
String useJwt = environment.getProperty("spring.security.oauth.jwt");
if (useJwt != null && "true".equalsIgnoreCase(useJwt.trim())) {
endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtConeverter())
.authenticationManager(authenticationManager);
} else {
endpoints.authenticationManager(authenticationManager);
}
}
}
-
启动授权服务器
:在
ch12/sample01/目录下执行以下命令启动授权服务器。
mvn spring-boot:run
-
获取 JWT 访问令牌
:使用客户端凭证授权类型获取 JWT 访问令牌,将
$CLIENTID和$CLIENTSECRET替换为实际值。
curl -v -X POST --basic -u $CLIENTID:$CLIENTSECRET -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" -k -d "grant_type=client_credentials&scope=foo" https://localhost:8443/oauth/token
返回的 JWT 是 Base64 URL 编码的,解码后的头部和有效负载示例如下:
{ "alg": "RS256", "typ": "JWT" }
{ "scope": [ "foo" ], "exp": 1524793284, "jti": "6e55840e-886c-46b2-bef7-1a14b813dd0a", "client_id": "10101010" }
5.2 使用 JWT 保护微服务
为了让订单处理微服务支持 JWT 验证,我们需要对配置文件进行修改。
在
sample02/src/main/resources/application.properties
文件中,注释掉
security.oauth2.resource.user-info-uri
属性,并取消注释
security.oauth2.resource.jwt.keyUri
属性。
#security.oauth2.resource.user-info-uri:https://localhost:8443/user
security.oauth2.resource.jwt.keyUri: https://localhost:8443/oauth/token_key
security.oauth2.resource.jwt.keyUri
属性指向授权服务器用于签署 JWT 的私钥对应的公钥端点。资源服务器(即订单处理微服务)将使用该公钥来验证请求中包含的 JWT 的签名。
6. JWT 保护微服务流程
graph LR
A[客户端] -->|请求 JWT 令牌| B(授权服务器)
B -->|颁发 JWT 令牌| A
A -->|携带 JWT 令牌请求资源| C(资源服务器)
C -->|使用公钥验证 JWT 签名| B
B -->|返回验证结果| C
C -->|根据结果响应请求| A
7. 不同授权方式总结
| 授权方式 | 适用场景 | 特点 |
|---|---|---|
| 客户端凭证授权 | 无用户交互,客户端以自身身份请求资源 | 无需用户参与,使用客户端 ID 和密钥获取令牌,无刷新令牌 |
| 密码授权 | 用户直接参与,提供用户名和密码 | 需要用户提供敏感信息,有刷新令牌 |
| JWT 授权 | 增强安全性,减少对授权服务器的依赖 | 自包含令牌,包含信息多,验证依赖公钥 |
8. 总结
通过本文,我们详细介绍了如何使用 OAuth 2.0 和 JWT 来保护微服务。从搭建 OAuth 2.0 授权服务器,到使用 OAuth 2.0 保护微服务,再到引入 JWT 进一步增强安全性,我们逐步深入地探讨了微服务安全的各个方面。
在实际应用中,我们可以根据具体的业务需求和安全要求选择合适的授权方式。客户端凭证授权适用于无用户交互的场景,密码授权适用于需要用户直接参与的场景,而 JWT 授权则可以在保证安全性的同时,减少对授权服务器的依赖。
希望本文能够帮助你更好地理解和应用 OAuth 2.0 和 JWT 来保护微服务,提升系统的安全性和可靠性。
超级会员免费看
870

被折叠的 条评论
为什么被折叠?



