从Client应用场景介绍IdentityServer4(二)
本节介绍client的clientcredentials客户端模式,先看下画的草图:
一、在server上添加动态新增client的api 接口。
为了方便测试,在server服务端中先添加swagger,添加流程可参考:
在valuescontroller控制器中注入configurationdbcontext上下文,此上下文可用来加载或配置identityserver4.entityframework的client、身份信息、api资源信息或cors数据等。
在valuescontroller中实添加以下代码:
private configurationdbcontext _context; public valuescontroller(configurationdbcontext context) { _context = context; }
添加动态新增client的api接口:
[httppost] public iactionresult post([frombody] identityserver4.entityframework.entities.client client) { var res = _context.clients.add(client); if(_context.savechanges() >0) return ok(true); else return ok(false); }
控制器代码如下:
二、对server上的api进行保护
(1)安装identityserver4.accesstokenvalidation包
(2)在startup.cs中configureservices方法添加如下代码:
//protect api services.addmvccore() .addauthorization() .addjsonformatters(); services.addauthentication("bearer") .addidentityserverauthentication(options => { options.authority = "http://localhost:5000"; options.requirehttpsmetadata = false; options.apiname = "api1"; });
addauthentication把bearer配置成默认模式,将身份认证服务添加到di中。
addidentityserverauthentication把identityserver的access token添加到di中,供身份认证服务使用。
(3)在startup.cs中configure方法添加如下代码:
public void configure(iapplicationbuilder app, ihostingenvironment env) { //if (env.isdevelopment()) //{ // app.usedeveloperexceptionpage(); //} //addswagger app.useswagger(); app.useswaggerui(c => { c.swaggerendpoint("/swagger/v1/swagger.json", "server接口文档"); }); initializedatabase(app); app.useauthentication(); app.useidentityserver(); app.usemvc(); }
useauthentication将身份验证中间件添加到管道中,以便在每次调用主机时自动执行身份验证。
(4)在valuescontroller控制器中添加[authorize]
(5)在项目属性->调试 中,启动浏览器,并设成swagger,如图:
(6)启动项目,并调用第一个get接口。
显示unauthorized(未授权),证明[authorize]起作用了。
三、搭建client客户端
(1)新建一个控制台程序,安装identitymodel包。
(2)添加类idshelper.cs,添加客户端请求api接口代码。
public class idshelper { public static async task mainasync() { try { discoveryresponse disco = await discoveryclient.getasync("http://localhost:5000"); if (disco.iserror) { console.writeline(disco.error); return; } tokenclient tokenclient = new tokenclient(disco.tokenendpoint, "client", "secret"); var tokenresponse = await tokenclient.requestclientcredentialsasync("api1"); if (tokenresponse.iserror) { console.writeline(tokenresponse.error); return; } console.writeline(tokenresponse.json); var client = new httpclient(); client.setbearertoken(tokenresponse.accesstoken); var response = await client.getasync("http://localhost:5000/api/values/"); if (!response.issuccessstatuscode) { console.writeline(response.statuscode); } else { var content = await response.content.readasstringasync(); console.writeline(content); } } catch (exception ex) { } } }
(3)修改program.cs代码,如下:
class program { static void main(string[] args) => idshelper.mainasync().getawaiter().getresult(); }
(4)按ctrl+f5,可以获取到access token和接口返回值
复制token,用postman调用,成功获取到了接口返回值。
四、测试动态新增client接口
安装identityserver4包。
安装identityserver4.entityframework包。
在idshelper.cs类中添加post方法:
public static async task post() { try { discoveryresponse disco = await discoveryclient.getasync("http://localhost:5000"); if (disco.iserror) { console.writeline(disco.error); return; } tokenclient tokenclient = new tokenclient(disco.tokenendpoint, "client", "secret"); var tokenresponse = await tokenclient.requestclientcredentialsasync("api1"); if (tokenresponse.iserror) { console.writeline(tokenresponse.error); return; } console.writeline(tokenresponse.json); var client = new httpclient(); client.setbearertoken(tokenresponse.accesstoken); client c1 = new client { clientid = "test", allowedgranttypes = granttypes.clientcredentials, clientsecrets = { new secret("secret".sha256()) }, allowedscopes = { "api1" } }; string strjson = jsonconvert.serializeobject(c1 .toentity()); httpcontent content = new stringcontent(strjson); content.headers.contenttype = new system.net.http.headers.mediatypeheadervalue("application/json"); //由httpclient发出post请求 task<httpresponsemessage> response = client.postasync("http://localhost:5000/api/values/", content); if (response.result.statuscode != system.net.httpstatuscode.ok) { console.writeline(response.result.statuscode); } else { console.writeline(response.result.content.readasstringasync().result); } } catch (exception ex) { } }
顺便把main中改成对post调用:
static void main(string[] args) => idshelper.post().getawaiter().getresult();
按ctrl+f5,调用新增client的接口,并成功返回true。
同时可以在数据库中的client表找到相关记录。需要注意的是,不能添加相同client id的client。
五、在client中添加claim信息,并在api接口中对claim信息进行验证。
关于claim的介绍可以看这篇文章:
这里把claim简单当做用户的身份信息使用,修改post方法里面的client:
client c1 = new client { clientid = "superadmin", allowedgranttypes = granttypes.clientcredentials, clientsecrets = { new secret("secret".sha256()) }, allowedscopes = { "api1" }, claims = new list<claim> { new claim(jwtclaimtypes.role, "admin") } };
可以看出,claims为list,可以是很多个角色,这里只添加一个。
ctrl+f5,运行成功添加superadmin client。
现在,需要对server服务端的新增client接口进行claim身份验证,添加如下代码:
[authorize(roles ="admin")]
然后再客户端修改授权的账号为superadmin。
tokenclient tokenclient = new tokenclient(disco.tokenendpoint, "superadmin", "secret");
ctrl+f5运行
问题出现了,返回了forbidden,没有权限进行访问。
这时候我们上官网查阅了资料,发现在添加client的claim时候,identityserver entityframework会为claim的role添加一个默认前缀,为client_。所以,实际上它为client_role。
而服务端只能对role进行验证。
此时我们需要把claim的默认前缀去掉,设置为空clientclaimsprefix = "" 。
去掉server的role验证,添加形如下面代码的client。
client c1 = new client { clientid = "adminclient", allowedgranttypes = granttypes.clientcredentials, clientsecrets = { new secret("secret".sha256()) }, allowedscopes = { "api1" }, claims = new list<claim> { new claim(jwtclaimtypes.role, "admin") }, clientclaimsprefix = "" //把client_ 前缀去掉 };
ctrl+f5,运行成功添加adminclient client,这次的是role为admin。
然后重新再server服务端加上[authorize(roles ="admin")]
同时修改验证账号为adminclient。
tokenclient tokenclient = new tokenclient(disco.tokenendpoint, "adminclient", "secret");
最后运行程序,成功地在[authorize(roles ="admin")]权限下访问并新增了client。
六、需要注意的问题
(1)新增client到数据库时候,这里需要接收identityserver4.entityframework.entities.client
而不是identityserver4.models.client,否则api接口在接收和转化client模型的时候会报错。
(2)此外,本节介绍的client的allowedgranttypes 都为 granttypes.clientcredentials,相应的,客户端请求是,需要用requestclientcredentialsasync方法。
最后再次提下,clientcredentials模式的适用场景:用于和用户无关,服务与服务之间直接交互访问资源。
server服务端源码地址:https://github.com/bingjian-zhu/server
client客户端源码地址:https://github.com/bingjian-zhu/client
文中如有错漏,欢迎指正。