درستی‌سنجی کردن کدهای App Check از باطن سفارشی

می‌توانید از App Check برای محافظت از منابع باطن سفارشی غیرGoogle برای برنامه‌تان، مثل باطن خودمیزبان خودتان، استفاده کنید. برای انجام این کار، باید هر دو مورد زیر را انجام دهید:

  • کلاینت برنامه‌تان را اصلاح کنید تا یک کد App Check را همراه با هر درخواست به باپس‌تان ارسال کند، همان‌طور که در صفحه‌های مربوط به iOS+، Android، وب، Flutter، Unity، یا C++ شرح داده شده است.
  • همان‌طور که در این صفحه شرح داده شده است، باطن خود را به‌گونه‌ای اصلاح کنید که برای هر درخواست به یک کد App Check معتبر نیاز داشته باشد.

درستی‌سنجی کد

برای درستی‌سنجی کردن کدهای App Check در پس‌بخش، منطقی به نقطه‌های پایانی میانای برنامه‌سازی کاربردی خود اضافه کنید که کارهای زیر را انجام دهد:

  • بررسی کنید که هر درخواست شامل یک کد App Check باشد.

  • درستی‌سنجی کردن کد App Check بااستفاده از «سرپرست SDK».

    اگر درستی‌سنجی موفقیت‌آمیز باشد، «سرپرست SDK» کد App Check رمزگشایی‌شده را برمی‌گرداند. درستی‌سنجی موفق نشان می‌دهد که کد از برنامه‌ای منشأ گرفته است که به پروژه Firebase شما تعلق دارد.

هر درخواستی را که در هریک از این بررسی‌ها رد شود رد کنید. برای مثال:

Node.js

اگر قبلاً Node.js Admin SDK را نصب نکرده‌اید، این کار را انجام دهید.

سپس، بااستفاده از میان‌افزار Express.js به‌عنوان نمونه:

import express from "express";
import { initializeApp } from "firebase-admin/app";
import { getAppCheck } from "firebase-admin/app-check";

const expressApp = express();
const firebaseApp = initializeApp();

const appCheckVerification = async (req, res, next) => {
    const appCheckToken = req.header("X-Firebase-AppCheck");

    if (!appCheckToken) {
        res.status(401);
        return next("Unauthorized");
    }

    try {
        const appCheckClaims = await getAppCheck().verifyToken(appCheckToken);

        // If verifyToken() succeeds, continue with the next middleware
        // function in the stack.
        return next();
    } catch (err) {
        res.status(401);
        return next("Unauthorized");
    }
}

expressApp.get("/yourApiEndpoint", [appCheckVerification], (req, res) => {
    // Handle request.
});

پایتون

اگر قبلاً Python Admin SDK را نصب نکرده‌اید، این کار را انجام دهید.

سپس، در گرداننده‌های نقطه پایانی میانای برنامه‌سازی کاربردی، app_check.verify_token() را فراخوانی کنید و اگر ناموفق بود، درخواست را رد کنید. در مثال زیر، تابعی که با @before_request تزئین شده است این کار را برای همه درخواست‌ها انجام می‌دهد:

import firebase_admin
from firebase_admin import app_check
import flask
import jwt

firebase_app = firebase_admin.initialize_app()
flask_app = flask.Flask(__name__)

@flask_app.before_request
def verify_app_check() -> None:
    app_check_token = flask.request.headers.get("X-Firebase-AppCheck", default="")
    try:
        app_check_claims = app_check.verify_token(app_check_token)
        # If verify_token() succeeds, okay to continue to route handler.
    except (ValueError, jwt.exceptions.DecodeError):
        flask.abort(401)

@flask_app.route("/yourApiEndpoint")
def your_api_endpoint(request: flask.Request):
    # Handle request.
    ...

رفتن

اگر قبلاً Admin SDK for Go را نصب نکرده‌اید، این کار را انجام دهید.

سپس، در گرداننده‌های نقطه پایانی میانای برنامه‌سازی کاربردی، appcheck.Client.VerifyToken() را فراخوانی کنید و اگر ناموفق بود، درخواست را رد کنید. در مثال زیر، یک تابع پوشاننده این منطق را به گرداننده‌های نقطه پایانی اضافه می‌کند:

package main

import (
    "context"
    "log"
    "net/http"

    firebaseAdmin "firebase.google.com/go/v4"
    "firebase.google.com/go/v4/appcheck"
)

