你好呀,我的老朋友!我是老寇,跟我一起学习Spring gRPC集成Spring Security OAuth2
引入Spring Security组件,能够提供统一、可声明式的方法级安全,让每个方法都可以按需进行权限控制
基于Interceptor(拦截器)实现,需要提前将Token放入

Spring Authorization Server
Spring Authorization Server官方文档
OAuth 2.1 授权服务器功能为OAuth 2.1 授权框架中定义的授权服务器角色提供支持。
授权服务器功能提供了OAuth 2.1和OpenID Connect 1.0规范以及其他相关规范的实现。它为构建 OpenID Connect 1.0 身份提供程序和 OAuth 2.1 授权服务器产品提供了一个安全、轻量级且可定制的基础。
client_credentials
实现 client_credentials 认证模式的OAuth2认证服务
增加依赖
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security-oauth2-authorization-server</artifactId>
</dependency>
</dependencies>
具体代码
SecurityConfig
java
@Configuration
public class SecurityConfig {
@Bean
RegisteredClientRepository registeredClientRepository(
PasswordEncoder passwordEncoder,
@Value("${app.oauth2.client.id}") String clientId,
@Value("${app.oauth2.client.secret}") String clientSecret) {
RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId(clientId)
.clientSecret(passwordEncoder.encode(clientSecret))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope("read")
.tokenSettings(TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofHours(1))
.build())
.build();
return new InMemoryRegisteredClientRepository(client);
}
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, _) -> jwkSelector.select(jwkSet);
}
@Bean
JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
@Bean
@Order(1)
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
http.securityMatcher(endpointsMatcher);
http.with(authorizationServerConfigurer, Customizer.withDefaults());
http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());
return http.build();
}
private static RSAKey generateRsa() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyUse(KeyUse.SIGNATURE)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}
catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("RSA algorithm is not available", ex);
}
}
}
java
OAuth2TestApp
@SpringBootApplication
public class OAuth2TestApp {
public static void main(String[] args) {
SpringApplication.run(OAuth2TestApp.class, args);
}
}
application.yml
yaml
server:
port: 9088
app:
oauth2:
client:
id: client-id
secret: client-secret
logging:
level:
org.springframework.security: info
测试
bash
curl -u machine-client:machine-secret \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&scope=message.read" \
http://localhost:9088/oauth2/token
Spring gRPC Server集成Spring Security
Spring gRPC Server Security文档地址
采用全局拦截@GlobalServerInterceptor,Spring OAuth2 Resource Server拦截Token并请求Spring Authorization Server的JWK Set Endpoint(/oauth2/jwks)
引入依赖
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-grpc-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
</dependencies>
具体代码
java
/**
*
* <a href=
* "https://docs.springframework.org.cn/spring-security/reference/reactive/oauth2/resource-server/opaque-token.html">OAuth
* 2.0 资源服务器不透明令牌</a>.
* <p>
* <a href="https://docs.spring.io/spring-grpc/reference/server.html">Spring gRPC 安全</a>
* <p>
* <a href=
* "https://github.com/spring-projects/spring-grpc/tree/main/samples/grpc-oauth2">Spring
* gRPC 安全例子</a>
*
* @author laokou
*/
@Configuration
@EnableMethodSecurity
class GrpcOAuth2Config {
@Bean
@GlobalServerInterceptor
AuthenticationProcessInterceptor jwtAuthenticationProcessInterceptor(GrpcSecurity grpc) throws Exception {
return grpc.authorizeRequests(requests -> requests.allRequests().authenticated())
.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()))
.build();
}
}
appcation.yml
yaml
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: http://127.0.0.1:9088/oauth2/jwks
方法拦截示例
java
@Slf4j
@GrpcService
@RequiredArgsConstructor
public class xxxImpl extends xxxGrpc.xxxImplBase {
private final String resultMsg = MessageUtils.getMessage(StatusCode.OK);
@Override
@PreAuthorize("hasAuthority('SCOPE_read')")
public void generateId(GenerateIdRequest request, StreamObserver<xxxResponse> responseObserver) {
GenerateIdResponse response = GenerateIdResponse.newBuilder()
.setCode(StatusCode.OK)
.setMsg(resultMsg)
.setData(1)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
Spring gRPC Client集成Spring Security
Spring gRPC Client Security文档地址
采用全局拦截@GlobalClientInterceptor,从Spring Authorization Server获取Token并塞入Interceptor
依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-grpc-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
具体代码
java
@Configuration
final class GrpcClientConfig {
@Bean
OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository registrations,
OAuth2AuthorizedClientService service) {
OAuth2AuthorizedClientProvider provider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
AuthorizedClientServiceOAuth2AuthorizedClientManager manager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
registrations, service);
manager.setAuthorizedClientProvider(provider);
return manager;
}
@Bean
@GlobalClientInterceptor
ClientInterceptor clientInterceptor(OAuth2AuthorizedClientManager authorizedClientManager) {
return new BearerTokenAuthenticationInterceptor(() -> {
// 塞入token(default来源于yaml配置,请查看yaml配置)
OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest.withClientRegistrationId("default")
.principal("system")
.build();
OAuth2AuthorizedClient client = authorizedClientManager.authorize(request);
Assert.notNull(client, "authorized client is null");
return client.getAccessToken().getTokenValue();
});
}
}
application.yml
yaml
spring:
security:
oauth2:
client:
registration:
default:
client-id: client-id
client-secret: client-secret
client-name: OAuth2.1 M2M认证客户端
authorization-grant-type: client_credentials
client-authentication-method: client_secret_basic
scope:
- read
provider: local
provider:
local:
token-uri: http://127.0.0.1:9088/oauth2/token
我是老寇,我们下次再见啦!