「玩转腾讯云」API网关+云函数SCF开启OAuth2.0认证实战

2020-06-04 20:44:39 浏览数 (1)

如果在按照教程测试的时候遇到服务调用失败的情况,可以直接跳到第五步,查看以下避坑指北呦!

一、创建生成Json Web Token的云函数

按照https://cloud.tencent.com/document/product/628/38393文章的指导,在开启API网关的OAuth2.0认证之前必须新建OAuth认证服务器,下面我们用SCF云函数实现一个Golang版本的认证服务器

云函数代码如下

代码语言:go复制
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/google/uuid"
	"github.com/lestrrat-go/jwx/jwk"
	"github.com/tencentyun/scf-go-lib/cloudfunction"
	"log"
	"time"
)

// ssh-keygen -t rsa生成的私钥文件,可以根据需求替换成自己的私钥
var keyData = []byte("-----BEGIN RSA PRIVATE KEY-----nMIIEowIBAAKCAQEA4f5wg5l2hKsTeNem/V41fGnJm6gOdrj8ym3rFkEU/wT8RDtnnSgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7mCpz9Er5qLaMXJwZxzHzAahlfA0incqabvJOMvQtzD6uQv6wPEyZtDTWiQi9AXwBpHssPnpYGIn20ZZuNlX2BrClciHhCnPUIIZOQn/MmqTD31jSyjoQoV7MhhMTATKJx2XrHhR 1DcKJzQBSTAGnpYVaqpsARnap nwRipr3nUTuxyGohBTSmjJ2usSeQXHI3bODIRe1AuTyHceAbewn8b462yEWKAnRdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy7wIDAQABAoIBAQCwia1k7 2oZ2d3nn6agCAbqIE1QXfCmh41ZqJHbOY3oRQG3X1wpcGH4Gk O zDVTV2JszdcOt7E5dAynMaomETAhRxB7hlIOnEN7WKm dGNrKRvV0wDU5ReFMRHg31/Lnu8c 5BvGjZX ky9nPOIhFFYJqwCRlopGSUIxmVj5rSgtzk3iWOQXr ah1bjEXvlxDOWkHN6YfpV5ThdEnKdBIPGEVqa63r9n2h qazKrtiRqJqGnOrHzOECYbRFYhexsNFz7YT02xdfSHn7gMnIvabDDP/Qp0PjE1jdouiMaFHYnLBbgvlnZW9yuVf/rpXTUq/njxIXMmvmEyyvSDnnFcFikB8pAoGBAPF77hK4m3/rdGT7X8a/gwvZ2R121aBcdPwEaUhvj/36dx596zvYnmEOjrWfZhF083/nYWE2kVquj2wjs otCLfifEEgXcVPTnEOPO9Zg3uNSL0nNQghjnFuD3iGLTUBCtM66oTe0jLSslHe8gLGEQqyMzHOzYxNqibxcOZIe8Qt0NAoGBAO UnI5 XWjWEgDmvyC3TrOSf/KCGjtu0TSv30ipv27bDLMrpvPmD/5lpptTFwcxvVhCsn2b chCjlghFSWFbBULBrfci2FtliClOVMYrlNBdUSJhf3aYSG2Doe6Bgt1n2CpNnn/iu37Y3NfemZBJA7hNl4dYe f uzM87cdQ214 jrAoGAXA0XxX8ll2 ToOLJsaNTnOvNB9h9Uc5qK5X5w 7G7O998BN2PC/MWp8H 2fVqpXgNENpNXttkRm1hk1dych86nEunfdPuqsX as44oCyJGFHVBnWpm33eWQw9YqANRI pCJzP08I5WK3osnPiwshd nhR54yjgfYhBFNI7B95PmEQkCgYBzFSz7h1 s34Ycr8SvxsOBWxymG5zaCsUbPsL0n4aCgLScCHb9J E86aVbbVFdglYa5Id7DPTL61ixhl7WZjujspeXZGSbmq0KcnckbnmDgqkLECiOJW2NHP/j0McAkDLL4tysF8TLDO8gvuvzNC WQ6drO2ThrypLVZQ ryneBIPmwKBgEZxhqa0gVvHQG/7Od69KWj4eJP28kq13RhKay8JOoN0vPmspXJo1HY3nCKuHRG AP579dncdUnOMvfXOtkdM4vk0 hWASBQzM9xzVcztCa koAugjVaLS9A n9uQoqEeVNTckxx0S2bYevRy7hGQmUJTyQm3j1zEUR5jpdbL83Fbqn-----END RSA PRIVATE KEY-----")

