Cloudflare Access 已启用
Cloudflare Access 已启用
已为以下各项启用 Access:
rough-fog-ba96.public-521.workers.dev重要信息:更新您的 Worker 脚本,验证 Access JWT 或您的应用程序是否不安全。只有您 Cloudflare 帐户中的用户才能访问此域。
管理策略。7ac0548bd5157d817437dd1862f30aafb9884187ae125e4a5b369859cc1e473b
https://shilonglibrary.cloudflareaccess.com/cdn-cgi/access/certs
验证JWT
当Cloudflare向你的源发送请求时,请求会包含一个应用令牌作为请求头。通过浏览器发出的请求也会将令牌作为 Cookie 传递。Cf-Access-Jwt-AssertionCF_Authorization
Cloudflare会用你账户独有的密钥对签约令牌。你应该用公钥验证令牌,确保请求来自Access而不是恶意第三方。我们建议验证头部而非 Cookie,因为 Cookie 并不保证会被传递。Cf-Access-Jwt-AssertionCF_Authorization
签名密钥对的公钥位于 ,其中是你的Cloudflare One队名。https://
默认情况下,Access 每 6 周轮换一次签名密钥。这意味着你需要在按键旋转时通过程序或手动更新它们。之前的密钥在轮换后有效期为7天,以便你有时间进行更新。
你也可以用API手动旋转密钥。这可以用于测试或安全目的。
如下例所示,包含两个公钥:当前用于签名所有新令牌的密钥,以及已被旋转出去的旧密钥。https://
keys:两个调性都以JWK格式呈现public_cert:当前密钥以PEM格式呈现public_certs:两键均为PEM格式
{'keys': [{'kid': '1a1c3986a44ce6390be42ec772b031df8f433fdc71716db821dc0c39af3bce49','kty': 'RSA','alg': 'RS256','use': 'sig','e': 'AQAB','n': '5PKw-...-AG7MyQ'},{'kid': '6c3bffef71bb0a90c9cbef3b7c0d4a1c7b4b8b76b80292a623afd9dac45d1c65','kty': 'RSA','alg': 'RS256','use': 'sig','e': 'AQAB','n': 'pwVn...AA6Hw'}],'public_cert': {'kid': '6c3bffef71bb0a90c9cbef3b7c0d4a1c7b4b8b76b80292a623afd9dac45d1c65','cert': '-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- '},'public_certs': [{'kid': '1a1c3986a44ce6390be42ec772b031df8f433fdc71716db821dc0c39af3bce49','cert': '-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- '},{'kid': '6c3bffef71bb0a90c9cbef3b7c0d4a1c7b4b8b76b80292a623afd9dac45d1c65','cert': '-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- '}]}手动验证令牌:
- 从请求头复制JWT。
Cf-Access-Jwt-Assertion - 去 jwt.io ↗。
- 选择RS256算法。
- 把JWT粘贴到编码框里。
- 在有效载荷框中,确保字段指向你的队伍领域()。 利用该值获取公钥以进行令牌验证。
isshttps://.cloudflareaccess.com jwt.ioiss - 确保页面显示签名已验证。
您可以放心,这个请求是由Access发出的。
你可以在你的Origin服务器上运行自动化脚本来验证收到的请求。提供的示例代码从请求中获取应用令牌,并与你的公钥进行签名核对。你需要在示例代码中插入自己的团队域和应用受众(AUD)标签。
Cloudflare Access 为每个应用分配了独特的 AUD 标签。令牌有效载荷中的声明指定了JWT适用于哪个应用。aud
要获得AUD标签:
- 在Cloudflare One ↗中,进入访问控制>应用程序。
- 选择为您的应用配置。
- 在基本信息标签中,复制应用受众(AUD)标签。
你现在可以把AUD标签粘贴到你的代币验证脚本里。除非你删除或重建Access应用,否则AUD标签永远不会改变。
当Cloudflare Access显示在你的工作者面前时,你的工作者仍然需要验证Cloudflare Access添加到入请求头部的JWT。Cf-Access-Jwt-Assertion
以下代码将使用 Jose NPM 包↗验证 JWT:
import { jwtVerify, createRemoteJWKSet } from 'jose';export default {async fetch(request, env, ctx) {// Verify the POLICY_AUD environment variable is setif (!env.POLICY_AUD) {return new Response('Missing required audience', {status: 403,headers: { 'Content-Type': 'text/plain' },});}// Get the JWT from the request headersconst token = request.headers.get('cf-access-jwt-assertion');// Check if token existsif (!token) {return new Response('Missing required CF Access JWT', {status: 403,headers: { 'Content-Type': 'text/plain' },});}try {// Create JWKS from your team domainconst JWKS = createRemoteJWKSet(new URL(`${env.TEAM_DOMAIN}/cdn-cgi/access/certs`),);// Verify the JWTconst { payload } = await jwtVerify(token, JWKS, {issuer: env.TEAM_DOMAIN,audience: env.POLICY_AUD,});// Token is valid, proceed with your application logicreturn new Response(`Hello ${payload.email || 'authenticated user'}!`, {headers: { 'Content-Type': 'text/plain' },});} catch (error) {// Token verification failedconst message = error instanceof Error ? error.message : 'Unknown error';return new Response(`Invalid token: ${message}`, {status: 403,headers: { 'Content-Type': 'text/plain' },});}},};将这些环境变量添加到你的工作者中:
POLICY_AUD:你的申请AUD标签TEAM_DOMAIN: ,其中 被替换为你的真实队名。https://.cloudflareaccess.com
你可以通过添加到Worker的Wrangler配置文件中,或者通过Cloudflare仪表盘的“工人与页面”>your-worker >设置>环境变量来设置这些变量。
package mainimport ('context''fmt''net/http''github.com/coreos/go-oidc/v3/oidc')var (ctx= context.TODO()teamDomain = 'https://test.cloudflareaccess.com'certsURL = fmt.Sprintf('%s/cdn-cgi/access/certs', teamDomain)// The Application Audience (AUD) tag for your applicationpolicyAUD = '4714c1358e65fe4b408ad6d432a5f878f08194bdb4752441fd56faefa9b2b6f2'config = &oidc.Config{ClientID: policyAUD,}keySet = oidc.NewRemoteKeySet(ctx, certsURL)verifier = oidc.NewVerifier(teamDomain, keySet, config))// VerifyToken is a middleware to verify a CF Access tokenfunc VerifyToken(next http.Handler) http.Handler {fn := func(w http.ResponseWriter, r *http.Request) {headers := r.Header// Make sure that the incoming request has our token header//Could also look in the cookies for CF_AUTHORIZATIONaccessJWT := headers.Get('Cf-Access-Jwt-Assertion')if accessJWT == '' {w.WriteHeader(http.StatusUnauthorized)w.Write([]byte('No token on the request'))return}// Verify the access tokenctx := r.Context()_, err := verifier.Verify(ctx, accessJWT)if err != nil {w.WriteHeader(http.StatusUnauthorized)w.Write([]byte(fmt.Sprintf('Invalid token: %s', err.Error())))return}next.ServeHTTP(w, r)}return http.HandlerFunc(fn)}func MainHandler() http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {w.Write([]byte('welcome'))})}func main() {http.Handle('/', VerifyToken(MainHandler()))http.ListenAndServe(':3000', nil)}pip安装以下内容:
- 烧瓶
- 请求
- PyJWT
- 密码学
from flask import Flask, requestimport requestsimport jwtimport jsonimport osapp = Flask(__name__)# The Application Audience (AUD) tag for your applicationPOLICY_AUD = os.getenv('POLICY_AUD')# Your CF Access team domainTEAM_DOMAIN = os.getenv('TEAM_DOMAIN')CERTS_URL = '{}/cdn-cgi/access/certs'.format(TEAM_DOMAIN)def _get_public_keys():'''Returns:List of RSA public keys usable by PyJWT.'''r = requests.get(CERTS_URL)public_keys = []jwk_set = r.json()for key_dict in jwk_set['keys']:public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key_dict))public_keys.append(public_key)return public_keysdef verify_token(f):'''Decorator that wraps a Flask API call to verify the CF Access JWT'''def wrapper():# Check for the POLICY_AUD environment variableif not POLICY_AUD:return 'missing required audience', 403token = ''if 'CF_Authorization' in request.cookies:token = request.cookies['CF_Authorization']else:return 'missing required cf authorization token', 403keys = _get_public_keys()# Loop through the keys since we can't pass the key set to the decodervalid_token = Falsefor key in keys:try:# decode returns the claims that has the email when neededjwt.decode(token, key=key, audience=POLICY_AUD, algorithms=['RS256'])valid_token = Truebreakexcept:passif not valid_token:return 'invalid token', 403return f()return wrapper@app.route('/')@verify_tokendef hello_world():return 'Hello, World!'if __name__ == '__main__':app.run()const express = require('express');const jose = require('jose');// The Application Audience (AUD) tag for your applicationconst AUD = process.env.POLICY_AUD;// Your CF Access team domainconst TEAM_DOMAIN = process.env.TEAM_DOMAIN;const CERTS_URL = `${TEAM_DOMAIN}/cdn-cgi/access/certs`;const JWKS = jose.createRemoteJWKSet(new URL(CERTS_URL));// verifyToken is a middleware to verify a CF authorization tokenconst verifyToken = async (req, res, next) => {// Check for the AUD environment variableif (!AUD) {return res.status(403).send({status: false,message: 'missing required audience',});}const token = req.headers['cf-access-jwt-assertion'];// Make sure that the incoming request has our token headerif (!token) {return res.status(403).send({status: false,message: 'missing required cf authorization token',});}try {const result = await jose.jwtVerify(token, JWKS, {issuer: TEAM_DOMAIN,audience: AUD,});req.user = result.payload;next();} catch (err) {return res.status(403).send({status: false,message: 'invalid token',});}};const app = express();app.use(verifyToken);app.get('/', (req, res) => {res.send('Hello World!');});app.listen(3333);验证JWT
当Cloudflare向你的源发送请求时,请求会包含一个应用令牌作为请求头。通过浏览器发出的请求也会将令牌作为 Cookie 传递。Cf-Access-Jwt-AssertionCF_Authorization
Cloudflare会用你账户独有的密钥对签约令牌。你应该用公钥验证令牌,确保请求来自Access而不是恶意第三方。我们建议验证头部而非 Cookie,因为 Cookie 并不保证会被传递。Cf-Access-Jwt-AssertionCF_Authorization
签名密钥对的公钥位于 ,其中是你的Cloudflare One队名。https://
默认情况下,Access 每 6 周轮换一次签名密钥。这意味着你需要在按键旋转时通过程序或手动更新它们。之前的密钥在轮换后有效期为7天,以便你有时间进行更新。
你也可以用API手动旋转密钥。这可以用于测试或安全目的。
如下例所示,包含两个公钥:当前用于签名所有新令牌的密钥,以及已被旋转出去的旧密钥。https://
keys:两个调性都以JWK格式呈现public_cert:当前密钥以PEM格式呈现public_certs:两键均为PEM格式
{'keys': [{'kid': '1a1c3986a44ce6390be42ec772b031df8f433fdc71716db821dc0c39af3bce49','kty': 'RSA','alg': 'RS256','use': 'sig','e': 'AQAB','n': '5PKw-...-AG7MyQ'},{'kid': '6c3bffef71bb0a90c9cbef3b7c0d4a1c7b4b8b76b80292a623afd9dac45d1c65','kty': 'RSA','alg': 'RS256','use': 'sig','e': 'AQAB','n': 'pwVn...AA6Hw'}],'public_cert': {'kid': '6c3bffef71bb0a90c9cbef3b7c0d4a1c7b4b8b76b80292a623afd9dac45d1c65','cert': '-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- '},'public_certs': [{'kid': '1a1c3986a44ce6390be42ec772b031df8f433fdc71716db821dc0c39af3bce49','cert': '-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- '},{'kid': '6c3bffef71bb0a90c9cbef3b7c0d4a1c7b4b8b76b80292a623afd9dac45d1c65','cert': '-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- '}]}手动验证令牌:
- 从请求头复制JWT。
Cf-Access-Jwt-Assertion - 去 jwt.io ↗。
- 选择RS256算法。
- 把JWT粘贴到编码框里。
- 在有效载荷框中,确保字段指向你的队伍领域()。 利用该值获取公钥以进行令牌验证。
isshttps://.cloudflareaccess.com jwt.ioiss - 确保页面显示签名已验证。
您可以放心,这个请求是由Access发出的。
你可以在你的Origin服务器上运行自动化脚本来验证收到的请求。提供的示例代码从请求中获取应用令牌,并与你的公钥进行签名核对。你需要在示例代码中插入自己的团队域和应用受众(AUD)标签。
Cloudflare Access 为每个应用分配了独特的 AUD 标签。令牌有效载荷中的声明指定了JWT适用于哪个应用。aud
要获得AUD标签:
- 在Cloudflare One ↗中,进入访问控制>应用程序。
- 选择为您的应用配置。
- 在基本信息标签中,复制应用受众(AUD)标签。
你现在可以把AUD标签粘贴到你的代币验证脚本里。除非你删除或重建Access应用,否则AUD标签永远不会改变。
当Cloudflare Access显示在你的工作者面前时,你的工作者仍然需要验证Cloudflare Access添加到入请求头部的JWT。Cf-Access-Jwt-Assertion
以下代码将使用 Jose NPM 包↗验证 JWT:
import { jwtVerify, createRemoteJWKSet } from 'jose';export default {async fetch(request, env, ctx) {// Verify the POLICY_AUD environment variable is setif (!env.POLICY_AUD) {return new Response('Missing required audience', {status: 403,headers: { 'Content-Type': 'text/plain' },});}// Get the JWT from the request headersconst token = request.headers.get('cf-access-jwt-assertion');// Check if token existsif (!token) {return new Response('Missing required CF Access JWT', {status: 403,headers: { 'Content-Type': 'text/plain' },});}try {// Create JWKS from your team domainconst JWKS = createRemoteJWKSet(new URL(`${env.TEAM_DOMAIN}/cdn-cgi/access/certs`),);// Verify the JWTconst { payload } = await jwtVerify(token, JWKS, {issuer: env.TEAM_DOMAIN,audience: env.POLICY_AUD,});// Token is valid, proceed with your application logicreturn new Response(`Hello ${payload.email || 'authenticated user'}!`, {headers: { 'Content-Type': 'text/plain' },});} catch (error) {// Token verification failedconst message = error instanceof Error ? error.message : 'Unknown error';return new Response(`Invalid token: ${message}`, {status: 403,headers: { 'Content-Type': 'text/plain' },});}},};将这些环境变量添加到你的工作者中:
POLICY_AUD:你的申请AUD标签TEAM_DOMAIN: ,其中 被替换为你的真实队名。https://.cloudflareaccess.com
你可以通过添加到Worker的Wrangler配置文件中,或者通过Cloudflare仪表盘的“工人与页面”>your-worker >设置>环境变量来设置这些变量。
package mainimport ('context''fmt''net/http''github.com/coreos/go-oidc/v3/oidc')var (ctx= context.TODO()teamDomain = 'https://test.cloudflareaccess.com'certsURL = fmt.Sprintf('%s/cdn-cgi/access/certs', teamDomain)// The Application Audience (AUD) tag for your applicationpolicyAUD = '4714c1358e65fe4b408ad6d432a5f878f08194bdb4752441fd56faefa9b2b6f2'config = &oidc.Config{ClientID: policyAUD,}keySet = oidc.NewRemoteKeySet(ctx, certsURL)verifier = oidc.NewVerifier(teamDomain, keySet, config))// VerifyToken is a middleware to verify a CF Access tokenfunc VerifyToken(next http.Handler) http.Handler {fn := func(w http.ResponseWriter, r *http.Request) {headers := r.Header// Make sure that the incoming request has our token header//Could also look in the cookies for CF_AUTHORIZATIONaccessJWT := headers.Get('Cf-Access-Jwt-Assertion')if accessJWT == '' {w.WriteHeader(http.StatusUnauthorized)w.Write([]byte('No token on the request'))return}// Verify the access tokenctx := r.Context()_, err := verifier.Verify(ctx, accessJWT)if err != nil {w.WriteHeader(http.StatusUnauthorized)w.Write([]byte(fmt.Sprintf('Invalid token: %s', err.Error())))return}next.ServeHTTP(w, r)}return http.HandlerFunc(fn)}func MainHandler() http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {w.Write([]byte('welcome'))})}func main() {http.Handle('/', VerifyToken(MainHandler()))http.ListenAndServe(':3000', nil)}pip安装以下内容:
- 烧瓶
- 请求
- PyJWT
- 密码学
from flask import Flask, requestimport requestsimport jwtimport jsonimport osapp = Flask(__name__)# The Application Audience (AUD) tag for your applicationPOLICY_AUD = os.getenv('POLICY_AUD')# Your CF Access team domainTEAM_DOMAIN = os.getenv('TEAM_DOMAIN')CERTS_URL = '{}/cdn-cgi/access/certs'.format(TEAM_DOMAIN)def _get_public_keys():'''Returns:List of RSA public keys usable by PyJWT.'''r = requests.get(CERTS_URL)public_keys = []jwk_set = r.json()for key_dict in jwk_set['keys']:public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key_dict))public_keys.append(public_key)return public_keysdef verify_token(f):'''Decorator that wraps a Flask API call to verify the CF Access JWT'''def wrapper():# Check for the POLICY_AUD environment variableif not POLICY_AUD:return 'missing required audience', 403token = ''if 'CF_Authorization' in request.cookies:token = request.cookies['CF_Authorization']else:return 'missing required cf authorization token', 403keys = _get_public_keys()# Loop through the keys since we can't pass the key set to the decodervalid_token = Falsefor key in keys:try:# decode returns the claims that has the email when neededjwt.decode(token, key=key, audience=POLICY_AUD, algorithms=['RS256'])valid_token = Truebreakexcept:passif not valid_token:return 'invalid token', 403return f()return wrapper@app.route('/')@verify_tokendef hello_world():return 'Hello, World!'if __name__ == '__main__':app.run()const express = require('express');const jose = require('jose');// The Application Audience (AUD) tag for your applicationconst AUD = process.env.POLICY_AUD;// Your CF Access team domainconst TEAM_DOMAIN = process.env.TEAM_DOMAIN;const CERTS_URL = `${TEAM_DOMAIN}/cdn-cgi/access/certs`;const JWKS = jose.createRemoteJWKSet(new URL(CERTS_URL));// verifyToken is a middleware to verify a CF authorization tokenconst verifyToken = async (req, res, next) => {// Check for the AUD environment variableif (!AUD) {return res.status(403).send({status: false,message: 'missing required audience',});}const token = req.headers['cf-access-jwt-assertion'];// Make sure that the incoming request has our token headerif (!token) {return res.status(403).send({status: false,message: 'missing required cf authorization token',});}try {const result = await jose.jwtVerify(token, JWKS, {issuer: TEAM_DOMAIN,audience: AUD,});req.user = result.payload;next();} catch (err) {return res.status(403).send({status: false,message: 'invalid token',});}};const app = express();app.use(verifyToken);app.get('/', (req, res) => {res.send('Hello World!');});app.listen(3333);

