客户端认证 客户端使用自己的名义,而不是用户的名义,向“服务提供商” 进行认证。 我们可以看个图来理解一下客户端认证
所以我们可以从图上看出,当用户非常信任客户端的时候,这个客户端有可能是手机,或者PC又或者是任何一个App,客户端向认证服务器(Authorization Server 简称AS)发送认证请求,AS回传一个Token,然后认证服务器在Header里面加入Authorization字段,请求资源服务器(Resource Server 简称RS),获取资源
- 认证服务器和资源服务器是可以在同一个项目里面的
- 本案例为了演示,就将认证服务器和资源服务器分开新建一个项目,在OAuth认证中,将会一直使用这个认证服务器和资源服务器
0 新建一个RS(认证服务器)
- 引入Owin组件
OWIN是Open Web Server Interface for .NET的首字母缩写。OWIN在.NET Web Servers与Web Application之间定义了一套标准接口,OWIN的目标是用于解耦Web Server和Web Application。基于此标准,鼓励开发者开发简单、灵活的模块,从而推进.NET Web Development开源生态系统的发展。
-
打开包管理器,安装以下组件
-
在App_Start文件夹下面新建一个Owin StartUp的Class文件,其中代码会生成
修改StarpUp.cs的文件内容
- 在Configuration方法内增加以下代码
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
//请求路径,其中客户端应用程序会将重定向用户代理以获取用户同意使用颁发的令牌或代码。 它必须以具有前导斜杠,例如,"/Authorize"
AuthorizeEndpointPath = new PathString("/Aauthorize"),
//TokenEndpointPath: 请求路径客户端应用程序直接进行通信以获取访问令牌。 它必须以具有前导斜杠,如"/token"。 如果客户端颁发客户端_机密,它必须提供给此终结点
TokenEndpointPath = new PathString("/token"),
//设置为true如果 web 应用程序想要在生成的客户端验证错误的自定义错误页 / Authorize终结点。 这才必需的情况下,在浏览器不会重定向回客户端应用程序,例如,当client_id或redirect_uri不正确。 / Authorize终结点应该会看到"oauth。错误"、"oauth。ErrorDescription"和"oauth。ErrorUri"属性添加到 OWIN 环境。
ApplicationCanDisplayErrors = true,
#if DEBUG
//AllowInsecureHttp: True 以允许授权和令牌请求到达 HTTP URI 地址,并允许传入redirect_uri授权请求参数,具有 HTTP URI 地址。
AllowInsecureHttp = true,
#endif
//处理由授权服务器中间件引发事件应用程序提供的对象。 应用程序可能完全实现接口,或它可能会造成的实例OAuthAuthorizationServerProvider并分配此服务器支持的 OAuth 流所需的委托。
Provider = new OAuthAuthorizationServerProvider
{
OnGrantResourceOwnerCredentials = OnGrantResourceOwnerCredentials,
OnGrantClientCredentials= OnGrantClientCredentials,
OnValidateClientRedirectUri= OnValidateClientRedirectUri,
OnValidateClientAuthentication= OnValidateClientAuthentication,
},
//生成一次性授权代码返回到客户端应用程序。 为 OAuth 服务器要保护的应用程序必须提供的一个实例AuthorizationCodeProvider由生成的令牌
AuthorizationCodeProvider = new AuthenticationTokenProvider
{
OnCreate = CreateAuthenticationCode,
OnReceive = ReceiveAuthenticationCode,
},
// 生成可用于生成新的访问令牌时所需的刷新令牌。 如果未提供授权服务器不会返回的刷新令牌/Token终结点
RefreshTokenProvider = new AuthenticationTokenProvider
{
OnCreate = CreateRefreshToken,
OnReceive = ReceiveRefreshToken,
}
});
- 因为starUp.cs是个非常重要的类,笔者水平有限,很难通过文字描述清楚,所以把startUp.cs整个类文件放置
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.Infrastructure;
using Microsoft.Owin.Security.OAuth;
using Owin;
[assembly: OwinStartup(typeof(AuthorizationServer.App_Start.Startup))]
namespace AuthorizationServer.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// 有关如何配置应用程序的详细信息,请访问 https://go.microsoft.com/fwlink/?LinkID=316888
//
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Application", //这里有个坑,先提醒下
AuthenticationMode = AuthenticationMode.Passive,
LoginPath = new PathString("/Account/Login"),
LogoutPath = new PathString("/Account/Logout"),
});
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
//请求路径,其中客户端应用程序会将重定向用户代理以获取用户同意使用颁发的令牌或代码。 它必须以具有前导斜杠,例如,"/Authorize"
AuthorizeEndpointPath = new PathString("/OAuth/Aauthorize"),
//TokenEndpointPath: 请求路径客户端应用程序直接进行通信以获取访问令牌。 它必须以具有前导斜杠,如"/token"。 如果客户端颁发客户端_机密,它必须提供给此终结点
TokenEndpointPath = new PathString("/OAuth/token"),
//设置为true如果 web 应用程序想要在生成的客户端验证错误的自定义错误页 / Authorize终结点。 这才必需的情况下,在浏览器不会重定向回客户端应用程序,例如,当client_id或redirect_uri不正确。 / Authorize终结点应该会看到"oauth。错误"、"oauth。ErrorDescription"和"oauth。ErrorUri"属性添加到 OWIN 环境。
ApplicationCanDisplayErrors = true,
#if DEBUG
//AllowInsecureHttp: True 以允许授权和令牌请求到达 HTTP URI 地址,并允许传入redirect_uri授权请求参数,具有 HTTP URI 地址。
AllowInsecureHttp = true, //这里的设置包含整个流程通信环境是否启用ssl
#endif
//处理由授权服务器中间件引发事件应用程序提供的对象。 应用程序可能完全实现接口,或它可能会造成的实例OAuthAuthorizationServerProvider并分配此服务器支持的 OAuth 流所需的委托。
Provider = new OAuthAuthorizationServerProvider
{
OnGrantResourceOwnerCredentials = GrantResourceOwnerCredentials, //客户端通过包括从资源所有者处收到的凭据从授权服务器的令牌终结点请求访问令牌。 当发出请求,客户端进行身份验证与授权服务器。身份验证模式,就是账号密码
OnGrantClientCredentials = GrantClientCredentials, //客户端向授权服务器进行身份验证,并从令牌终结点请求访问令牌。客户端模式
OnValidateClientRedirectUri = ValidateClientRedirectUri, //用来验证其注册的重定向 URL 的客户端。防钓鱼
OnValidateClientAuthentication = ValidateClientAuthentication, //检查的基本方案标头和窗体正文以获取客户端的凭据
},
//生成一次性授权代码返回到客户端应用程序。 为 OAuth 服务器要保护的应用程序必须提供的一个实例AuthorizationCodeProvider由生成的令牌
AuthorizationCodeProvider = new AuthenticationTokenProvider
{
OnCreate = CreateAuthenticationCode,
OnReceive = ReceiveAuthenticationCode,
},
// 生成可用于生成新的访问令牌时所需的刷新令牌。 如果未提供授权服务器不会返回的刷新令牌/Token终结点
RefreshTokenProvider = new AuthenticationTokenProvider
{
OnCreate = CreateRefreshToken,
OnReceive = ReceiveRefreshToken,
}
});
}
private void ReceiveRefreshToken(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
private void CreateRefreshToken(AuthenticationTokenCreateContext context)
{
context.SetToken(context.SerializeTicket());
}
private void ReceiveAuthenticationCode(AuthenticationTokenReceiveContext context)
{
string value;
if (_authenticationCodes.TryRemove(context.Token, out value))
{
context.DeserializeTicket(value);
}
}
private readonly ConcurrentDictionary _authenticationCodes =
new ConcurrentDictionary(StringComparer.Ordinal);
private void CreateAuthenticationCode(AuthenticationTokenCreateContext context)
{
context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n"));
_authenticationCodes[context.Token] = context.SerializeTicket();
}
private Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret;
if (context.TryGetBasicCredentials(out clientId, out clientSecret) ||
context.TryGetFormCredentials(out clientId, out clientSecret))
{
if (clientId == "123456" && clientSecret == "123456")
{
context.Validated();
}
}
return Task.FromResult(0);
}
private Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == "123456")
{
context.Validated();
}
return Task.FromResult(0);
}
private Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
{
var identity = new ClaimsIdentity(new GenericIdentity(context.ClientId, OAuthDefaults.AuthenticationType), context.Scope.Select(x => new Claim("urn:oauth:scope", x)));
context.Validated(identity);
return Task.FromResult(0);
}
private Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(new GenericIdentity(context.UserName, OAuthDefaults.AuthenticationType), context.Scope.Select(x => new Claim("urn:oauth:scope", x)));
context.Validated(identity);
return Task.FromResult(0);
}
}
}
1 新建资源服务器
- 建立一个webapi的项目,然后在项目目录下面新建一个OWIN StartUp.cs文件
- 同样也要安装Owin包
-
在App_Start文件夹下面新建一个StartUp.Auth.cs文件
StartUp.cs
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(ResourceServer.Startup))]
namespace ResourceServer
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigurationAuth(app);
}
}
}
StartUp.Auth.cs
using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ResourceServer
{
public partial class Startup
{
public void ConfigurationAuth(IAppBuilder app)
{
app.UseOAuthBearerAuthentication(new Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationOptions());
}
}
}
3在解决方案文件夹下面新建一个文件夹Clients,然后新建ClientCredentialGrant项目
using Constants;
using DotNetOpenAuth.OAuth2;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace ClientCredentialGrant
{
class Program
{
private static WebServerClient _webServerClient;
private static string _accessToken;
static void Main(string[] args)
{
InitializeWebServerClient();
Console.WriteLine("Requesting Token...");
RequestToken();
Console.WriteLine("Access Token: {0}", _accessToken);
Console.WriteLine("Access Protected Resource");
AccessProtectedResource();
Console.ReadKey();
}
private static void InitializeWebServerClient()
{
var authorizationServerUri = new Uri(Paths.AuthorizationServerBaseAddress);
var authorizationServer = new AuthorizationServerDescription
{
AuthorizationEndpoint = new Uri(authorizationServerUri, Paths.AuthorizePath),
TokenEndpoint = new Uri(authorizationServerUri, Paths.TokenPath)
};
Console.WriteLine(authorizationServerUri);
_webServerClient = new WebServerClient(authorizationServer, Clients.Client1.Id, Clients.Client1.Secret);
}
private static void RequestToken()
{
var state = _webServerClient.GetClientAccessToken();
_accessToken = state.AccessToken;
}
private static void AccessProtectedResource()
{
var resourceServerUri = new Uri(Paths.ResourceServerBaseAddress);
var client = new HttpClient(_webServerClient.CreateAuthorizingHandler(_accessToken));
var body = client.GetStringAsync(new Uri(resourceServerUri, Paths.MePath)).Result;
Console.WriteLine(body);
}
}
}