gRPC微服务搭建(学习阶段2:权限校验)

在gRPC的权限校验中,写一个类,然后继承grpc.ServerInterceptor即可使用权限校验,每隔函数在调用前,都会调用这个函数,这个函数可用于权限校验或是一些数据准备等操作。

文章目录

权限校验示例程序

服务端代码

py 复制代码
import json
from concurrent import futures
import grpc


# 业务函数:收到客户端请求后,返回问候消息。
def say_hello(request, _context):
    return {"message": f"Hello, {request['name']}!"}


# 拦截器是 gRPC 的固定扩展写法,可以在请求到达业务函数前做日志、鉴权等处理。
# 这里为了保持简单,只打印请求方法,不做真正的权限校验。
class LogInterceptor(grpc.ServerInterceptor):
    def intercept_service(self, next_step, handler_call_details):
        print(f"收到请求:{handler_call_details.method}")
        return next_step(handler_call_details)


def server(env="dev", address="[::]:50051", max_workers=2):
    """启动 gRPC 服务。

    env:运行环境名称,当前只用于打印提示。
    address:监听地址,格式通常是"主机:端口"。
    max_workers:线程池中的最大工作线程数。
    """
    print(f"当前运行环境:{env}")

    # 这是 gRPC 创建服务端的固定写法。
    grpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=max_workers), interceptors=[LogInterceptor()])

    # 下面是本示例的 JSON 请求处理器。
    # 标准 gRPC 项目通常会用 .proto 文件自动生成这部分代码。
    handler = grpc.unary_unary_rpc_method_handler(
        say_hello,
        request_deserializer=lambda data: json.loads(data.decode("utf-8")),
        response_serializer=lambda response: json.dumps(response).encode("utf-8"),
    )

    # 注册服务和方法,这是手动注册 RPC 方法的写法。
    grpc_server.add_generic_rpc_handlers((
        grpc.method_handlers_generic_handler("hello.Greeter", {"SayHello": handler}),))

    # 添加监听端口、启动服务、持续等待,是 gRPC 服务启动的固定流程。
    grpc_server.add_insecure_port(address)
    grpc_server.start()
    print(f"gRPC 服务已启动,监听地址:{address}")
    grpc_server.wait_for_termination()


# 程序从这里开始执行,调用 server() 启动服务。
if __name__ == "__main__":
    server()

客户端代码

py 复制代码
import json
import grpc

# 连接地址必须和 server.py 中的端口一致。
with grpc.insecure_channel("127.0.0.1:50051") as channel:
    # 指定要调用的服务方法:/服务名/方法名。
    say_hello = channel.unary_unary(
        "/hello.Greeter/SayHello",
        # 发送请求前:Python 字典转换为 JSON 字节。
        request_serializer=lambda data: json.dumps(data).encode("utf-8"),
        # 收到响应后:JSON 字节转换为 Python 字典。
        response_deserializer=lambda data: json.loads(data.decode("utf-8")),
    )

    # 发送名称,并获取服务端返回的问候消息。
    response = say_hello({"name": "World"})
    print(response["message"])

带Login的权限管理

使用带login的权限管理,核心在于AuthInterceptor这个类中:

  1. 当method == "/auth.Auth/Login"时,直接执行校验
  2. 当执行其他的method的时候,直接替换调用check_token这个函数校验token,如果可以,就通过handler.unary_unary(request, context)继续执行,如果不行就通过context.abort(grpc.StatusCode.UNAUTHENTICATED, "请先登录")直接返回,不进行后续的执行

服务端代码

py 复制代码
import json
from concurrent import futures
import grpc

# 服务端保存登录成功后的 Token。
TOKEN = None


# 登录接口不需要 Token,登录成功后生成并保存 Token。
def login(request, _context):
    global TOKEN
    if request["username"] != "admin" or request["password"] != "123456":
        return {"success": False, "message": "用户名或密码错误"}
    TOKEN = "token-123"
    return {"success": True, "token": TOKEN, "message": "登录成功"}


# 只有通过 Token 校验后,才会执行这个业务函数。
def say_hello(request, _context):
    return {"message": f"Hello, {request['name']}!"}


# 将普通 Python 函数注册成 gRPC 方法,并处理 JSON 转换。
def json_handler(function):
    return grpc.unary_unary_rpc_method_handler(
        function,
        request_deserializer=lambda data: json.loads(data.decode("utf-8")),
        response_serializer=lambda data: json.dumps(data).encode("utf-8"),
    )


