欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

IdentityServer4 使用用户名和密码保护API访问

程序员文章站 2023-11-14 17:14:58
接上一篇:IdentityServer4 初识,上一篇介绍了客户端模式保护API访问。这一篇讲IdentityServer4 使用用户名和密码模式保护API访问。 添加用户:要用到用户名称密码当然得添加用户,在IdentityServer项目的Config类中的新增一个方法,GetUsers。返回一 ......

接上一篇:identityserver4 初识,上一篇介绍了客户端模式保护api访问。这一篇讲identityserver4 使用用户名和密码模式保护api访问。

  • 添加用户:要用到用户名称密码当然得添加用户,在identityserver项目的config类中的新增一个方法,getusers。返回一个testuser的集合。
    public static list<testuser> getusers() {
            return new list<testuser>()
            {
                new testuser()
                {
                    //用户名
                     username="apiuser",
                     //密码
                     password="apiuserpassword",
                     //用户id
                     subjectid="0"
                }
            };
        }

添加好用户还需要要将用户注册到identityserver4,修改identityserver项目的startup类configureservices方法

 

 public void configureservices(iservicecollection services)
        {
            services.addmvc();
            //添加identityserver
            var builder = services.addidentityserver()
                //身份信息授权资源
                .addinmemoryidentityresources(config.getidentityresources())
                //api访问授权资源
                .addinmemoryapiresources(config.getapis())
                //客户端
                .addinmemoryclients(config.getclients())
                //添加用户
                .addtestusers(config.getusers());
            if (environment.isdevelopment())
            {
                builder.adddevelopersigningcredential();
            }
            else
            {
                throw new exception("need to configure key material");
            }
        }
  • 添加一个客户端,用于用户名和密码模式的访问。客户端(client)定义里有一个allowedgranttypes的属性,这个属性决定了client可以被那种模式被访问,granttypes.clientcredentials为客户端凭证模式,granttypes.resourceownerpassword为用户名密码模式。上一节添加的client是客户端凭证模式,所以还需要添加一个client用于支持用户名密码模式。
public static ienumerable<client> getclients()
        {
            return new client[] {
              
                new client()
                {
                    //客户端id
                     clientid="apiclientcd",
                     //客户端密码
                     clientsecrets={new secret("apisecret".sha256()) },
                     //客户端授权类型,clientcredentials:客户端凭证方式
                     allowedgranttypes=granttypes.clientcredentials,
                     //允许访问的资源
                     allowedscopes={
                        "secretapi"
                    }
                },
                new client()
                {
                    //客户端id
                     clientid="apiclientpassword",
                     //客户端密码
                     clientsecrets={new secret("apisecret".sha256()) },
                     //客户端授权类型,clientcredentials:客户端凭证方式
                     allowedgranttypes=granttypes.resourceownerpassword,
                     //允许访问的资源
                     allowedscopes={
                        "secretapi"
                    }
                }

            };
        }

