GRPC(8)- JWT身份认证

最后更新:2020-06-08

gRPC内置了以下身份验证机制:

  • SSL / TLS:gRPC具有SSL / TLS集成,并促进使用SSL / TLS对服务器进行身份验证,并加密客户端和服务器之间交换的所有数据。可选机制可供客户端提供相互身份验证的证书。
  • 使用Google进行基于令牌的身份验证:gRPC提供了一种通用机制(如下所述),用于将基于元数据的凭据附加到请求和响应。
  • gRPC提供了一个基于Credentials对象统一概念的简单身份验证API,可以在创建整个gRPC频道或单个调用时使用。

所有的gRPC stubs都可以通过withCallCredentials(CallCredentials credentials)方法返回一个新的stub,用于简单身份验证。

继承CallCredentials实现自己的身份认证令牌

public class BearerToken extends CallCredentials {

    private String value;

    public BearerToken(String value) {
        this.value = value;
    }

    @Override
    public void applyRequestMetadata(RequestInfo requestInfo, Executor executor, MetadataApplier metadataApplier) {
        executor.execute(() -> {
            try {
                Metadata headers = new Metadata();
                headers.put(Constants.AUTHORIZATION_METADATA_KEY, String.format("%s %s", Constants.BEARER_TYPE, value));
                metadataApplier.apply(headers);
            } catch (Throwable e) {
                metadataApplier.fail(Status.UNAUTHENTICATED.withCause(e));
            }
        });
    }

    @Override
    public void thisUsesUnstableApi() {
        // noop
    }
}
  • applyRequestMetadata方法用将身份认证信息通过MetadataApplier传递给请求的metadata。

通过拦截器进行身份认证

public class Constants {
    public static final String JWT_SIGNING_KEY = "L8hHXsaQOUjk5rg7XPGv4eL36anlCrkMz8CJ0i/8E/0=";
    public static final String BEARER_TYPE = "Bearer";

    public static final Metadata.Key<String> AUTHORIZATION_METADATA_KEY = Metadata.Key.of("Authorization", ASCII_STRING_MARSHALLER);
    public static final Context.Key<String> CLIENT_ID_CONTEXT_KEY = Context.key("clientId");

    private Constants() {
        throw new AssertionError();
    }
}
public class AuthorizationServerInterceptor implements ServerInterceptor {

    private JwtParser parser = Jwts.parser().setSigningKey(Constants.JWT_SIGNING_KEY);

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
        String value = metadata.get(Constants.AUTHORIZATION_METADATA_KEY);

        Status status;
        if (value == null) {
            status = Status.UNAUTHENTICATED.withDescription("Authorization token is missing");
        } else if (!value.startsWith(Constants.BEARER_TYPE)) {
            status = Status.UNAUTHENTICATED.withDescription("Unknown authorization type");
        } else {
            try {
                String token = value.substring(Constants.BEARER_TYPE.length()).trim();
                Jws<Claims> claims = parser.parseClaimsJws(token);
                Context ctx = Context.current().withValue(Constants.CLIENT_ID_CONTEXT_KEY, claims.getBody().getSubject());
                return Contexts.interceptCall(ctx, serverCall, metadata, serverCallHandler);
            } catch (Exception e) {
                status = Status.UNAUTHENTICATED.withDescription(e.getMessage()).withCause(e);
            }
        }

        serverCall.close(status, metadata);
        return new ServerCall.Listener<ReqT>() {
            // noop
        };
    }
}

客户端发起调用是通过withCallCredentials(token)方法带上身份认证信息,可以为单个调用设置,也可以全局设置.

Edgar

Edgar
一个略懂Java的小菜比