Spring gRPC集成Spring Security OAuth2
你好呀,我的老朋友!我是老寇,跟我一起学习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认证服务
增加依赖
<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
@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);
}
}
}
OAuth2TestApp
@SpringBootApplication
public class OAuth2TestApp {
public static void main(String[] args) {
SpringApplication.run(OAuth2TestApp.class, args);
}
}
application.yml
server:
port: 9088
app:
oauth2:
client:
id: client-id
secret: client-secret
logging:
level:
org.springframework.security: info
测试
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)
引入依赖
<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>
具体代码
/**
*
* <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
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: http://127.0.0.1:9088/oauth2/jwks
方法拦截示例
@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
依赖
<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>
具体代码
@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
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
我是老寇,我们下次再见啦!

浙公网安备 33010602011771号