IdentityServer4 实现OAuth2.0四种模式之密码模式
接上一篇:IdentityServer4 实现OAuth2.0四种模式之客户端模式,这一篇讲IdentityServer4 使用密码模式保护API访问。
一,IdentityServer配置
1,添加用户
要用到用户名称密码当然得添加用户,在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");
}
}
2,添加客户端
添加一个客户端用于用户名和密码模式的访问。客户端(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"
}
} };
}
二,保用密码模式访问受保护的Api
1,使用IdentityMvc项目访问受保护的Api
修改GetData控制器,使其支持密码模式访问
public async Task<IActionResult> GetData(string type)
{
type = type ?? "client";
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
if (disco.IsError)
return new JsonResult(new { err=disco.Error});
TokenResponse token = null;
switch (type)
{
case "client":
token = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest()
{
//获取Token的地址
Address = disco.TokenEndpoint,
//客户端Id
ClientId = "apiClientCd",
//客户端密码
ClientSecret = "apiSecret",
//要访问的api资源
Scope = "secretapi"
});
break;
case "password":
token = await client.RequestPasswordTokenAsync(new PasswordTokenRequest()
{
//获取Token的地址
Address = disco.TokenEndpoint,
//客户端Id
ClientId = "apiClientPassword",
//客户端密码
ClientSecret = "apiSecret",
//要访问的api资源
Scope = "secretapi",
UserName = "apiUser",
Password = "apiUserPassword"
});
break;
}
if (token.IsError)
return new JsonResult(new { err = token.Error });
client.SetBearerToken(token.AccessToken);
string data = await client.GetStringAsync("https://localhost:5001/api/identity");
JArray json = JArray.Parse(data);
return new JsonResult(json);
}
运行三个项目后访问:https://localhost:5002/home/getdata?type=password
2,使用原生HTTP请求访问受保护的Api
获取access_token

获取到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})
};
}
现在可以启动项目测试一下,可以发现已经可以返回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")
                     }
                }
            };
        }
再回到IdentityMvc项目,修改下获取数据的测试接口GetData,把用户名和密码参数化,方便调试
IdentityMvc.HomeContoller.GetData
public async Task<IActionResult> GetData(string type,string userName,string password)
{
type = type ?? "client";
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
if (disco.IsError)
return new JsonResult(new { err=disco.Error});
TokenResponse token = null;
switch (type)
{
case "client":
token = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest()
{
//获取Token的地址
Address = disco.TokenEndpoint,
//客户端Id
ClientId = "apiClientCd",
//客户端密码
ClientSecret = "apiSecret",
//要访问的api资源
Scope = "secretapi"
});
break;
case "password":
token = await client.RequestPasswordTokenAsync(new PasswordTokenRequest()
{
//获取Token的地址
Address = disco.TokenEndpoint,
//客户端Id
ClientId = "apiClientPassword",
//客户端密码
ClientSecret = "apiSecret",
//要访问的api资源
Scope = "secretapi",
UserName =userName,
Password = password
});
break;
}
if (token.IsError)
return new JsonResult(new { err = token.Error });
client.SetBearerToken(token.AccessToken);
string data = await client.GetStringAsync("https://localhost:5001/api/identity");
JArray json = JArray.Parse(data);
return new JsonResult(json);
}
分别用apiUser和apiUserGuest访问,用apiUserGuest访问时请求被拒绝
https://localhost:5002/home/getdata?type=password&userName=apiUserGuest&password=apiUserPassword

上边是添加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访问
https://localhost:5002/home/getdata?type=password&userName=apiUser&password=apiUserPassword

密码模式需要知道用户的密码,那能不能用户自己从identityServer登录,不把密码给到第三方呢?,下一篇讲的隐藏模式就解决了这个问题。
IdentityServer4 实现OAuth2.0四种模式之密码模式的更多相关文章
- IdentityServer4实现Oauth2.0四种模式之隐藏模式
		
接上一篇:IdentityServer4实现OAuth2.0四种模式之密码模式,密码模式将用户的密码暴露给了客户端,这无疑是不安全的,隐藏模式可以解决这个问题,由用户自己在IdentityServ ...
 - IdentityServer4实现OAuth2.0四种模式之授权码模式
		
接上一篇:IdentityServer4实现OAuth2.0四种模式之隐藏模式 授权码模式隐藏码模式最大不同是授权码模式不直接返回token,而是先返回一个授权码,然后再根据这个授权码去请求token ...
 - IdentityServer4实现OAuth2.0四种模式之客户端模式
		