# 在业务函数执行前检查 Token。
class AuthInterceptor(grpc.ServerInterceptor):
    def intercept_service(self, continuation, handler_call_details):
        method = handler_call_details.method
        handler = continuation(handler_call_details)
        if handler is None:
            return None
        # 登录接口不能检查 Token,否则客户端无法登录。
        if method == "/auth.Auth/Login":
            return handler

        # 其他接口都必须携带正确的 Token。
        def check_token(request, context):
            if request.get("token") != TOKEN:
                context.abort(grpc.StatusCode.UNAUTHENTICATED, "请先登录")
            return handler.unary_unary(request, context)  # Token 正确,继续执行原来的业务函数

        # 用权限检查函数替换原来的业务处理函数。
        return handler._replace(unary_unary=check_token)


def server(address="[::]:50051", max_workers=2):
    """启动 gRPC 服务。"""
    # 创建服务,并安装 AuthInterceptor 权限拦截器
    grpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=max_workers), interceptors=[AuthInterceptor()])
    # 注册登录接口
    grpc_server.add_generic_rpc_handlers(
        (
            grpc.method_handlers_generic_handler(
                "auth.Auth",
                {"Login": json_handler(login)},
            ),
            # 注册需要登录的问候接口。
            grpc.method_handlers_generic_handler(
                "hello.Greeter",
                {"SayHello": json_handler(say_hello)},
            ),
        )
    )
    grpc_server.add_insecure_port(address)
    grpc_server.start()
    print(f"gRPC 服务已启动,监听地址:{address}")
    grpc_server.wait_for_termination()


if __name__ == "__main__":
    server()

客户端代码

py 复制代码
import json
import grpc

# 连接地址必须和 server.py 中的端口一致。
with grpc.insecure_channel("127.0.0.1:50051") as channel:
    # 第一步:调用登录接口,获取服务端保存的 Token。
    login = channel.unary_unary(
        "/auth.Auth/Login",
        request_serializer=lambda data: json.dumps(data).encode("utf-8"),
        response_deserializer=lambda data: json.loads(data.decode("utf-8")),
    )
    login_result = login({"username": "admin", "password": "123456"})
    print(login_result["message"])

    # 第二步:调用需要登录的 SayHello 接口。
    say_hello = channel.unary_unary(
        "/hello.Greeter/SayHello",
        request_serializer=lambda data: json.dumps(data).encode("utf-8"),
        response_deserializer=lambda data: json.loads(data.decode("utf-8")),
    )

    # 登录成功后,把 Token 和名称一起发送给服务端。
    response = say_hello({"name": "World", "token": login_result["token"]})
    print(response["message"])

    # 第三步:不发送 Token,模拟未登录调用。
    try:
        say_hello({"name": "World"})
    except grpc.RpcError as error:
        print(f"未登录:{error.details()}")
相关推荐
wdyan2973 天前
带着小白学习-微服务-5 实战:配置中心-数据库连接
数据库·学习·微服务
胡写代码4 天前
把数据库连接搬进配置中心,服务起不来:spring.config.import 的 4 个必填项
spring cloud·微服务
利威尔xu6 天前
Go IM 源码实战(14):本地缓存快 3000 倍,失效却难 10 倍
后端·缓存·微服务·面试·golang
利威尔xu7 天前
Go IM 源码实战(13):30 个会话只有 2 个红点,未读数错在三条路径
redis·后端·微服务·面试·golang
骑着蜗牛撵大象3277 天前
Spring Boot 4.1 + Spring AI 2.0:RAG 供文、图谱供关系、MCP 供工具的 Java Agent 平台
java·spring boot·spring·微服务·知识图谱·rag·mcp
code_slave(码畜)7 天前
微服务架构落地:慢 SQL 治理与连接池调优 —— 守住数据库的最后一道防线
数据库·spring boot·sql·spring cloud·微服务·架构
微三云 - 廖会灵 (私域系统开发)9 天前
企业级 SaaS 系统架构设计:单体、微服务还是模块化单体?技术选型实战
微服务·架构·系统架构
shuair9 天前
Spring Cloud 微服务脚手架:Simple Cloud 系统架构设计
spring cloud·微服务·系统架构
CodeGL9 天前
微服务框架——go-zero
微服务·架构·golang
草莓熊Lotso9 天前
【C++ 基于微服务的即时通讯系统】网关基建:cpp-httplib 与 WebSocketpp 从入门到实战
linux·开发语言·数据库·c++·redis·微服务·云原生