var (
    appCheck *appcheck.Client
)

func main() {
    app, err := firebaseAdmin.NewApp(context.Background(), nil)
    if err != nil {
        log.Fatalf("error initializing app: %v\n", err)
    }

    appCheck, err = app.AppCheck(context.Background())
    if err != nil {
        log.Fatalf("error initializing app: %v\n", err)
    }

    http.HandleFunc("/yourApiEndpoint", requireAppCheck(yourApiEndpointHandler))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func requireAppCheck(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
    wrappedHandler := func(w http.ResponseWriter, r *http.Request) {
        appCheckToken, ok := r.Header[http.CanonicalHeaderKey("X-Firebase-AppCheck")]
        if !ok {
            w.WriteHeader(http.StatusUnauthorized)
            w.Write([]byte("Unauthorized."))
            return
        }

        _, err := appCheck.VerifyToken(appCheckToken[0])
        if err != nil {
            w.WriteHeader(http.StatusUnauthorized)
            w.Write([]byte("Unauthorized."))
            return
        }

        // If VerifyToken() succeeds, continue with the provided handler.
        handler(w, r)
    }
    return wrappedHandler
}

func yourApiEndpointHandler(w http.ResponseWriter, r *http.Request) {
    // Handle request.
}

جاوا

اگر قبلاً Admin SDK for Java (نسخه ۹.۱۲.۰ یا جدیدتر) را نصب نکرده‌اید، این کار را انجام دهید.

سپس، در فیلترهای درخواست یا گرداننده‌های نقطه پایانی میانای برنامه‌سازی کاربردی، FirebaseAppCheck.getInstance().verifyToken() (یا verifyTokenAsync()) را فراخوانی کنید و اگر ناموفق بود، درخواست را رد کنید. مثال زیر از فیلتر Spring Boot برای درستی‌سنجی کردن کد برای درخواست‌های ورودی استفاده می‌کند:

import com.google.firebase.FirebaseApp;
import com.google.firebase.appcheck.FirebaseAppCheck;
import com.google.firebase.appcheck.FirebaseAppCheckException;
import com.google.firebase.appcheck.VerifyAppCheckTokenResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.filter.OncePerRequestFilter;

@SpringBootApplication
@RestController
public class Application {

    public static void main(String[] args) {
        FirebaseApp.initializeApp();
        SpringApplication.run(Application.class, args);
    }

    @GetMapping("/yourApiEndpoint")
    public String yourApiEndpoint() {
        // Handle request.
        return "Success";
    }

    @Component
    public static class AppCheckFilter extends OncePerRequestFilter {
        @Override
        protected void doFilterInternal(
                HttpServletRequest request,
                HttpServletResponse response,
                FilterChain filterChain) throws ServletException, IOException {
            String appCheckToken = request.getHeader("X-Firebase-AppCheck");
            if (appCheckToken == null || appCheckToken.isEmpty()) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
                return;
            }

            try {
                VerifyAppCheckTokenResponse appCheckResponse =
                    FirebaseAppCheck.getInstance().verifyToken(appCheckToken);
                // If verifyToken() succeeds, continue with the filter chain.
                filterChain.doFilter(request, response);
            } catch (FirebaseAppCheckException e) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
            }
        }
    }
}

غیره

اگر باطن شما به زبان دیگری نوشته شده است، می‌توانید از یک کتابخانه JWT چندمنظوره، مانند کتابخانه‌ای که در jwt.io یافت می‌شود، برای تأیید نشان‌های App Check استفاده کنید.

منطق درستی‌سنجی کد شما باید مراحل زیر را تکمیل کند:

  1. «مجموعه کلید وب JSON» (JWK) عمومی «بررسی برنامه Firebase» را از نقطه پایانی JWKS «بررسی برنامه» دریافت کنید: https://firebaseappcheck.googleapis.com/v1/jwks
  2. امضای کد App Check را درستی‌سنجی کنید تا مطمئن شوید قانونی است.
  3. مطمئن شوید که سرصفحه کد از الگوریتم RS256 استفاده می‌کند.
  4. مطمئن شوید سرایند کد از نوع JWT باشد.
  5. مطمئن شوید که توکن توسط Firebase App Check تحت پروژه شما صادر شده است.
  6. مطمئن شوید که کد منقضی نشده باشد.
  7. مطمئن شوید که مخاطب توکن با پروژه شما مطابقت داشته باشد.
  8. اختیاری: بررسی کنید که موضوع کد با شناسه برنامه شما مطابقت داشته باشد.