func init()  {
	parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyData)
	if err != nil {
		log.Println(err)
	}
	publicKey, err := jwk.New(&parsedKey.PublicKey)

	if err != nil {
		log.Println(err)
	}

	jsonbuf, err := json.MarshalIndent(publicKey, "", "  ")
	if err != nil {
		log.Println(err)
	}
	fmt.Println("公钥:n", string(jsonbuf))
}

type Event struct {
	RequestContext struct {
		ServiceID  string `json:"serviceId"`
		Path       string `json:"path"`
		HTTPMethod string `json:"httpMethod"`
		RequestID  string `json:"requestId"`
		Identity   struct {
			SecretID string `json:"secretId"`
		} `json:"identity"`
		SourceIP string `json:"sourceIp"`
		Stage    string `json:"stage"`
	} `json:"requestContext"`
	Headers struct {
		AcceptLanguage string `json:"Accept-Language"`
		Accept         string `json:"Accept"`
		Host           string `json:"Host"`
		UserAgent      string `json:"User-Agent"`
		OAuthClientId string `json:"OAuth_Client_ID"`
		OAuthClientSecret string `json:"OAuth_Client_Secret"`
	} `json:"headers"`
	Body           string `json:"body"`
	PathParameters struct {
		Path string `json:"path"`
	} `json:"pathParameters"`
	QueryStringParameters struct {
		Code string `json:"code"`
		GrantType string `json:"grant_type"`
	} `json:"queryStringParameters"`
	HeaderParameters struct {
		Refer string `json:"Refer"`
	} `json:"headerParameters"`
	StageVariables struct {
		Stage string `json:"stage"`
	} `json:"stageVariables"`
	Path        string `json:"path"`
	QueryString struct {
		Foo string `json:"foo"`
		Bob string `json:"bob"`
	} `json:"queryString"`
	HTTPMethod string `json:"httpMethod"`
}

type Claims struct {
	Username string `json:"username"`
	jwt.StandardClaims
}

func integratedResponse(body map[string]interface{}) (map[string]interface{}, error) {
	response := make(map[string]interface{})
	response["isBase64Encoded"] = false
	response["statusCode"] = 200
	headers := make(map[string]string)
	headers["Content-Type"] = "application/json"
	response["headers"] = headers

	js, err := json.Marshal(body)
	if err != nil {
		log.Println("3333", err)
	}

	response["body"] = fmt.Sprintf("%s", js)
	return response, nil
}

func getToken(ctx context.Context, event Event) (map[string]interface{}, error) {
	result := make(map[string]interface{})
	if event.QueryStringParameters.Code != "cloud.tencent.com" || event.Headers.OAuthClientId != "cloud.tencent.com" || event.Headers.OAuthClientSecret != "cloud.tencent.com" {
		result["error"] = "error param!"
		result["error_code"] = 10021

		return integratedResponse(result)
	}

	expirationTime := time.Now().Add(5 * time.Minute)
	claims := &Claims{
		Username: "admin",
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: expirationTime.Unix(),
		},
	}

	parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyData)
	if err != nil {
		log.Println("1111", err)
		result["error"] = "secret key parse error!"
		result["error_code"] = 10022
		return integratedResponse(result)
	}

	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
	tokenString, err := token.SignedString(parsedKey)
	if err != nil {
		log.Println("2222", err)
		result["error"] = "generate token error"
		return integratedResponse(result)
	}

	result["refresh_token"], _ = uuid.NewUUID()
	result["id_token"] = tokenString
	result["token_type"] = "Bearer"
	result["expires_in"] = 300
	return integratedResponse(result)
}

func main() {
	// Make the handler available for Remote Procedure Call by Cloud Function
	cloudfunction.Start(getToken)
}

1、执行以下命令将如上golang代码编译

GOOS=linux GOARCH=amd64 go build -o main main.go

2、将编译成功后的代码执行以下命令打包

zip main.zip main

3、在上海地区新建函数,函数名称为getToken,运行环境选择Golang 1,创建方式选择空白函数,然后点击下一步按钮

4、在函数配置页面,提交方法选择本地上传zip包,函数代码,选择刚刚编译并打包的main.zip,然后点击完成按钮

二、创建OAuth认证服务器

1、打开API网关服务页面,在上海地区,点击新建按钮,在弹出框中填写服务名为AuthorizationService,前端

类型选择http和https,访问方式勾选内网VPC公网,然后点击提交按钮

2、点击提交按钮创建成功后,点击服务ID进入服务详情页面

3、进入服务详情页面后,点击管理API标签页

4、进入管理API标签页后,点击新建按钮

