API网关和AWS Lambda进行身份验证
當(dāng)Foreach最初涉足微服務(wù)領(lǐng)域時,我們并沒有真正構(gòu)建微服務(wù)。 我們以為我們做到了,但是我們所有的服務(wù)中總存在一些邏輯。 當(dāng)然,每個服務(wù)實際上應(yīng)該只專注于自己的任務(wù),而不應(yīng)該專注于屬于另一個微服務(wù)的事物。 我們這方面最明顯的棘手是認證和授權(quán)邏輯。
在某個時候,我們有幾個“微”服務(wù),它們根據(jù)AuthenticationService (甚至在較早的日子,甚至是針對共享數(shù)據(jù)庫)驗證了傳入請求的Authorization標(biāo)頭。 這給我們的AuthenticationService造成了比我們想要的更多的負載(多次驗證同一個令牌),但是這也導(dǎo)致在所有這些服務(wù)中都存在一些重要的代碼。 而且,正如任何開發(fā)人員所知,共享代碼鋪平了通往地獄的道路。 微服務(wù)變得超出其實際用途,這使得它們變得更難開發(fā)和維護。
在尋求救贖的過程中,我們Swift找到了一些可以幫助我們的解決方案。
JSON Web令牌
我們考慮的第一件事是開始使用JSON Web令牌(JWT) 。 JWT是一個開放標(biāo)準,它定義了一種獨立的方式來在各方之間安全地傳輸信息。 自包含意味著令牌本身可以包含我們需要的所有信息,例如用戶的標(biāo)識符或用戶名。 安全意味著其他方不可能干擾這些令牌。 令牌包含一個加密部分,要解密它,您需要一個只有您知道的秘密密鑰。 換句話說,如果令牌已被篡改,您將知道。
JWT是一個非常有趣的領(lǐng)導(dǎo)者,因為在我們這方面進行最小的調(diào)整,從理論上講,我們甚至可以消除微服務(wù)中的一些額外工作量(無論如何它們都不應(yīng)該這樣做)。 令牌的驗證是一個最小的過程,可以很好地集成到Spring框架中,因此我們不需要那么多代碼。 令牌還將包含我們需要的所有信息,因此我們不再需要從另一個Web服務(wù)請求此信息。
但是,JWT的問題在于,已經(jīng)有其他各方開發(fā)的其他一些應(yīng)用程序與API集成在一起。 事實證明,當(dāng)我們開始分發(fā)JWT令牌時,并不是所有的應(yīng)用程序都那么滿意。 由于短期內(nèi)無法更改這些應(yīng)用程序,因此我們暫時保留了這一想法。
API網(wǎng)關(guān)
我們的另一個想法是引入API網(wǎng)關(guān)。 這可以看作是我們API的包裝,意在為最終用戶抽象我們的API。 它可以更改對另一種格式的響應(yīng)。 它可以將多個HTTP請求合并為一個請求。 或者它可以提供其他監(jiān)視功能(例如“誰向某個端點發(fā)送垃圾郵件?”)。 但最重要的是,它應(yīng)該抽象與身份驗證有關(guān)的所有內(nèi)容。
在我們的例子中,想法是API網(wǎng)關(guān)甚至在請求被代理到我們的應(yīng)用程序之前都會驗證傳入的Authorization標(biāo)頭。 它應(yīng)該緩存結(jié)果,以便如果同一用戶請求五個端點,我們?nèi)匀幻啃r僅驗證一次令牌,并且應(yīng)該將身份驗證信息傳遞給我們的API,以便我們知道誰在請求資源。
我們的解決方案:AWS API Gateway
https://docs.aws.amazon.com/apigateway/latest/developerguide/images/custom-auth-workflow.png
市場上有許多符合此描述的產(chǎn)品,但經(jīng)過一番考慮,我們決定嘗試一下AWS API Gateway。 我們實施了自定義的“授權(quán)人”。 這是一個Lambda函數(shù),它接收客戶端提供的授權(quán)令牌作為輸入,并返回客戶端是否有權(quán)訪問所請求的資源。 如果身份驗證被拒絕,API網(wǎng)關(guān)將向客戶端返回403 HTTP代碼。 否則,該請求將被代理到我們的服務(wù)中。 授權(quán)者Lambda的結(jié)果在緩存中保留了一個小時。 我們還希望使用HTTP標(biāo)頭將用戶的身份傳遞給我們的基礎(chǔ)服務(wù)。 這樣,我們知道誰在我們的應(yīng)用程序中執(zhí)行請求。
授權(quán)者
我們的自定義Lambda函數(shù)是用Python編寫的。 它從傳入的請求中獲取Authorization標(biāo)頭,并向我們的AuthenticationService啟動HTTP請求-這是我們唯一可以驗證傳入的信息是否有效以及令牌適用于誰的地方。 這個HTTP請求將告訴我們最終用戶是誰。
Lambda函數(shù)的代碼(主要基于AWS提供的示例代碼)如下所示:
from __future__ import print_function import re import urllib2 import base64 import json import os def lambda_handler(event, context): print("Client token (provided): " + event['authorizationToken']) clientAuthorizationToken = re.sub('^%s' % 'Bearer', '', re.sub('^%s' % 'bearer', '', event['authorizationToken'])).strip() print("Client token (parsed): " + clientAuthorizationToken) print("Method ARN: " + event['methodArn']) url = os.environ['CHECK_TOKEN_ENDPOINT'] + "?token=" + clientAuthorizationToken print("Check token URL: " + url) authorizationHeader = 'Basic %s' % base64.b64encode(os.environ['CHECK_TOKEN_ENDPOINT_CLIENT_ID'] + ':' + os.environ['CHECK_TOKEN_ENDPOINT_CLIENT_SECRET']) print("Our authorization header: " + authorizationHeader) tmp = event['methodArn'].split(':') apiGatewayArnTmp = tmp[5].split('/') awsAccountId = tmp[4] policy = AuthPolicy('urn:user:unknown', awsAccountId) policy.restApiId = apiGatewayArnTmp[0] policy.region = tmp[3] policy.stage = apiGatewayArnTmp[1] request = urllib2.Request(url, headers={"Authorization": authorizationHeader}) try: result = urllib2.urlopen(request) data = json.load(result) print("HTTP Response data: " + str(data)) context = { 'userUrn': data['user_urn'] if data.has_key('user_urn') else None, 'clientId': data['client_id'] } policy.principalId = data['user_urn'] if data.has_key('user_urn') else 'urn:client:%s' % data['client_id'] policy.allowMethod('*', '*') print('Allowing resource %s. Client: %s, User: %s, Principal: %s' % (policy.allowMethods[0]['resourceArn'], context['clientId'], context['userUrn'], policy.principalId)) except urllib2.HTTPError, e: print("Error during the HTTP call: %s" % e) policy.denyAllMethods() context = {} authResponse = policy.build() authResponse['context'] = context return authResponse class HttpVerb: GET = 'GET' POST = 'POST' PUT = 'PUT' PATCH = 'PATCH' HEAD = 'HEAD' DELETE = 'DELETE' OPTIONS = 'OPTIONS' ALL = '*' class AuthPolicy(object): awsAccountId = '' principalId = '' version = '2012-10-17' pathRegex = '^[/.a-zA-Z0-9-\*]+$' allowMethods = [] denyMethods = [] restApiId = '*' region = '*' stage = '*' def __init__(self, principal, awsAccountId): self.awsAccountId = awsAccountId self.principalId = principal self.allowMethods = [] self.denyMethods = [] def _addMethod(self, effect, verb, resource, conditions): if verb != '*' and not hasattr(HttpVerb, verb): raise NameError('Invalid HTTP verb ' + verb + '. Allowed verbs in HttpVerb class') resourcePattern = re.compile(self.pathRegex) if not resourcePattern.match(resource): raise NameError('Invalid resource path: ' + resource + '. Path should match ' + self.pathRegex) if resource[:1] == '/': resource = resource[1:] resourceArn = 'arn:aws:execute-api:{}:{}:{}/{}/{}/{}'.format(self.region, self.awsAccountId, self.restApiId, self.stage, verb, resource) if effect.lower() == 'allow': self.allowMethods.append({ 'resourceArn': resourceArn, 'conditions': conditions }) elif effect.lower() == 'deny': self.denyMethods.append({ 'resourceArn': resourceArn, 'conditions': conditions }) def _getEmptyStatement(self, effect): statement = { 'Action': 'execute-api:Invoke', 'Effect': effect[:1].upper() + effect[1:].lower(), 'Resource': [] } return statement def _getStatementForEffect(self, effect, methods): statements = [] if len(methods) > 0: statement = self._getEmptyStatement(effect) for curMethod in methods: if curMethod['conditions'] is None or len(curMethod['conditions']) == 0: statement['Resource'].append(curMethod['resourceArn']) else: conditionalStatement = self._getEmptyStatement(effect) conditionalStatement['Resource'].append(curMethod['resourceArn']) conditionalStatement['Condition'] = curMethod['conditions'] statements.append(conditionalStatement) if statement['Resource']: statements.append(statement) return statements def allowAllMethods(self): self._addMethod('Allow', HttpVerb.ALL, '*', []) def denyAllMethods(self): self._addMethod('Deny', HttpVerb.ALL, '*', []) def allowMethod(self, verb, resource): self._addMethod('Allow', verb, resource, []) def denyMethod(self, verb, resource): self._addMethod('Deny', verb, resource, []) def allowMethodWithConditions(self, verb, resource, conditions): self._addMethod('Allow', verb, resource, conditions) def denyMethodWithConditions(self, verb, resource, conditions): self._addMethod('Deny', verb, resource, conditions) def build(self): if ((self.allowMethods is None or len(self.allowMethods) == 0) and (self.denyMethods is None or len(self.denyMethods) == 0)): raise NameError('No statements defined for the policy') policy = { 'principalId': self.principalId, 'policyDocument': { 'Version': self.version, 'Statement': [] } } policy['policyDocument']['Statement'].extend(self._getStatementForEffect('Allow', self.allowMethods)) policy['policyDocument']['Statement'].extend(self._getStatementForEffect('Deny', self.denyMethods)) return policy網(wǎng)關(guān)配置
創(chuàng)建Lambda函數(shù)之后,該配置網(wǎng)關(guān)了。 您可以在AWS控制臺中或使用CloudFormation模板執(zhí)行此操作。 我們不會詳細解釋如何配置API網(wǎng)關(guān),因為這是AWS站點上記錄良好的任務(wù) 。 但是,我將解釋一些配置授權(quán)者的細節(jié)。
授權(quán)人
在“ API網(wǎng)關(guān)配置”部分中的左側(cè),您會看到“授權(quán)者”選項。 您可以在那里選擇創(chuàng)建新的授權(quán)者。 當(dāng)您單擊按鈕時,您將看到以下表格:
重要事項:
- Lambda函數(shù):選擇之前創(chuàng)建的授權(quán)者Lambda
- Lamba事件有效負載:令牌
- 令牌來源:授權(quán)(如果您的客戶端使用“授權(quán)”標(biāo)頭發(fā)送令牌)
- 授權(quán)緩存:已啟用
資源資源
接下來,我們轉(zhuǎn)到您要保護的方法。 單擊左側(cè)的資源,然后在列表中選擇一種方法。 您應(yīng)該看到類似于以下屏幕的屏幕:
點擊“方法請求”。 然后,您可以在頂部配置為使用之前添加的授權(quán)者。
返回上一個屏幕,然后單擊“集成請求”。 在底部,我們將配置一些要發(fā)送到API的標(biāo)頭。 這些包含有關(guān)用戶的信息,我們將在API中使用這些信息來了解誰在發(fā)出請求。 注意:我們不必擔(dān)心惡意用戶在請求中發(fā)送這些標(biāo)頭。 我們的自定義授權(quán)者的結(jié)果將覆蓋它們。
未來
雖然我們當(dāng)前的實施在生產(chǎn)中運行良好,但我們始終在尋找有關(guān)如何改進產(chǎn)品以及由此向客戶提供服務(wù)的想法。 我們將繼續(xù)關(guān)注的事情之一是,有一天開始使用JWT令牌,這很可能與API Gateway結(jié)合使用。 這將使設(shè)置更加容易,但是將需要對某些應(yīng)用程序進行更改,而這是我們目前無法做到的。
此外,我們確實對如何從API網(wǎng)關(guān)中獲取更多信息有一些想法。 我們對每個應(yīng)用程序和每個用戶的速率限制非常感興趣。 我們希望能夠以這種方式配置移動應(yīng)用程序,例如,僅允許每小時執(zhí)行一百個請求,或者僅允許某個最終用戶少量請求。
將API Gateway與AWS Lambda結(jié)合使用是一種相對簡單的方法,可以向您的應(yīng)用程序添加可靠的身份驗證方法,而不會中斷其他服務(wù)。
翻譯自: https://www.javacodegeeks.com/2018/11/api-gateway-aws-lambda-authentication.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的API网关和AWS Lambda进行身份验证的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑主板上的那些接口电脑主板有哪些接口
- 下一篇: 关闭电脑弹窗广告的几种方法如何屏蔽电脑弹