قابلیت‌های کتابخانه‌های JWT می‌تواند متفاوت باشد؛ حتماً هر مرحله‌ای را که توسط کتابخانه انتخابی شما انجام نمی‌شود به‌صورت دستی تکمیل کنید.

مثال زیر مراحل لازم را در Ruby بااستفاده از jwt gem به‌عنوان لایه میان‌افزار Rack انجام می‌دهد.

require 'json'
require 'jwt'
require 'net/http'
require 'uri'

class AppCheckVerification
def initialize(app, options = {})
    @app = app
    @project_number = options[:project_number]
end

def call(env)
    app_id = verify(env['HTTP_X_FIREBASE_APPCHECK'])
    return [401, { 'Content-Type' => 'text/plain' }, ['Unauthenticated']] unless app_id
    env['firebase.app'] = app_id
    @app.call(env)
end

def verify(token)
    return unless token

    # 1. Obtain the Firebase App Check Public Keys
    # Note: It is not recommended to hard code these keys as they rotate,
    # but you should cache them for up to 6 hours.
    uri = URI('https://firebaseappcheck.googleapis.com/v1/jwks')
    jwks = JSON(Net::HTTP.get(uri))

    # 2. Verify the signature on the App Check token
    payload, header = JWT.decode(token, nil, true, jwks: jwks, algorithms: 'RS256')

    # 3. Ensure the token's header uses the algorithm RS256
    return unless header['alg'] == 'RS256'

    # 4. Ensure the token's header has type JWT
    return unless header['typ'] == 'JWT'

    # 5. Ensure the token is issued by App Check
    return unless payload['iss'] == "https://firebaseappcheck.googleapis.com/#{@project_number}"

    # 6. Ensure the token is not expired
    return unless payload['exp'] > Time.new.to_i

    # 7. Ensure the token's audience matches your project
    return unless payload['aud'].include? "projects/#{@project_number}"

    # 8. The token's subject will be the app ID, you may optionally filter against
    # an allow list
    payload['sub']
rescue
end
end

class Application
def call(env)
    [200, { 'Content-Type' => 'text/plain' }, ["Hello app #{env['firebase.app']}"]]
end
end

use AppCheckVerification, project_number: 1234567890
run Application.new

محافظت دربرابر بازپخش (بتا)

برای محافظت از نقطه پایانی دربرابر حملات بازپخش، می‌توانید پس‌از درستی‌سنجی کردن کد App Check، از آن استفاده کنید تا فقط یک‌بار قابل‌استفاده باشد.

استفاده از محافظت دربرابر بازپخش، یک رفت‌وبرگشت شبکه به تماس درستی‌سنجی کد اضافه می‌کند و بنابراین به هر نقطه پایانی که از آن استفاده می‌کند تأخیر اضافه می‌کند. به همین دلیل، توصیه می‌کنیم محافظت دربرابر بازپخش را فقط در نقاط پایانی بسیار حساس فعال کنید.