5、点击新建按钮后,API名称填写为请求token,路径为/oauth/token,鉴权类型为免鉴权,参数配置新增4个参数

  1. 参数名code,参数位置选择Query
  2. 参数名grant_type,参数位置选择Query
  3. 参数名OAuth_Client_ID,取消必填勾选,默认值填写为cloud.tencent.com,这是因为在云函数代码中写死了
  4. 参数名OAuth_Client_Secret,取消必填勾选,默认值填写为cloud.tencent.com,这是因为在云函数代码中写死了

然后点击下一步

6、在后端配置页面,后端类型选择cloud function,云函数名称选择getToken,勾选是否启用响应集成,然后点击下一步

7、在响应结果配置页面,选择返回类型为JSON,然后点击完成

8、在点击完成后弹出的页面中点击前往发布服务

9、在点击前往发布服务后出现的页面点击发布按钮,在弹出框填写备注,然后点击提交按钮,生成token服务就发布成功了

10、本地测试生成token服务是否正常

三、创建授权API

1、在上海区域新建服务,服务名为OAuthService,前段类型为http和https,访问方式勾选内网VPC和公网,然后点击提交

2、点击提交完成后,进去OAuthServic的详情页面,点击管理API标签页,在管理API页面点击新建按钮

3、点击新建按钮后出现的前段配置页面中,鉴权模式选择OAuth 2.0,OAuth模式选择授权API,新增两个参数

  1. 参数名code,参数位置选择Query
  2. 参数名grant_type,参数位置选择Query

然后点击下一步

4、点击下一步在出现的后端配置页面中,认证服务器填写我们刚刚创建的AuthorizationService的公网域名,后端路径为/oauth/token,Token携带位置选择Header,公钥填写

{  "kty": "RSA",  "e": "AQAB",  "n": "4f5wg5l2hKsTeNem_V41fGnJm6gOdrj8ym3rFkEU_wT8RDtnSgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7mCpz9Er5qLaMXJwZxzHzAahlfA0icqabvJOMvQtzD6uQv6wPEyZtDTWiQi9AXwBpHssPnpYGIn20ZZuNlX2BrClciHhCPUIIZOQn_MmqTD31jSyjoQoV7MhhMTATKJx2XrHhR-1DcKJzQBSTAGnpYVaqpsARap-nwRipr3nUTuxyGohBTSmjJ2usSeQXHI3bODIRe1AuTyHceAbewn8b462yEWKARdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy7w"}

这里的公钥是和笔者提供的Golang云函数的私钥相对应的,如果需要更换私钥,则公钥也要跟着替换。

参数名OAuth_Client_ID,参数值填写为cloud.tencent.com,这是因为在云函数代码中写死了

参数名OAuth_Client_Secret,参数值填写为cloud.tencent.com,这是因为在云函数代码中写死了,然后点击下一步

5、在点击下一步出现的响应结果配置页面中,返回类型选择JSON,然后点击完成

6、在点击完成后弹出的页面中点击前往发布服务,然后点击发布按钮,填写发布备注信息,点击提交

四、创建业务API

1、在OAuthService的管理API页面,点击新建按钮

2、在点击新建按钮出现的前端配置页面中,鉴权类型选择OAuth 2.0,OAuth模式选择业务API,关联授权API选择刚刚创建的名称为token的API,然后点击下一步

3、在点击下一步出现的后端配置页面中,后端类型选择mock,返回数据填写helloworld,然后点击完成按钮

4、在点击完成按钮出现的页面中选择前往发布服务选项,然后点击发布,填写备注信息后,点击提交按钮

5、测试

我们直接访问刚刚发布的helloworld业务API出现如下图所示

接下来我们先调用授权API获取一个访问token,然后把token放到请求Header中再访问helloworld业务API

可以看到把token放到请求Header后再次请求业务API,正常返回了helloworld

五、避坑指北

1、API网关 云函数SCF创建的认证服务,如果是在同地域的话,可能会遇到不通的情况

笔者在整理这片文章的时候本来是打算用北京地区测试的,结果在测试的时候遇到了一个大坑,后面又改用了上海区域。

这是笔者在北京测试的时候创建的3个服务的域名做dig以后的结果,如果两个服务dig出来的IP地址是相同的,那这两个服务之间调用的时候会有问题

这是服务域名dig以后是同一个IP地址做API调试时的结果这是服务域名dig以后是同一个IP地址做API调试时的结果
用postman测试时结果有可能是这样的用postman测试时结果有可能是这样的

如果你在测试的时候不幸遇到了以上两种情况,可以先对域名执行dig命令,看下IP地址是否是同一个,然后重新新建一个服务重试下!

0 人点赞