You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Wildfly中gRPC请求Keycloak OAuth Token安全上下文填充与拦截器咨询

解决方案:实现全局gRPC服务端拦截器填充EJB安全上下文

是的,你需要实现全局gRPC服务端拦截器来解决这个问题。Wildfly对REST请求的安全上下文自动填充是基于Java EE标准的安全机制(比如JAX-RS容器认证),但gRPC请求不在Wildfly默认的安全处理范围内,必须手动提取认证信息并注入EJB安全上下文。

核心思路

  1. 从gRPC请求的元数据(Metadata)中提取认证令牌(通常是Authorization: Bearer <token>格式)
  2. 利用Keycloak的API解析令牌,验证有效性并获取用户声明
  3. 手动构建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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 15:22:24