برای استفاده از محافظت دربرابر بازپخش، کارهای زیر را انجام دهید:

  1. در کنسول Cloud، نقش «تأییدکننده کد Firebase App Check» را به حساب سرویس مورداستفاده برای تأیید کدها اعطا کنید.

    • اگر Admin SDK را با اعتبارنامه حساب سرویس Admin SDK که از کنسول Firebase بارگیری کرده‌اید مقداردهی اولیه کرده باشید، نقش موردنیاز قبلاً اعطا شده است.
    • اگر از نسل اول Cloud Functions با پیکربندی پیش‌فرض «سرپرست SDK» استفاده می‌کنید، این نقش را به حساب خدماتی پیش‌فرض App Engine اعطا کنید. تغییر اجازه‌های حساب سرویس را ببینید.
    • اگر از Cloud Functions نسل دوم با پیکربندی پیش‌فرض Admin SDK استفاده می‌کنید، این نقش را به حساب سرویس محاسباتی پیش‌فرض اعطا کنید.
  2. برای مصرف کردن یک کد، { consume: true } (Node.js)، consume=True (Python)، یا VerifyAppCheckTokenOptions را با setConsume(true) (Java) به تماس درستی‌سنجی کد ارسال کنید، یا VerifyOneTimeToken() (Go) را فراخوانی کنید، و سپس شیء نتیجه را بررسی کنید. اگر کد قبلاً مصرف شده است، درخواست را رد کنید یا اقدام اصلاحی انجام دهید، مثلاً از فراخواننده بخواهید سایر بررسی‌ها را بگذراند:

    Node.js

    const appCheckClaims = await getAppCheck().verifyToken(appCheckToken, { consume: true });
    
    if (appCheckClaims.alreadyConsumed) {
        res.status(401);
        return next('Unauthorized');
    }
    
    // If verifyToken() succeeds and alreadyConsumed is not set, okay to continue.
    

    این کار کد را درستی‌سنجی می‌کند و سپس آن را به‌عنوان مصرف‌شده پرچم‌گذاری می‌کند. فراخوانی‌های آتی verifyToken(appCheckToken, { consume: true }) در همان مجموعه کد alreadyConsumed به true. (توجه داشته باشید که اگر consume تنظیم نشده باشد، verifyToken() توکن مصرف‌شده را رد نمی‌کند یا حتی بررسی نمی‌کند که آیا مصرف شده است یا نه.)

    پایتون

    app_check_claims = app_check.verify_token(app_check_token, consume=True)
    
    if app_check_claims.get("already_consumed"):
        return "App Check token already consumed", 401
    
    # If verify_token() succeeds and already_consumed is not set, okay to continue.
    

    این کار کد را درستی‌سنجی می‌کند و سپس آن را به‌عنوان مصرف‌شده پرچم‌گذاری می‌کند. فراخوانی‌های آتی app_check.verify_token(app_check_token, consume=True) در همان مجموعه کد already_consumed به True. (توجه داشته باشید که app_check.verify_token() توکن مصرف‌شده را رد نمی‌کند یا حتی اگر consume تنظیم نشده باشد، بررسی نمی‌کند که آیا مصرف شده است یا نه.)

    رفتن

    decodedToken, err := appCheck.VerifyOneTimeToken(r.Context(), appCheckToken[0])
    if err != nil || (decodedToken.AlreadyConsumed != nil && *decodedToken.AlreadyConsumed) {
        w.WriteHeader(http.StatusUnauthorized)
        w.Write([]byte("Unauthorized."))
        return
    }
    
    // If VerifyOneTimeToken() succeeds and AlreadyConsumed is not true, okay to
    // continue with the provided handler.
    

    این کار کد را درستی‌سنجی می‌کند و سپس آن را به‌عنوان مصرف‌شده پرچم‌گذاری می‌کند. ‫appCheck.VerifyOneTimeToken()های بعدی در همان مجموعه کد AlreadyConsumed به true. (توجه داشته باشید که appCheck.VerifyToken() توکن مصرف‌شده را رد نمی‌کند یا حتی بررسی نمی‌کند که آیا مصرف شده است یا نه.)

    جاوا

    VerifyAppCheckTokenOptions options = VerifyAppCheckTokenOptions.builder()
        .setConsume(true)
        .build();
    VerifyAppCheckTokenResponse appCheckResponse =
        FirebaseAppCheck.getInstance().verifyToken(appCheckToken, options);
    
    if (appCheckResponse.isAlreadyConsumed().orElse(false)) {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
        return;
    }
    
    // If verifyToken() succeeds and isAlreadyConsumed() is not true, continue
    // with the filter chain.
    

    این کار کد را درستی‌سنجی می‌کند و سپس آن را به‌عنوان مصرف‌شده پرچم‌گذاری می‌کند. فراخوانی‌های آینده verifyToken(appCheckToken, options) با setConsume(true) در همان کد Optional.of(true) را از isAlreadyConsumed() برمی‌گرداند. (توجه داشته باشید که verifyToken() کد مصرف‌شده را رد نمی‌کند یا حتی بررسی نمی‌کند که آیا مصرف شده است یا نه، اگر setConsume(true) تنظیم نشده باشد.)

وقتی این ویژگی را برای نقطه پایانی خاصی فعال می‌کنید، باید کد کارخواه برنامه‌تان را نیز به‌روز کنید تا توکن‌های با استفاده محدود مصرف‌شدنی را برای استفاده با نقطه پایانی دریافت کنید. اسناد سمت کارخواه را برای پلاتفرم‌های Apple، Android، و وب ببینید.