至此,服务端工作完成。转到ientityapi项目。

  • 在后台获取token:identitymodel为支持用户名密码模式,对httpclient做了一个扩展方法:requestpasswordtokenasync,修改一下identitycontroller控制器的gettoken接口,让它支持用户名密码模式获取token
 [httpget]
        [route("api/gettoken")]
        public async task<object> getcdtokenasync(string type,bool? request)
        {
            var client = new httpclient();
            var disco = await client.getdiscoverydocumentasync("http://localhost:5000");
            tokenresponse resp = null;
            switch (type)
            {
                case "cd":
                    resp = await client.requestclientcredentialstokenasync(new clientcredentialstokenrequest
                    {
                        //获取token的地址
                        address = disco.tokenendpoint,
                        //客户端id
                        clientid = "apiclientcd",
                        //客户端密码
                        clientsecret = "apisecret",
                        //要访问的api资源
                        scope = "secretapi"
                    });
                    break;
                case "pass":
                    resp = await client.requestpasswordtokenasync(new passwordtokenrequest()
                    {
                        //获取token的地址
                        address = disco.tokenendpoint,
                        //客户端id
                        clientid = "apiclientpassword",
                        //客户端密码
                        clientsecret = "apisecret",
                        //要访问的api资源
                        scope = "secretapi",
                        //用户名
                        username = "apiuser",
                        //密码
                        password = "apiuserpassword"
                    });
                    break;
            }
//如果request为true,直接利用token访问被保护的api if (request??false&&null!=resp) {
          //添加bearer认证头 client.setbearertoken(resp.accesstoken); var reslut =await client.getstringasync("https://localhost:5001/api/identity"); jarray json = jarray.parse(reslut); return json; } return resp?.json; }

  

 

  • 同样,也可以通过http请求获取

IdentityServer4 使用用户名和密码保护API访问

 

 获取到token后,访问受保护的api和通过客户端模式一样。

IdentityServer4 使用用户名和密码保护API访问

 到目前为止,昨们还没有搞清这两个模式有什么区别,如果仅仅是为了能访问这个api,那加不加用户名和密码有什么区别呢。昨们对比下这两种模式取得token后访问api返回的数据,可以发现用户名密码模式返回的claim的数量要多一些。claim是什么呢,简尔言之,是请求方附带在token中的一些信息。但客户端模式不涉及到用户信息,所以返回的claim数量会少一些。在identityserver4中,testuser有一个claims属性,允许自已添加claim,有一个claimtypes枚举列出了可以直接添加的claim。添加一个claimtypes.role试试。

identityserver.config.getusers

    public static list<testuser> getusers() {
            return new list<testuser>()
            {
                new testuser()
                {
                    //用户名
                     username="apiuser",
                     //密码
                     password="apiuserpassword",
                     //用户id
                     subjectid="0",
                     claims=new list<claim>(){
                         new claim(claimtypes.role,"admin")
                     }
                }
            };
        }

这时如果启动两个项目,采用用户密码和密码模式获取token访问api,返回的值依然是没有role:admin的claim的。这时又要用到apiresouce,apiresouce的构造函数有一个重载支持传进一个claim集合,用于允许该api资源可以携带那些claim。

identityserver.config.getapis

public static ienumerable<apiresource> getapis()
        {
            return new apiresource[] {
                //secretapi:标识名称,secret api:显示名称,可以自定义
                new apiresource("secretapi","secret api",new list<string>(){ claimtypes.role})
            };
        }

现在可以启动identityapi和identityserver两个项目测试一下,可以发现已经可以返回role这个claim了。

IdentityServer4 使用用户名和密码保护API访问

 

 role(角色)这个claim很有用,可以用来做简单的权限管理。

首先修改下被保护api的,使其支持role验证

identityapi.controllers.identitycontroller.getuserclaims

 [httpget]
        [route("api/identity")]
        [microsoft.aspnetcore.authorization.authorize(roles ="admin")]
        public object getuserclaims()
        {
            return user.claims.select(r => new { r.type, r.value });
        }

然后在identityserver端添加一个来宾角色用户

 identityserver.config.getusers

public static list<testuser> getusers() {
            return new list<testuser>()
            {
                new testuser()
                {
                    //用户名
                     username="apiuser",
                     //密码
                     password="apiuserpassword",
                     //用户id
                     subjectid="0",
                     claims=new list<claim>(){
                         new claim(claimtypes.role,"admin")

                     }
                },
                 new testuser()
                {
                    //用户名
                     username="apiuserguest",
                     //密码
                     password="apiuserpassword",
                     //用户id
                     subjectid="1",
                     claims=new list<claim>(){
                         new claim(claimtypes.role,"guest")
                     }
                }
            };
        }

再回到identityapi,修改下测试接口,把用户名和密码参数化,方便调试

identityapi.controllers.identitycontroller.getcdtokenasync

  

 [httpget]
        [route("api/gettoken")]
        public async task<object> getcdtokenasync(string type,bool? request,string username,string password)
        {
            var client = new httpclient();
            var disco = await client.getdiscoverydocumentasync("http://localhost:5000");
            tokenresponse resp = null;
            switch (type)
            {
                case "cd":
                    resp = await client.requestclientcredentialstokenasync(new clientcredentialstokenrequest
                    {
                        //获取token的地址
                        address = disco.tokenendpoint,
                        //客户端id
                        clientid = "apiclientcd",
                        //客户端密码
                        clientsecret = "apisecret",
                        //要访问的api资源
                        scope = "secretapi"
                    });
                    break;
                case "pass":
                    resp = await client.requestpasswordtokenasync(new passwordtokenrequest()
                    {
                        //获取token的地址
                        address = disco.tokenendpoint,
                        //客户端id
                        clientid = "apiclientpassword",
                        //客户端密码
                        clientsecret = "apisecret",
                        //要访问的api资源
                        scope = "secretapi",
                        //用户名
                        username = username,
                        //密码
                        password = password
                    });
                    break;
            }
            if (request??false&&null!=resp)
            {
                //添加bearer认证头
                client.setbearertoken(resp.accesstoken);
                var reslut =await client.getstringasync("https://localhost:5001/api/identity");
                jarray json = jarray.parse(reslut);
                return json;
            }
            return resp?.json;
        }

  分别用apiuser和apiuserguest访问

IdentityServer4 使用用户名和密码保护API访问

 

 

 IdentityServer4 使用用户名和密码保护API访问

 

 apiuserguest访问被拒绝。

 上边是添加claimtypes枚举里定义好的claim,但如果要定义的claim不在claim枚举里应该怎么办呢,比如我想所有用户都有一个项目编号,要添加一个名为prog的claim。

先在apiresouce里允许携带名为prog.claim

identityserver.config.getapis

 public static ienumerable<apiresource> getapis()
        {
            return new apiresource[] {
                //secretapi:标识名称,secret api:显示名称,可以自定义
                new apiresource("secretapi","secret api",new list<string>(){ claimtypes.role,claimtypes.name,"prog"})
            };
        }

在用户定义的claims属性里添加prog信息

identityserver.config.

getusers
    public static list<testuser> getusers() {
            return new list<testuser>()
            {
                new testuser()
                {
                    //用户名
                     username="apiuser",
                     //密码
                     password="apiuserpassword",
                     //用户id
                     subjectid="0",
                     claims=new list<claim>(){
                         new claim(claimtypes.role,"admin"),
                         new claim("prog","正式项目"),

                     }
                },
                 new testuser()
                {
                    //用户名
                     username="apiuserguest",
                     //密码
                     password="apiuserpassword",
                     //用户id
                     subjectid="1",
                     claims=new list<claim>(){
                         new claim(claimtypes.role,"guest"),
                         new claim("prog","测试项目"),
                     }
                }
            };
        }

 使用apiuser访问

IdentityServer4 使用用户名和密码保护API访问

 用户密码和密码模式就讲到这,两种模式讲完,config类里的identityresource还一点都没用上,它倒底有什么用呢,下一节讲另外二种模式:授权码模式和隐藏模式会用到它。