Wildfly中gRPC请求Keycloak OAuth Token安全上下文填充与拦截器咨询
解决方案:实现全局gRPC服务端拦截器填充EJB安全上下文
是的,你需要实现全局gRPC服务端拦截器来解决这个问题。Wildfly对REST请求的安全上下文自动填充是基于Java EE标准的安全机制(比如JAX-RS容器认证),但gRPC请求不在Wildfly默认的安全处理范围内,必须手动提取认证信息并注入EJB安全上下文。
核心思路
- 从gRPC请求的元数据(Metadata)中提取认证令牌(通常是
Authorization: Bearer <token>格式) - 利用Keycloak的API解析令牌,验证有效性并获取用户声明
- 手动构建Keycloak安全上下文,设置到EJB的
SecurityContext中
示例代码
1. 全局gRPC服务端拦截器实现
import io.grpc.*; import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakSecurityContext; import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.jbosswildfly.WildFlyKeycloakAdapterContext; import org.keycloak.representations.AccessToken; import javax.ejb.EJBContext; import javax.naming.InitialContext; import javax.naming.NamingException; import java.security.Principal; public class GrpcSecurityInterceptor implements ServerInterceptor { private static final Metadata.Key<String> AUTHORIZATION_KEY = Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER); @Override public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall( ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { // 提取认证令牌 String authHeader = headers.get(AUTHORIZATION_KEY); if (authHeader == null || !authHeader.startsWith("Bearer ")) { // 无有效令牌,返回未授权错误 call.close(Status.UNAUTHENTICATED.withDescription("Missing or invalid Authorization header"), new Metadata()); return new ServerCall.Listener<>() {}; } String token = authHeader.substring(7); try { // 解析并验证Keycloak令牌(利用Wildfly集成的Keycloak适配器) WildFlyKeycloakAdapterContext adapterContext = InitialContext.doLookup("java:jboss/KeycloakAdapterContext"); RefreshableKeycloakSecurityContext securityContext = adapterContext.getDeploymentContext().createSecurityContext(token); securityContext.getToken(); // 触发令牌验证 // 构建Keycloak Principal Principal principal = new KeycloakPrincipal<>(securityContext); // 获取EJB上下文并设置安全上下文 EJBContext ejbContext = InitialContext.doLookup("java:comp/EJBContext"); ejbContext.setSecurityContext(new EJBSecurityContextWrapper(principal, securityContext)); // 继续处理请求 return next.startCall(call, headers); } catch (NamingException e) { call.close(Status.INTERNAL.withDescription("Failed to access server context"), new Metadata()); return new ServerCall.Listener<>() {}; } catch (Exception e) { call.close(Status.UNAUTHENTICATED.withDescription("Invalid token"), new Metadata()); return new ServerCall.Listener<>() {}; } } // 自定义EJB安全上下文包装类,适配KeycloakSecurityContext private static class EJBSecurityContextWrapper implements javax.ejb.EJBSecurityContext { private final Principal principal; private final KeycloakSecurityContext keycloakContext; public EJBSecurityContextWrapper(Principal principal, KeycloakSecurityContext keycloakContext) { this.principal = principal; this.keycloakContext = keycloakContext; } @Override public Principal getCallerPrincipal() { return principal; } @Override public boolean isCallerInRole(String roleName) { AccessToken token = keycloakContext.getToken(); return token.getRealmAccess().getRoles().contains(roleName); } // 实现其他必要的方法(根据EJBSecurityContext接口要求) @Override public <T> T getContextData(Class<T> contextDataClass) { return null; } } }
2. 注册全局拦截器到Wildfly
要让拦截器全局生效,有两种方式:
- 通过部署配置文件:在
jboss-deployment-structure.xml中添加拦截器配置
<jboss-deployment-structure> <deployment> <extensions> <extension module="org.wildfly.extension.grpc"/> </extensions> <properties> <property name="grpc.server.interceptors" value="com.yourpackage.GrpcSecurityInterceptor"/> </properties> </deployment> </jboss-deployment-structure>
- 手动注册:在gRPC服务启动时直接绑定拦截器
Server server = ServerBuilder.forPort(50051) .addService(ServerInterceptors.intercept(new YourGrpcServiceImpl(), new GrpcSecurityInterceptor())) .build();
关键注意事项
- 确保gRPC客户端在请求元数据中正确传递
Authorization头 - 拦截器中要处理令牌验证失败、上下文查找失败等异常,返回对应的gRPC错误状态
- 依赖的Keycloak适配器版本要与Wildfly版本匹配,避免兼容性问题
内容的提问来源于stack exchange,提问作者mattobob
相关产品推荐
相关产品推荐