一,准备内容 IdentityServer4 是Asp.net core的一个中间件,用于添加符合OpenId Connect和OAuth2.0规范的终端到Asp.net Core应用.在这里简单介绍 ...
 - OAuth2.0 四种授权模式
		
OAuth2.0简单笔记(四种授权模式) 金天:坚持写东西,不是一件容易的事,换句话说其实坚持本身都不是一件容易的事.如果学习有捷径,那就是不断实践,不断积累.写笔记,其实是给自己看的,是体现积累的一 ...
 - Core篇——初探IdentityServer4(客户端模式,密码模式)
		
Core篇——初探IdentityServer4(客户端模式,密码模式) 目录 1.Oatuth2协议的客户端模式介绍2.IdentityServer4客户端模式实现3.Oatuth2协议的密码模式介 ...
 - Spring Boot Security Oauth2之客户端模式及密码模式实现
		
Spring Boot Security Oauth2之客户端模式及密码模式实现 示例主要内容 1.多认证模式(密码模式.客户端模式) 2.token存到redis支持 3.资源保护 4.密码模式用户 ...
 - 快速了解yuv4:4:4   yuv4:2:2   yuv 4:1:1   yuv 4:2:0四种YUV格式区别
		
四种YUV格式区别如下: 1.YUV 4:4:4抽样方式: Y: Y0 Y1 Y2 Y3 U: U0 U1 U2 U3 V: V0 V1 V2 V3 2.YUV 4:2:2抽样方式: Y : ...
 - JS创建对象的四种简单方式 (工厂模式和自定义构造函数创建对象的区别)
		
// 对象:特指的某个事物,具有属性和方法(一组无序的属性的集合) // 特征------>属性 // 行为------>方法 // 创建对象的四种方式 1 // 1.字面量的方式,就是实 ...
 - 四种webAPP横向滑动模式图解—H5页面开发
		
一.容器整体滑动(DEMO只演示A-B-C-B,下同) 模拟动画效果见下图(上),滑动分解见下图(下): DEMO地址:http://nirvana.sinaapp.com/demo_slider/s ...
 
随机推荐
- Pycharm 导入Selenium,出现错误
			
问题 导入Selenium,出现红色波浪线. 解决 点击Pycharm左上角File>setting>Project Interpreter,双击PIP,搜索栏搜索Selenium 然后选 ...
 - cv2 的用法
			
转载:https://www.cnblogs.com/shizhengwen/p/8719062.html 一.读入图像 使用函数cv2.imread(filepath,flags)读入一副图片 fi ...
 - 南开大学2020年数学分析高等代数考研试题回忆版TeX排版
			
南开大学2020年数学分析高等代数考研试题回忆版TeX排版 220328[南开大学2020年高等代数考研试题回忆版] 220329[南开大学2020年数学分析考研试题回忆版]
 - Oracle的“ORA-00937: 不是单组分组函数” 如何解决?
			
之前在编写oracle的sql语句时遇到这个问题,这里做个记录 问题描述:ORA-00937: 不是单组分组函数 问题原因:select语句中又在查询某一列的值,其中还有聚合函数 原先本人编写SQL是 ...
 - JDBC 线程安全 数据库连接池
			
jdbc 是线程安全的,但是,推荐一个线程用一个链接 JDBC is thread safe: It is quite OK to pass the various JDBC objects betw ...
 - Roberts算子
			
https://blog.csdn.net/likezhaobin/article/details/6892176 https://zhuanlan.zhihu.com/p/35032299 Robe ...
 - html5统计数据上报API:SendBeacon
			
公司为了精准的了解自己产品的用户使用情况,通常会对用户数据进行统计分析,获取pv.uv.页面留存率.访问设备等信息.与之相关的就是客户端的数据采集,然后上报的服务端.为了保证数据的准确性,就需要保证数 ...
 - sed 变量在shell引用
			
#!/bin/bashZipName=`ls -lt /data/office_services/*.zip | head -1 | awk -F"/" '{print $NF}' ...
 - 大话图解golang map
			
前言 网上分析golang中map的源码的博客已经非常多了,随便一搜就有,而且也非常详细,所以如果我再来写就有点画蛇添足了(而且我也写不好,手动滑稽).但是我还是要写,略略略,这篇博客的意义在于能从几 ...
 - django学习问题集
			
case 1: python manage.py migrate时报错:django.core.exceptions.ImproperlyConfigured: Error loading MySQL ...