亚马逊网络服务(AWS)虽然作为全球市场份额最大的云计算厂商,其产品并不完美,但Cognito(AWS的身份认证解决方案)及其配套的中文文档是一个反面教材,难度大到离谱。当然,除了难用之外,还有访问速度慢、不适用于中国市场等问题。国内的Authing可以解决很多使用Cognito的问题。先来看看Authing的介绍:Authing是一家身份认证服务商,提供企业级身份认证和管理解决方案,客户分布教育,物联网,互联网和电子商务等多个行业。Lambda是AWS提供的功能即服务(FaaS)平台。Lambda和AWS生态系统紧密结合。接入Lambda后,开发者可以使用AWS生态中的所有资源。比如我们可以创建一个Lambda函数让用户通过Cognito登录(当然本文是让用户使用Authing登录),然后调用另一个Lambda函数可以上传文件到S3(AWS存储服务)).这类平台(现在多被称为Serverless,无服务器架构)的好处之一是可以让开发者专注于业务研发,而不用担心基础设施。FaaS或Serverless平台正在逐渐获得市场关注,因为这种类型的平台可以让开发人员不再关注基础设施。《什么是无服务器》这篇文章详细解释了什么是“无服务器计算”以及“无服务器计算”的好处。推荐阅读。本文的主要目的是介绍如何使用Authing+Lambda代替AWSCogito,点这里体验最终的demo。另外Authing遵循OIDC规范,所以本文将使用OIDC进行认证。如果您不知道OIDC是什么,请查看这篇文章。首先确认用户的操作流程:打开页面:https://sample.authing.cn/aws/;点击Login进行登录,然后跳转到Authing(应用的二级域名)的登录页面;输入账号密码登录登录,如果没有账号密码,请先注册;登录成功后,返回第一步打开的页面,显示已登录用户的头像;此时用户可以看到AWSLambda请求的Private信息;最终效果如下图所示:点击此处体验DEMO,创建Authing应用。如果您还没有注册Authing,请点击这里注册。注册后,按照以下步骤创建一个Authing应用程序。创建应用填写基本信息,选择应用类型Web应用创建完成后,会进入应用首页(空)创建OIDC应用创建应用后,有一个用户池,然后可以创建一个OIDC应用程序来授权其他程序(由您自己编写或其他第三方程序)访问您的用户池。如果你还不清楚什么是OIDC,请参考这篇文章。点击“第三方登录”开始创建OIDC应用选择“OIDC应用”选项卡,点击“创建OIDC应用”填写应用名称和认证地址,并勾选id_tokentoken这里说明一下,认证地址时创建OIDC应用会由Authing提供生成一个二级域名(支持HTTPS),不能重复。回调地址可以填写自己的回调地址。这里我使用的是https://authing.cn。注意OIDC协议不允许回调URL为localhost,使用代理工具调试。点击OK,可以看到我们有了第一个基于OIDC协议的授权应用。创建完成后,可以访问lambda.authing.cn。这时候你会看到报错了。不要害怕,这是因为我们发起的授权链接不正确。请继续往下看如何发起正确的授权请求。发起授权请求类似于大多数OAuth应用程序。OIDC授权链接也需要拼接(如果你开发过微信应用,应该很容易看懂)。AuthingOIDC应用的授权链接符合标准规范。具体格式为:https://lambda.authing.cn/oau...;ApplicationID>&redirect_uri=<回调URL,必须与平台配置完全一致>&scope=openidprofile&response_type=&state=<一个随机字符串,用于防止CSRF攻击>如需查看详细参数,请点此查看。例如:https://lambda.authing.cn/oau...://authing.cn&scope=openidprofile&response_type=id_tokentoken&state=jacket为了简单起见,这里我们的response_type设置为“id_tokentoken”,所以不需要使用“code”换取token,token会直接附在回调地址上。如果你的授权链接正确,你应该会看到如上图所示的登录窗口,这个窗口也是使用的窗口你的终端用户会从这里登录,然后回调到你配置的回调地址,你可以尝试注册一个账号,然后登录,登录完成后,可以在控制台观察登录状态。注册登录成功,在授权页面的控制台观察到的用户数据,应该会回调到你登录成功后填写的url中,并且附带了很多参数,下面我们将讲解这些参数的使用方法.获取到用户信息后回调到控制台配置的redirect_uri,会附加如下信息:{"id_token":"JWT_TOKEN","access_token":"JWT_TOKEN","expires_in":"3600","token_type":"Bearer","state":"jacket","session_state":"644d7b324ba61d517fdedd28b5b6e365d78f2a8178f2ee742474d5b57a99eb3f"}可以看到里面包含了access_token和id_token,access_token可以帮助你从_Authing后端获取用户的基本信息如果要获取用户头像,需要通过access_token获取。我们先看一个id_token的例子:eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InIxTGtiQm8zOTI1UmIyWkZGckt5VTNNVmV4OVQyODE3S3gwdmJpNmlfS2MifQ.eyJzdWIiOiI1Y2MyYTg1MTFiYmFmMDRmOTNjZTQ4OWYiLCJub25jZSI6IjE4MzEyODkiLCJzaWQiOiI5MzkwZDA1ZC01ZTM3LTQ3ZWUtODJjNi1jNTQ1ZjA2ODhhMDAiLCJhdF9oYXNoIjoiNmxZMGRXajZYUTY0aExWdHAtR2tEdyIsInNfaGFzaCI6IlZVOU5QYV9JQ0VTSEdxRmxUZ3A2LUEiLCJhdWQiOiI1Y2MyYjU0OGQxNGM3NDJkYjg5M2JhNTUiLCJleHAiOjE1NTYzNjY0ODksImlhdCI6MTU1NjM2Mjg4OSwiaXNzIjoiaHR0cHM6Ly9vYXV0aC5hdXRoaW5nLmNuL29hdXRoL29pZGMifQ.Qc_OMqMf6_wwzW2SsEgEtiaGr3ZY1FWHnRrMU2M7LADGlNpq_pvPrFxAVsR2j-BFr1y48M-Trvq6yAu4_ZOUBHPtIIpoQ5W2bnABytUV693ZcwNlf9CCiLc-k0LG3o1U-BmiH3L6NAV7aKGsfVHS8toiNbVDuimPVdYJsRrF2C1jj1meM1K8FBVwqozXm6YtB--u3sqY4IszHnd5PMEWguLsOkpZJIh7xWeYPpVQ5WKfx0cA8rB_T2puSCbeaUVhgIwNADy06qBqXhUOiA4gdcNbHtx7tvGZMxzMC3rdjpXoZk89Duh3O5tHlMtaBlidJGYavUSjVl7potESecSlBg使用jwt.io解析后将得到如下结果:{"sub":"5cc2a8511bbaf04f93ce489f","nonce":"1831289","sid":"9390d05d-5e37-47ee-82c6-c545f0688a00","at_hash":"6lY0dWj6XQ64hLVtp-GkDw","s_hash":"VU9NPa_ICESHGqFlTgp6-A","aud":"5cc2b548d14c742db893ba55","exp":1556366489,"iat":1559636iss.28//oauth/oidc"}其中包含颁发时间(iat)、过期时间(exp)等字段,可以用来判断用户是否已经通过认证。在OIDC规范中,JWT使用OIDC应用的秘密发行需要开发者在后端验证后继续执行开发者自己的业务流程(这一步我们会在Lambda中执行)再来看看access_token的例子:eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InIxTGtiQm8zOTI1UmIyWkZGckt5VTNNVmV4OVQyODE3S3gwdmJpNmlfS2MifQ.eyJqdGkiOiJza0p-bTNaYmZsTjVxVGEzR2J2YlMiLCJzdWIiOiI1Y2MyYTg1MTFiYmFmMDRmOTNjZTQ4OWYiLCJpc3MiOiJodHRwczovL29hdXRoLmF1dGhpbmcuY24vb2F1dGgvb2lkYyIsImlhdCI6MTU1NjM2Mjg4OSwiZXhwIjoxNTU2MzY2NDg5LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIiwiYXVkIjoiNWNjMmI1NDhkMTRjNzQyZGI4OTNiYTU1In0.Uf3YK4D9HL-G71hkA4cWt5kitDo5rNgwVA9Vqlv4RjAILNDTylYWtkacKJpLcOSS81ivaNpDVNYYzBSoyN-eMH80VhArPUre74F9SHdonA-IVFVPT0DHRtOAJI9kqDW4tgTXhZeZMUm-MCjVjR-q8XrayXaqrC5Hu5W3D1N-K_jZOlwxzIBf51nuC4NMvSI_wPpYj2WPzGxFwpfTCEbnhj5RO0CcThRpC3EdmpbtcJqStd7AZQhkLyTb1TQLHJOel8DSxLnLnoIU0rZXsodK6EjE_oqRLagetNXF1cKfRmnGFaAKZKqgvHc527S_CVkgXIwcHBRmDeqo93CCId_hmQ使用jwt.io解析后将得到如下结果:{"jti":"skJ~m3ZbflN5qTa3GbvbS","sub":"5cc2a8511bbaf04f93ce489f","iss":"https://oauth.authing.cn/oauth/oidc","iat":1556362889,"exp":1556366489,"scope":"openidprofile","aud":"5cc2b548d14c742db893ba55"}可以看到access_token的信息比id_token少很多。这里有英文介绍,解释了access_token和id_token的区别:IDTokensvsAccessTokens。IDToken是OpenIDProvider授予的安全令牌包含有关最终用户的信息。此信息告诉您的客户端应用程序该用户已通过身份验证,还可以为您提供他们的用户名或区域设置等信息。您可以在客户端的不同组件周围传递ID令牌,这些组件可以使用ID令牌来确认用户已通过身份验证并检索有关他们的信息。另一方面,访问令牌并不打算携带有关用户的信息。它们只是允许访问某些已定义的服务器资源。有关更多讨论什么时候使用accesstoken可以在ValidatingAccessTokens中找到。简单的说,id_token告诉你用户已经通过验证,access_token是你可以访问资源服务器的凭证(这里是Authing)你还可以看到,idtoken包含的信息较少,如果想获取更多信息,需要使用access_token获取。获取方法也很简单,用access_token向以下链接发送GET请求即可,如:$curlhttps://users.authing.cn/oauth/oidc/user/userinfo?access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InIxTGtiQm8zOTI1UmIyNyWkZG...。.verylong...可以获取到id等信息,获取到id后,可以将id存储到自己的数据库中,完成自己的实际业务。{"sub":"5cc2a8511bbaf04f93ce489f","nickname":"","picture":"https://usercontents.authing.cn/authing-avatar.png"}上面的json是用access_token返回的用户数据结果。好了,现在我们已经拿到了Token,接下来我们需要在Lambda中验证Token的合法性,并在前端展示不同的信息。编写Lambda函数建议使用ServerlessCLI编写Lambda函数。在AWS控制台中编写函数是一件很痛苦的事情。同时,您可以到这里查看完整代码。Lambda在本文中主要用来做三件事:对id_token进行鉴权,获取用户是否已经通过鉴权;提供PublicAPI,可以直接访问;提供一个PrivateAPI,需要认证访问;验证id_token要验证id_token,您首先需要知道OIDC应用程序的秘密。这个值可以在Authing控制台的OIDC应用详情中找到:请妥善保管这个值,以免id_token签发给任何人时泄露签名这个是秘密的,所以可以直接使用jsonwebtoken库JavaScript验证id_token的合法性(详见:VerifyTokenlegitimacy)。在控制台中安装jsonwebtoken:$npminstalljsonwebtoken--save在lambda中导入包后,会打包上传到AWSLambda运行时。constjwt=require('jsonwebtoken');//Policyhelperfunction//这是AWS提供的模型代码,这里不需要做修改constgeneratePolicy=(principalId,effect,resource)=>{constauthResponse={};authResponse.principalId=principalId;if(effect&&resource){constpolicyDocument={};policyDocument.Version='2012-10-17';policyDocument.Statement=[];conststatementOne={};statementOne.Action='execute-api:Invoke';statementOne.Effect=效果;statementOne.Resource=资源;policyDocument.Statement[0]=statementOne;authResponse.policyDocument=policyDocument;}returnauthResponse;};//可重用授权函数,在serverless.ymlmodule.exports.auth=async(event,context,cb)中的`authorizer`字段上设置=>{if(event.authorizationToken){//remove"bearer"fromtokenconsttoken=event.authorizationToken.substring(7);尝试{letdecoded=jwt.verify(token,'YOUR_OIDC_APP_SECRET'),expired=(Date.parse(newDate())/1000)>decoded.exp;if(expired){cb('未经授权,登录信息已过期。');}else{cb(null,generatePolicy('user','Allow',event.methodArn));}}catch(error){cb('未经授权');}}else{cb('未经授权');}};公共API//PublicAPImodule.exports.publicEndpoint=(event,context,cb)=>{cb(null,{message:'WelcometoourPublicAPI!'});};私有API//PrivateAPImodule.exports.privateEndpoint=(event,context,cb)=>{cb(null,{message:'Onlyloggedinuserscanseethis'});};serverless.ymlservice:serverless-authorizerprovider:name:awsruntime:nodejs8.10functions:auth:handler:handler.authgetUserInfo:handler:handler.getUserInfoevents:-http:path:api/userInfo方法:getintegration:lambdacors:truepublicEndpoint:handler:handler.publicEndpointevents:-http:path:api/publicmethod:getintegration:lambdacors:trueprivateEndpoint:handler:handler.privateEndpointevents:-http:path:api/privatemethod:getintegration:lambdaauthorizer:auth#查看自定义授权方docs在这里:http://bit.ly/2gXw9pOcors:true这个文件可以用来配置需要认证的路由,比如上面代码中的privateEndpoint,authorizer配置为auth函数。单击此处查看完整代码。测试Lambda编写代码后,我们需要对其进行测试。Lambda支持直接本地测试。可以使用如下命令:$slsinvokelocal-fauth--data'{"authorizationToken":"Bearer"}'如果本地测试返回如下信息,则认证成功:{"principalId":"user"}DeployLambda$serverlessdeploy部署完成后会得到三个链接,分别是上面代码的三个功能。红框中的路由定义在serverless.yml中,可以直接映射到函数中。使用curl或者postman将OIDC登录后的id_token携带到header的Authorization中查看结果,如:$curl--header"Authorization:"以上三种路由的结果应该是:curl-应该可以!Public!curl-不应该工作curl--header"Authorization:"-应该工作!授权!最后在我们的前端添加相关信息,点击登录后应该会看到如下信息: