NET Core实现OAuth2.0的ResourceOwnerPassword和ClientCredentials模式

前言

开发授权服务框架一般使用OAuth2.0授权框架,而开发Webapi的授权更应该使用OAuth2.0授权标准,OAuth2.0授权框架文档说明参考:https://tools.ietf.org/html/rfc6749

.NET Core开发OAuth2的项目需要使用IdentityServer4(现在还处于RC预发行版本),可参考:https://identityserver4.readthedocs.io/en/dev/

IdentityServer4源码:https://github.com/IdentityServer

如果在.NET中开发OAuth2的项目可使用OWIN,可参考实例源码:https://www.asp.net/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server

实现ResourceOwnerPassword和client credentials模式:

授权服务器:

Program.cs --> Main方法中:需要调用UseUrls设置IdentityServer4授权服务的IP地址

1             var host = new WebHostBuilder()
2 .UseKestrel()
3 //IdentityServer4的使用需要配置UseUrls
4 .UseUrls("http://localhost:4537")
5 .UseContentRoot(Directory.GetCurrentDirectory())
6 .UseIISIntegration()
7 .UseStartup<Startup>()
8 .Build();

Startup.cs -->ConfigureServices方法中:

 1             //RSA:证书长度2048以上,否则抛异常
2 //配置AccessToken的加密证书
3 var rsa = new RSACryptoServiceProvider();
4 //从配置文件获取加密证书
5 rsa.ImportCspBlob(Convert.FromBase64String(Configuration["SigningCredential"]));
6 //IdentityServer4授权服务配置
7 services.AddIdentityServer()
8 .AddSigningCredential(new RsaSecurityKey(rsa)) //设置加密证书
9 //.AddTemporarySigningCredential() //测试的时候可使用临时的证书
10 .AddInMemoryScopes(OAuth2Config.GetScopes())
11 .AddInMemoryClients(OAuth2Config.GetClients())
12 //如果是client credentials模式那么就不需要设置验证User了
13 .AddResourceOwnerValidator<MyUserValidator>() //User验证接口
14 //.AddInMemoryUsers(OAuth2Config.GetUsers()) //将固定的Users加入到内存中
15 ;

Startup.cs --> Configure方法中:

1             //使用IdentityServer4的授权服务
2 app.UseIdentityServer();

Client配置

在Startup.cs中通过AddInMemoryClients(OAuth2Config.GetClients())设置到内存中,配置:

 1                 new Client
2 {
3 //client_id
4 ClientId = "pwd_client",
5 //AllowedGrantTypes = new string[] { GrantType.ClientCredentials }, //Client Credentials模式
6 AllowedGrantTypes = new string[] { GrantType.ResourceOwnerPassword }, //Resource Owner Password模式
7 //client_secret
8 ClientSecrets =
9 {
10 new Secret("pwd_secret".Sha256())
11 },
12 //scope
13 AllowedScopes =
14 {
15 "api1",
16 //如果想带有RefreshToken,那么必须设置:StandardScopes.OfflineAccess
17 //如果是Client Credentials模式不支持RefreshToken的,就不需要设置OfflineAccess
18 StandardScopes.OfflineAccess.Name,
19 },
20 //AccessTokenLifetime = 3600, //AccessToken的过期时间, in seconds (defaults to 3600 seconds / 1 hour)
21 //AbsoluteRefreshTokenLifetime = 60, //RefreshToken的最大过期时间,in seconds. Defaults to 2592000 seconds / 30 day
22 //RefreshTokenUsage = TokenUsage.OneTimeOnly, //默认状态,RefreshToken只能使用一次,使用一次之后旧的就不能使用了,只能使用新的RefreshToken
23 //RefreshTokenUsage = TokenUsage.ReUse, //可重复使用RefreshToken,RefreshToken,当然过期了就不能使用了
24 }

Scope设置

在Startup.cs中通过AddInMemoryScopes(OAuth2Config.GetScopes())设置到内存中,配置:

 1         public static IEnumerable<Scope> GetScopes()
2 {
3 return new List<Scope>
4 {
5 new Scope
6 {
7 Name = "api1",
8 Description = "My API",
9 },
10 //如果想带有RefreshToken,那么必须设置:StandardScopes.OfflineAccess
11 StandardScopes.OfflineAccess,
12 };
13 }

账号密码验证

Resource Owner Password模式需要对账号密码进行验证(如果是client credentials模式则不需要对账号密码验证了):

方式一:将Users加入到内存中,IdentityServer4从中获取对账号和密码进行验证:

  .AddInMemoryUsers(OAuth2Config.GetUsers())

方式二(推荐):实现IResourceOwnerPasswordValidator接口进行验证:

  .AddResourceOwnerValidator<MyUserValidator>()

IResourceOwnerPasswordValidator的实现:

 1     public class MyUserValidator : IResourceOwnerPasswordValidator
2 {
3 public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
4 {
5 if (context.UserName == "admin" && context.Password == "123")
6 {
7 //验证成功
8 //使用subject可用于在资源服务器区分用户身份等等
9 //获取:资源服务器通过User.Claims.Where(l => l.Type == "sub").FirstOrDefault();获取
10 context.Result = new GrantValidationResult(subject: "admin", authenticationMethod: "custom");
11 }
12 else
13 {
14 //验证失败
15 context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential");
16 }
17 return Task.FromResult(0);
18 }
19 }

设置加密证书

通过AddSigningCredential方法设置RSA的加密证书(注意:默认是使用临时证书的,就是AddTemporarySigningCredential(),无论如何不应该使用临时证书,因为每次重启授权服务,就会重新生成新的临时证书),RSA加密证书长度要2048以上,否则服务运行会抛异常

Startup.cs -->ConfigureServices方法中的配置:

1             //RSA:证书长度2048以上,否则抛异常
2 //配置AccessToken的加密证书
3 var rsa = new RSACryptoServiceProvider();
4 //从配置文件获取加密证书
5 rsa.ImportCspBlob(Convert.FromBase64String(Configuration["SigningCredential"]));
6 services.AddIdentityServer()
7 .AddSigningCredential(new RsaSecurityKey(rsa)) //设置加密证书

如何生成RSA加密证书(将生成的PrivateKey配置到IdentityServer4中,可以设置到配置文件中):

1             using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider(2048))
2 {
3 //Console.WriteLine(Convert.ToBase64String(provider.ExportCspBlob(false))); //PublicKey
4 Console.WriteLine(Convert.ToBase64String(provider.ExportCspBlob(true))); //PrivateKey
5 }

资源服务器

Program.cs -> Main方法中:

1             var host = new WebHostBuilder()
2 .UseKestrel()
3 //IdentityServer4的使用需要配置UseUrls
4 .UseUrls("http://localhost:4823")
5 .UseContentRoot(Directory.GetCurrentDirectory())
6 .UseIISIntegration()
7 .UseStartup<Startup>()
8 .Build();

Startup.cs --> Configure方法中的配置:

            //使用IdentityServer4的资源服务并配置
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:4537/",
ScopeName = "api1",
SaveToken = true,
AdditionalScopes = new string[] { "offline_access" }, //添加额外的scope,offline_access为Refresh Token的获取Scope
RequireHttpsMetadata = false,
});

需要进行授权验证的资源接口(控制器或方法)上设置AuthorizeAttribute:

1     [Authorize]
2 [Route("api/[controller]")]
3 public class ValuesController : Controller

测试

resource owner password模式测试代码:

 1         public static void TestResourceOwnerPassword()
2 {
3 var client = new HttpClientHepler("http://localhost:4537/connect/token");
4 string accessToken = null, refreshToken = null;
5 //获取AccessToken
6 client.PostAsync(null,
7 "grant_type=" + "password" +
8 "&username=" + "admin" +
9 "&password=" + "123" +
10 "&client_id=" + "pwd_client" +
11 "&client_secret=" + "pwd_secret" +
12 "&scope=" + "api1 offline_access", //scope需要用空格隔开,offline_access为获取RefreshToken
13 hd => hd.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"),
14 rtnVal =>
15 {
16 var jsonVal = JsonConvert.DeserializeObject<dynamic>(rtnVal);
17 accessToken = jsonVal.access_token;
18 refreshToken = jsonVal.refresh_token;
19 },
20 fault => Console.WriteLine(fault),
21 ex => Console.WriteLine(ex)).Wait();
22
23 if (!string.IsNullOrEmpty(refreshToken))
24 {
25 //使用RefreshToken获取新的AccessToken
26 client.PostAsync(null,
27 "grant_type=" + "refresh_token" +
28 "&client_id=" + "pwd_client" +
29 "&client_secret=" + "pwd_secret" +
30 "&refresh_token=" + refreshToken,
31 hd => hd.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"),
32 rtnVal => Console.WriteLine("refresh之后的结果: \r\n" + rtnVal),
33 fault => Console.WriteLine(fault),
34 ex => Console.WriteLine(ex)).Wait();
35 }
36
37 if (!string.IsNullOrEmpty(accessToken))
38 {
39 //访问资源服务
40 client.Url = "http://localhost:4823/api/values";
41 client.GetAsync(null,
42 hd => hd.Add("Authorization", "Bearer " + accessToken),
43 rtnVal => Console.WriteLine("\r\n访问资源服: \r\n" + rtnVal),
44 fault => Console.WriteLine(fault),
45 ex => Console.WriteLine(ex)).Wait();
46 }
47 }

client credentials模式测试代码:

 1         public static void TestClientCredentials()
2 {
3 var client = new HttpClientHepler("http://localhost:4537/connect/token");
4 string accessToken = null;
5 //获取AccessToken
6 client.PostAsync(null,
7 "grant_type=" + "client_credentials" +
8 "&client_id=" + "credt_client" +
9 "&client_secret=" + "credt_secret" +
10 "&scope=" + "api1", //不要加上offline_access,因为Client Credentials模式不支持RefreshToken的,不然会授权失败
11 hd => hd.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"),
12 rtnVal =>
13 {
14 var jsonVal = JsonConvert.DeserializeObject<dynamic>(rtnVal);
15 accessToken = jsonVal.access_token;
16 },
17 fault => Console.WriteLine(fault),
18 ex => Console.WriteLine(ex)).Wait();
19
20 if (!string.IsNullOrEmpty(accessToken))
21 {
22 //访问资源服务
23 client.Url = "http://localhost:4823/api/values";
24 client.GetAsync(null,
25 hd => hd.Add("Authorization", "Bearer " + accessToken),
26 rtnVal => Console.WriteLine("访问资源服: \r\n" + rtnVal),
27 fault => Console.WriteLine(fault),
28 ex => Console.WriteLine(ex)).Wait();
29 }
30 }

注意

1.RefreshToken是存储在内存中的,不像AccessToken通过授权服务器设置的加密证书进行加密的,而是生成一个唯一码存储在授权服务的内存中的,因此授权服务器重启了那么这些RefreshToken就消失了;

