卡帝雅家具,食人大蛇王,欧阳妮妮比基尼照
authorization code 授权码模式:认证服务返回授权码,后端用clientid和密钥向认证服务证明身份,使用授权码换取id token 和/或 access token。本模式的好处是由后端请求token,不会将敏感信息暴露在浏览器。本模式允许使用refreshtoken去维持长时间的登录状态。使用此模式的客户端必须有后端参与,能够保障客户端密钥的安全性。此模式从authorization接口获取授权码,从token接口获取令牌。
implict 简化模式:校验跳转uri验证客户端身份之后,直接发放token。通常用于纯客户端应用,如单页应用javascript客户端。因为没有后端参与,密钥存放在前端是不安全的。由于安全校验较宽松,本模式不允许使用refreshtoken来长时间维持登录状态。本模式的所有token从authorization接口获取。
hybrid 混合流程:混合流程顾名思义组合使用了授权码模式+简化模式。前端请求授权服务器返回授权码+id_token,这样前端立刻可以使用用户的基本信息;后续请求后端使用授权码+客户端密钥获取access_token。本模式能够使用refreshtoken来长时间维持登录状态。使用本模式必须有后端参与保证客户端密钥的安全性。混合模式极少使用,除非你的确需要使用它的某些特性(如一次请求获取授权码和用户资料),一般最常见的还是授权码模式。
resource owner password credential 用户名密码模式:一般用于无用户交互场景,或者第三方对接(如对接微信登录,实际登录界面就变成了微信的界面,如果不希望让客户扫了微信之后再跑你们系统登录一遍,就可以在后端用此模式静默登录接上自家的sso即可)
client credential 客户端密钥模式:仅需要约定密钥,仅用于完全信任的内部系统
特点 | 授权码模式 | 简化模式 | 混合模式 |
---|---|---|---|
所有token从authorization接口返回 | no | yes | yes |
所有token从token接口返回 | yes | no | no |
所有tokens不暴露在浏览器 | yes | no | no |
能够验证客户端密钥 | yes | no | yes |
能够使用刷新令牌 | yes | no | yes |
仅需一次请求 | no | yes | no |
大部分请求由后端进行 | yes | no | 可变 |
返回类型 | 认证模式 | 说明 |
---|---|---|
code | authorization code flow | 仅返回授权码 |
id_token | implicit flow | 返回身份令牌 |
id_token token | implicit flow | 返回身份令牌、通行令牌 |
code id_token | hybrid flow | 返回授权码、身份令牌 |
code token | hybrid flow | 返回授权码、通行令牌 |
code id_token token | hybrid flow | 返回授权码、身份令牌、通行令牌 |
相对来说,授权码模式还是用的最多的,我们详细解读一下本模式的协议内容。
认证接口必须同时支持get和post两种请求方式。如果使用get方法,客户端必须使用uri query传递参数,如果使用post方法,客户端必须使用form传递参数。
http/1.1 302 found location: https://server.example.com/authorize? response_type=code &scope=openid%20profile%20email &client_id=s6bhdrkqt3 &state=af0ifjsldkj &redirect_uri=https%3a%2f%2fclient.example.org%2fcb
认证服务必须想办法防止过程中的跨站伪造攻击和点击劫持攻击。
终端用户通过认证之后,认证服务必须与终端用户交互,询问用户是否同意对客户端的授权。
使用 application/x-www-form-urlencoded格式返回结果
例如:
http/1.1 302 found location: https://client.example.org/cb? code=splxlobezqqybys6wxsbia &state=af0ifjsldkj
错误代码包括这些
oauth2.0定义的响应代码
例如:
http/1.1 302 found location: https://client.example.org/cb? error=invalid_request &error_description= unsupported%20response_type%20value &state=af0ifjsldkj
协议规定客户端必须校验授权码的正确性
从authorizeendpoint的processasync方法作为入口开始认证接口的源码解析。
public override async task<iendpointresult> processasync(httpcontext context) { logger.logdebug("start authorize request"); namevaluecollection values; if (httpmethods.isget(context.request.method)) { values = context.request.query.asnamevaluecollection(); } else if (httpmethods.ispost(context.request.method)) { if (!context.request.hasformcontenttype) { return new statuscoderesult(httpstatuscode.unsupportedmediatype); } values = context.request.form.asnamevaluecollection(); } else { return new statuscoderesult(httpstatuscode.methodnotallowed); } var user = await usersession.getuserasync(); var result = await processauthorizerequestasync(values, user, null); logger.logtrace("end authorize request. result type: {0}", result?.gettype().tostring() ?? "-none-"); return result; }
认证站点如果cookie中存在当前会话信息,则直接返回用户信息,否则调用cookie架构的认证方法,会跳转到登录页面。
public virtual async task<claimsprincipal> getuserasync() { await authenticateasync(); return principal; } protected virtual async task authenticateasync() { if (principal == null || properties == null) { var scheme = await getcookieschemeasync(); var handler = await handlers.gethandlerasync(httpcontext, scheme); if (handler == null) { throw new invalidoperationexception($"no authentication handler is configured to authenticate for the scheme: {scheme}"); } var result = await handler.authenticateasync(); if (result != null && result.succeeded) { principal = result.principal; properties = result.properties; } } }
认证请求处理流程大致分为三步
internal async task<iendpointresult> processauthorizerequestasync(namevaluecollection parameters, claimsprincipal user, consentresponse consent) { if (user != null) { logger.logdebug("user in authorize request: {subjectid}", user.getsubjectid()); } else { logger.logdebug("no user present in authorize request"); } // validate request var result = await _validator.validateasync(parameters, user); if (result.iserror) { return await createerrorresultasync( "request validation failed", result.validatedrequest, result.error, result.errordescription); } var request = result.validatedrequest; logrequest(request); // determine user interaction var interactionresult = await _interactiongenerator.processinteractionasync(request, consent); if (interactionresult.iserror) { return await createerrorresultasync("interaction generator error", request, interactionresult.error, interactionresult.errordescription, false); } if (interactionresult.islogin) { return new loginpageresult(request); } if (interactionresult.isconsent) { return new consentpageresult(request); } if (interactionresult.isredirect) { return new customredirectresult(request, interactionresult.redirecturl); } var response = await _authorizeresponsegenerator.createresponseasync(request); await raiseresponseeventasync(response); logresponse(response); return new authorizeresult(response); }
此处只有authorizationcode、implicit、hybrid三种授权类型的判断,用户名密码、客户端密钥模式不能使用authorize接口。
public virtual async task<authorizeresponse> createresponseasync(validatedauthorizerequest request) { if (request.granttype == granttype.authorizationcode) { return await createcodeflowresponseasync(request); } if (request.granttype == granttype.implicit) { return await createimplicitflowresponseasync(request); } if (request.granttype == granttype.hybrid) { return await createhybridflowresponseasync(request); } logger.logerror("unsupported grant type: " + request.granttype); throw new invalidoperationexception("invalid grant type: " + request.granttype); }
protected virtual async task<authorizeresponse> createcodeflowresponseasync(validatedauthorizerequest request) { logger.logdebug("creating authorization code flow response."); var code = await createcodeasync(request); var id = await authorizationcodestore.storeauthorizationcodeasync(code); var response = new authorizeresponse { request = request, code = id, sessionstate = request.generatesessionstatevalue() }; return response; } protected virtual async task<authorizationcode> createcodeasync(validatedauthorizerequest request) { string statehash = null; if (request.state.ispresent()) { var credential = await keymaterialservice.getsigningcredentialsasync(); if (credential == null) { throw new invalidoperationexception("no signing credential is configured."); } var algorithm = credential.algorithm; statehash = cryptohelper.createhashclaimvalue(request.state, algorithm); } var code = new authorizationcode { creationtime = clock.utcnow.utcdatetime, clientid = request.client.clientid, lifetime = request.client.authorizationcodelifetime, subject = request.subject, sessionid = request.sessionid, codechallenge = request.codechallenge.sha256(), codechallengemethod = request.codechallengemethod, isopenid = request.isopenidrequest, requestedscopes = request.validatedscopes.grantedresources.toscopenames(), redirecturi = request.redirecturi, nonce = request.nonce, statehash = statehash, wasconsentshown = request.wasconsentshown }; return code; }
302 https://mysite.com?code=xxxxx&state=xxx
<html> <head> <meta http-equiv='x-ua-compatible' content='ie=edge' /> <base target='_self'/> </head> <body> <form method='post' action='https://mysite.com'> <input type='hidden' name='code' value='xxx' /> <input type='hidden' name='state' value='xxx' /> <noscript> <button>click to continue</button> </noscript> </form> <script>window.addeventlistener('load', function(){document.forms[0].submit();});</script> </body> </html>
private async task renderauthorizeresponseasync(httpcontext context) { if (response.request.responsemode == oidcconstants.responsemodes.query || response.request.responsemode == oidcconstants.responsemodes.fragment) { context.response.setnocache(); context.response.redirect(buildredirecturi()); } else if (response.request.responsemode == oidcconstants.responsemodes.formpost) { context.response.setnocache(); addsecurityheaders(context); await context.response.writehtmlasync(getformposthtml()); } else { //_logger.logerror("unsupported response mode."); throw new invalidoperationexception("unsupported response mode"); } }
客户端在回调地址接收code,即可向token接口换取token。
简单看一下简化流程和混合流程是怎么创建返回报文的。
可以看到,简化流程的所有token都是由authorization接口返回的,一次请求返回所有token。
protected virtual async task<authorizeresponse> createimplicitflowresponseasync(validatedauthorizerequest request, string authorizationcode = null) { logger.logdebug("creating implicit flow response."); string accesstokenvalue = null; int accesstokenlifetime = 0; var responsetypes = request.responsetype.fromspaceseparatedstring(); if (responsetypes.contains(oidcconstants.responsetypes.token)) { var tokenrequest = new tokencreationrequest { subject = request.subject, resources = request.validatedscopes.grantedresources, validatedrequest = request }; var accesstoken = await tokenservice.createaccesstokenasync(tokenrequest); accesstokenlifetime = accesstoken.lifetime; accesstokenvalue = await tokenservice.createsecuritytokenasync(accesstoken); } string jwt = null; if (responsetypes.contains(oidcconstants.responsetypes.idtoken)) { string statehash = null; if (request.state.ispresent()) { var credential = await keymaterialservice.getsigningcredentialsasync(); if (credential == null) { throw new invalidoperationexception("no signing credential is configured."); } var algorithm = credential.algorithm; statehash = cryptohelper.createhashclaimvalue(request.state, algorithm); } var tokenrequest = new tokencreationrequest { validatedrequest = request, subject = request.subject, resources = request.validatedscopes.grantedresources, nonce = request.raw.get(oidcconstants.authorizerequest.nonce), includeallidentityclaims = !request.accesstokenrequested, accesstokentohash = accesstokenvalue, authorizationcodetohash = authorizationcode, statehash = statehash }; var idtoken = await tokenservice.createidentitytokenasync(tokenrequest); jwt = await tokenservice.createsecuritytokenasync(idtoken); } var response = new authorizeresponse { request = request, accesstoken = accesstokenvalue, accesstokenlifetime = accesstokenlifetime, identitytoken = jwt, sessionstate = request.generatesessionstatevalue() }; return response; }
这段代码充分体现了它为啥叫混合流程,把生成授权码的方法调一遍,再把简化流程的方法调一遍,code和token可以一起返回。
protected virtual async task<authorizeresponse> createhybridflowresponseasync(validatedauthorizerequest request) { logger.logdebug("creating hybrid flow response."); var code = await createcodeasync(request); var id = await authorizationcodestore.storeauthorizationcodeasync(code); var response = await createimplicitflowresponseasync(request, id); response.code = id; return response; }
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
Net Core Web Api项目与在NginX下发布的方法
asp.net core3.1 引用的元包dll版本兼容性问题解决方案
IdentityServer4实现.Net Core API接口权限认证(快速入门)
ASP.NET Core MVC通过IViewLocationExpander扩展视图搜索路径的实现
网友评论