asp.net core系列 57 IS4 使用混合流(OIDC+OAuth2.0)添加API访问
一.概述
在上篇中,探讨了交互式用户身份验证,使用的是oidc协议。 在之前篇中对api访问使用的是oauth2.0协议。这篇把这两个部分放在一起,openid connect和oauth 2.0组合的优点在于:可以使用单个协议和令牌服务,进行单次交换来实现这两者。
上篇中使用了openid connect隐式流程。在隐式流程中,所有令牌都通过浏览器传输,这对于身份令牌来说是完全正确的。现在我们还想要一个访问令牌。
访问令牌比身份令牌更敏感,如果不需要,我们不希望将它们暴露给“外部”世界。openid connect包含一个名为“hybrid”的流程,它为我们提供了两全其美的优势,身份令牌通过浏览器渠道传输,因此客户端访问api时先进行身份验证。如果验证成功,客户端会打开令牌服务的反向通道以检索访问令牌。
从github中下载开源项目。该示例是在上篇示例的基础之上,做的一点修改。涉及到三个项目:identityserver、api、mvcclient。
二. identityserver 项目
1.1 定义客户端配置
允许客户端使用混合流hybrid,添加一个客户端密钥clientsecrets ,这将用于检索反向通道上的访问令牌。最后添加客户端访问offline_access范围 -这允许请求刷新令牌来进行长时间的api访问:
public static ienumerable<client> getclients() { return new list<client> { new client { clientid = "client", // no interactive user, use the clientid/secret for authentication allowedgranttypes = granttypes.clientcredentials, // secret for authentication clientsecrets = { new secret("secret".sha256()) }, // scopes that client has access to allowedscopes = { "api1" } }, // resource owner password grant client new client { clientid = "ro.client", allowedgranttypes = granttypes.resourceownerpassword, clientsecrets = { new secret("secret".sha256()) }, allowedscopes = { "api1" } }, // openid connect hybrid flow client (mvc) new client { clientid = "mvc", clientname = "mvc client", //混合流 allowedgranttypes = granttypes.hybrid, //添加客户端密钥 clientsecrets = { new secret("secret".sha256()) }, redirecturis = { "http://localhost:5002/signin-oidc" }, postlogoutredirecturis = { "http://localhost:5002/signout-callback-oidc" }, allowedscopes = { identityserverconstants.standardscopes.openid, identityserverconstants.standardscopes.profile, //api访问范围 "api1" }, //刷新令牌来进行长时间的api访问 allowofflineaccess = true } }; }
三. api项目
api项目没有变动,可以考参上面的开源地址。也可以查看54篇。
四. mvcclient客户端
4.1 启动类配置
在启动类startup. configureservices方法中,配置clientsecret匹配identityserver的secret。 添加offline_access和api1范围。并设置responsetype为code id_token,意味着“使用混合流”。 将website 声明保留在我们的mvc客户端标识中,需要使用claimactions显示映射声明。
public void configureservices(iservicecollection services) { services.addmvc(); jwtsecuritytokenhandler.defaultinboundclaimtypemap.clear(); services.addauthentication(options => { options.defaultscheme = "cookies"; options.defaultchallengescheme = "oidc"; }) .addcookie("cookies") .addopenidconnect("oidc", options => { options.signinscheme = "cookies"; //若不设置authority,就必须指定metadataaddress options.authority = "http://localhost:5000"; options.requirehttpsmetadata = false; //客户端标识id options.clientid = "mvc"; //匹配identityserver的secret options.clientsecret = "secret"; /* responsetype:oauth 2.0响应类型值,一次请求中可以同时获取code和id token,使用的是混合流hybrid flow, 也可以使用openidconnectresponsetype枚举。 code:授权代码。当使用混合流时,总是返回这个值。 id_token:标识牌 下面是一个使用混合流响应示例: http / 1.1 302 found location: https://client.example.org/cb# code = splxlobezqqybys6wxsbia & id_token = eyj0...nij9.eyj1c...i6ijiifx0.dewt4qu...zxso & state = af0ifjsldkj */ options.responsetype = "code id_token"; //是否将tokens保存到authenticationproperties中,最终到浏览器cookie中 options.savetokens = true; //是否从userinfoendpoint获取claims options.getclaimsfromuserinfoendpoint = true; //添加资源范围,访问api options.scope.add("api1"); //离线访问,此范围值请求发出oauth 2.0刷新令牌,该令牌可用于获取访问令牌, //该令牌授予对最终用户的userinfo端点的访问权,即使最终用户不存在(未登录)。 options.scope.add("offline_access"); //收集claims options.claimactions.mapjsonkey("website", "website"); }); }
configure方法配置不变。
4.2 使用访问令牌
在上面配置的openid connect处理程序,会自动为我们保存令牌(在本案例中为identity身份,access 访问和refresh 刷新)。这就是savetokens设置的作用。令牌存储在cookie的properties部分中。访问它们的最简单方法是使用microsoft.aspnetcore.authentication命名空间中的扩展方法(gettokenasync)。
//例如: var accesstoken = await httpcontext.gettokenasync("access_token") var refreshtoken = await httpcontext.gettokenasync("refresh_token");
//下面方法home/callapi调用受保护的api,先获取访问令牌,再使用访问令牌调用api。 public async task<iactionresult> callapi() { //获取访问令牌 var accesstoken = await httpcontext.gettokenasync("access_token"); var client = new httpclient(); client.defaultrequestheaders.authorization = new authenticationheadervalue("bearer", accesstoken); var content = await client.getstringasync("http://localhost:5001/identity"); viewbag.json = jarray.parse(content).tostring(); return view("json"); }
五. 测试
(1) 启动identityserver程序http://localhost:5000
(2) 启动api程序http://localhost:5001。这二个程序属于服务端
(3) 启动客户端mvcclient程序http://localhost:5002
(4) 用户点击secure,开始握手授权,重定向到identityserver站点的登录页
[authorize] public iactionresult secure() { viewdata["message"] = "secure page."; return view(); }
(5) 输入用户的用户名和密码,登录成功。跳转到identityserver站点consent同意页面
上面的应用程序访问权限:myapi和offline access 是在客户端程序中配置的:
options.scope.add("api1"); options.scope.add("offline_access");
(6) 点击 yes allow后,跳回到客户端站点http://localhost:5002/home/secure,完成了交互式身份认证。
(7) 调用http://localhost:5002/home/callapi,获取访问令牌,请求受保护的api资源。调用callapi 时,是访问的api站点http://localhost:5001/identity。
参考文献