2.资源服务器在第一次解析AccessToken的时候会先到授权服务器获取配置数据(例如会访问:http://localhost:4537/.well-known/openid-configuration 获取配置的,http://localhost:4537/.well-known/openid-configuration/jwks 获取jwks)),之后解析AccessToken都会使用第一次获取到的配置数据,因此如果授权服务的配置更改了(加密证书等等修改了),那么应该重启资源服务器使之重新获取新的配置数据;

3.调试IdentityServer4框架的时候应该配置好ILogger,因为授权过程中的访问(例如授权失败等等)信息都会调用ILogger进行日志记录,可使用NLog,例如:

  在Startup.cs --> Configure方法中配置:loggerFactory.AddNLog();//添加NLog

源码:http://files.cnblogs.com/files/skig/OAuth2CredentialsAndPassword.zip

 

NET Core实现OAuth2.0的ResourceOwnerPassword和ClientCredentials模式的更多相关文章

  1. ASP.NET Core实现OAuth2.0的ResourceOwnerPassword和ClientCredentials模式

    前言 开发授权服务框架一般使用OAuth2.0授权框架,而开发Webapi的授权更应该使用OAuth2.0授权标准,OAuth2.0授权框架文档说明参考:https://tools.ietf.org/ ...

  2. ASP.NET Core实现OAuth2.0的AuthorizationCode模式

    前言 在上一篇中实现了resource owner password credentials和client credentials模式:http://www.cnblogs.com/skig/p/60 ...

  3. springboot+spring security +oauth2.0 demo搭建(password模式)(认证授权端与资源服务端分离的形式)

    项目security_simple(认证授权项目) 1.新建springboot项目 这儿选择springboot版本我选择的是2.0.6 点击finish后完成项目的创建 2.引入maven依赖  ...

  4. OAuth2.0的四种授权模式

    1.什么是OAuth2 OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OA ...

  5. OAuth2.0的四种授权模式(转)

    1. OAuth2简易实战(一)-四种模式 1.1. 隐式授权模式(Implicit Grant) 第一步:用户访问页面时,重定向到认证服务器. 第二步:认证服务器给用户一个认证页面,等待用户授权. ...

  6. OAuth2.0学习(1-3)OAuth2.0的参与者和流程

    OAuth(开放授权)是一个开放标准.允许第三方网站在用户授权的前提下访问在用户在服务商那里存储的各种信息.而这种授权无需将用户提供用户名和密码提供给该第三方网站. OAuth允许用户提供一个令牌给第 ...

  7. 接口测试工具-Jmeter使用笔记(八:模拟OAuth2.0协议简化模式的请求)

    背景 博主的主要工作是测试API,目前已经用Jmeter+Jenkins实现了项目中的接口自动化测试流程.但是马上要接手的项目,API应用的是OAuth2.0协议授权,并且采用的是简化模式(impli ...

  8. 项目总结之Oauth2.0免登陆及相关知识点总结

    简介Oauth2.0授权步骤 授权码模式的基本步骤 原文链接地址 (A)用户访问客户端,后者将前者导向认证服务器. (B)用户选择是否给予客户端授权. (C)假设用户给予授权,认证服务器将用户导向客户 ...

  9. Oauth2.0 认证的Web api例子

    Oauth2.0的解释 OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容.OA ...

随机推荐

  1. google IO大会

    怎么参加一次 Google I/O?大概要多少预算? Google I/O(参加Goole I/O 是我的一个梦想,因为我是Google死忠,想亲自去Google总部看看,所以想知道这些) 费用构成: ...

  2. 改善C#程序的建议10:用Parallel简化Task

    在命名空间System.Threading.Tasks下,有一个静态类Parallel简化了在同步状态下的Task的操作.Parallel主要提供了3个有用的方法:For.ForEach.Invoke ...

  3. laravel 5.4 运行 make:auth 报错

    Laravel 5.4 migrate时报错: Specified key was too long error 问题根源 MySQL支持的utf8编码最大字符长度为3字节,如果遇到4字节的宽字符就会 ...

  4. HihoCoder1642 : 三角形面积和([Offer收割]编程练习赛37)(求面积)(扫描线||暴力)(占位)

    描述 如下图所示,在X轴上方一共有N个等腰直角三角形.这些三角形的斜边与X轴重合,斜边的对顶点坐标是(Xi, Yi). (11,5) (4,4) /\ /\(7,3) \ / \/\/ \ / /\/ ...

  5. bzoj 2159 Crash 的文明世界 & hdu 4625 JZPTREE —— 第二类斯特林数+树形DP

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2159 使用公式:\( n^{k} = \sum\limits_{i=0}^{k} S(k,i ...

  6. Poj_1011_Sticks(剪枝)

    一.Description 乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过50个长度单位.然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度. ...

  7. Poj_1005_I Think I Need A HouseBoat

    一.Description Fred Mapper is considering purchasing some land in Louisiana to build his house on. In ...

  8. Spring boot 2.0 学习

    Spring boot 2.0出来了,支持java 9, 好多新特性,应该学习.   待续... ...

  9. SELinux处理命令

    1 查看SELinux状态 Enforcing为开启状态:Disabled为关闭状态. [root@localhost /]# getenforce Enforcing 2 临时关闭SELinux [ ...

  10. [hdu2063]过山车(二分图匹配)

    题意:每个女人有感兴趣的k个男人,过山车两人一组,求最大匹配数. 解题关键:二分图最大匹配.匈牙利算法求解. 1.链式前向星建图 #include<cstdio> #include< ...