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

接上一篇: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请求获取

 获取到Token后,访问受保护的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了。

 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访问

 

 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访问

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

猜你喜欢

转载自www.cnblogs.com/liujiabing/p/11460486.html