在WebApi中基于Owin OAuth使用授权发放Token
如何基于Microsoft.Owin.Security.OAuth,使用Client Credentials
Grant授权方式给客户端发放access token? Client Credentials
Grant的授权方式就是只验证客户端(Client),不验证用户(Resource Owner),只要客户端通过验证就发access
token。举一个对应的应用场景例子,比如我们想提供一个“获取网站首页最新博文列表”的WebAPI给客户端App调用。由于这个数据与用户无关,所以不涉及用户登录与授权,不需要Resource
Owner的参与。但我们不想任何人都可以调用这个WebAPI,所以要对客户端进行验证,而使用OAuth中的 Client
Credentials Grant 授权方式可以很好地解决这个问题。
在App_Start文件夹下新增ApplicationDbInitializer,代码如下:
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext>
{
protected override void Seed(ApplicationDbContext context)
{
InitializeIdentityForEF(context);
base.Seed(context);
}
//创建用户名为admin@123.com,密码为“Admin@123456”
public static void InitializeIdentityForEF(ApplicationDbContext dbContext)
{
var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
const string name = "admin@123.com";//用户名
const string email = "admin@123.com";//邮箱
const string password = "Admin@123456";//密码
//如果没有admin@123.com用户则创建该用户
var user = userManager.FindByName(name);
if (user == null)
{
user = new ApplicationUser
{
UserName = name,
Email = email
};
var result = userManager.Create(user, password);
result = userManager.SetLockoutEnabled(user.Id, false);
}
}
}
修改Model文件夹下的IdentityModels.cs,添加斜体部分代码,需添加命名空间:using System.Data.Entity;
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
// 在第一次启动网站时初始化数据库添加管理员用户凭据到数据库
Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
}
我把WebApi的Controller放到一个新建的文件夹APIControllers中,TestController的View的js的测试代码
打开Startup.Auth.cs,以下代码是Oauth相关的配置代码
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
var OAuthOptions = new OAuthAuthorizationServerOptions
{
//获取Token的路径
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
//Token 过期时间,默认20分钟
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
//在生产模式下设 AllowInsecureHttp = false
AllowInsecureHttp = true
};
app.UseOAuthBearerTokens(OAuthOptions);
}
}
使用Client Credentials Grant的授权方式( grant_type= client_credentials)获取 Access Token,并以这个 Token 调用与用户相关的 Web API。
我们需要修改部分代码,修改ValidateClientAuthentication()方法,继承实现GrantClientCredentials()方法。代码如下
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId, clientSecret;
context.TryGetBasicCredentials(out clientId, out clientSecret);
if (clientId == "Mobile" && clientSecret == "Xiaomi")
{
context.Validated();
}
return Task.FromResult<object>(null);
}
public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
{
var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, "Xiaomi"));
var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
context.Validated(ticket);
return base.GrantClientCredentials(context);
}
}
在 ValidateClientAuthentication() 方法中获取客户端的 client_id 与 client_secret 进行验证。在 GrantClientCredentials() 方法中对客户端进行授权,授了权就能发 access token 。这样,OAuth的ClientCredentials授权服务端代码就完成了。在ASP.NET Web API中启用OAuth的Access Token验证非常简单,只需在相应的Controller或Action加上[Authorize]标记,VS已生成部分代码,详细查看APIController文件夹下的ValuesController
下面我们在客户端调用一下,添加TestController,生成Index的View,然后在View中添加如下
$(function () {
$("#clientCredentials").on("click", function () {
GetClientCredentialsAccessToken();
});
});
function GetClientCredentialsAccessToken() {
$("#clientResult").html("Requesting");
var clientId = "Mobile";
var clientSecret = "Xiaomi";
$.ajax({
url: "/Token",
type: "post",
data: { "grant_type": "client_credentials" },
dataType: "json",
headers: {
"Authorization": "Basic " + Base64_Encode(clientId + ":" + clientSecret)
},
success: function (data) {
var accessToken = data.access_token;
GetValues(accessToken);
}
});
}
function GetValues(accessToken) {
var html = "Token:" + accessToken + "<br/><br/>";
$.ajax({
url: "/api/Values",
type: "get",
dataType: "json",
headers: {
"Authorization": "Bearer " + accessToken
},
success: function (values) {
for (var i = 0; i < values.length; i++) {
html += "values[" + i + "] :" + values[i] + "<br/>";
}
$("#clientResult").html(html);
}
});
}
function Base64_Encode(str) {
var c1, c2, c3;
var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var i = 0, len = str.length, string = '';
while (i < len) {
c1 = str.charCodeAt(i++) & 0xff;
if (i === len) {
string += base64EncodeChars.charAt(c1 >> 2);
string += base64EncodeChars.charAt((c1 & 0x3) << 4);
string += "==";
break;
}
c2 = str.charCodeAt(i++);
if (i === len) {
string += base64EncodeChars.charAt(c1 >> 2);
string += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
string += base64EncodeChars.charAt((c2 & 0xF) << 2);
string += "=";
break;
}
c3 = str.charCodeAt(i++);
string += base64EncodeChars.charAt(c1 >> 2);
string += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
string += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
string += base64EncodeChars.charAt(c3 & 0x3F);
}
return string;
}
测试结果:
Token:4iIu7HProfJaxRiklsl-ORRO3hdyrsu50pQc1Eh2-Q5lSWK8UJgz6719ZaeeULhwkMPpEFYfk6QDOOMEyFqULULk65Sb0JY29wskyZyQhKJ3_P-eSVQ2PlbKbjH9ZcziAZsVOiNLp8CfUqL5qWUq8ggVAa8KRcnlJ1DIVWnEu0XvTEDZaLDpFqqj2Cex2CX7TmTgfs07RUBdx5_3WDavNA
Ps:
传递clientId与clientSecret有两种方式,上例使用BasicAuthentication,服务端使用TryGetBasicCredentials();另外一种方式是普通From的,把参数放到Ajax的data中,如:
{“clientId”: id ,” clientSecret”:”secret”, "grant_type":"client_credentials"}
对应服务端使用TryGetFormCredentials()获取clientId和clientSecret;
推荐使用Basic Authentication方式;
使用Resource Owner Password Credentials Grant 的授权方式( grant_type=password )获取 Access Token,并以这个 Token 调用与用户相关的 Web API。
Resource Owner Password Credentials Grant 授权方式(需要验证登录用户)
因为我们刚开始时已经初始化EF,添加了一个用户信息。ApplicationOAuthProvider.cs 的GrantResourceOwnerCredentials()方法(VS帮我们自动生成了),已经实现了先关的代码
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
//调用后台的登录服务验证用户名与密码
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "用户名或密码不正确。");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager, CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
添加一个测试用的Controller
public class UsersController : ApiController
{
[Authorize]
public string GetCurrent()
{
return User.Identity.Name;
//这里可以调用后台用户服务,获取用户相关数所,或者验证用户权限进行相应的操作
}
}
在Test的index.cshtml 中新增测试的代码,如下
function GetResourceOwnerCredentialsAccessToken() {
$("#resourceOwnerresult").html("Requesting");
var clientId = "Mobile";
var clientSecret = "Xiaomi";
$.ajax({
url: "/Token",
type: "post",
data: {
"grant_type": "password",
"username": "admin@123.com",
"password": "Admin@123456"
},
dataType: "json",
headers: {
"Authorization": "Basic " + Base64_Encode(clientId + ":" + clientSecret)
},
success: function (data) {
var accessToken = data.access_token;
GetCurrentUserName(accessToken);
}
});
}
function GetCurrentUserName(accessToken) {
var html = "Token:" + accessToken + "<br/><br/>";
$.ajax({
url: "/api/User",
type: "get",
dataType: "text",
headers: {
"Authorization": "Bearer " + accessToken
},
success: function (userName) {
html += "CurrentUserName:" + userName + "<br/>";
$("#resourceOwnerresult").html(html);
}
});
}
测试结果如下
Token:Cvct6BAKix_xLNEEOfidpEG0ymJihOSjdACazP2R2tJSB3TKVnxicgQK27DzDrICUC4A7vITqhkhBRsT5cRgiow--VkbiR4we3yQ54tc6B_W8KRrdGabjase_gpmFv8oYUPGLpI82acDpcZPzCkmgLLwAq8qfkmlK7iHm5tLM6-NRR8tgfEeOVBljHq4smIXw_eVuces3sRQm-PXTD4xmp05JdrJ9zFeRb_SAN0ADqDJfJxk1nNooCtdJyeHB6r1S2D81H6P7bhRK_edneWdkX5QCNBHL8b39UKnnk0ywza6vXcWct4RaATBYOw20iNu0XR6JRx5opP9vqqC2ag8Ux6s3GHl-vAZTaYuwunmWyY0FyJJWpjNnFpPo-pkxZaK1XJxgGPpSV-JJjEZLarnq9O57hQGfbVLCd3KtWuJflo5rMnfkAz2nXlcd3gAgjIhipAIlpsG72StzN0qBL8Ml2XvV9Re1Z8U4QtrE7tzjkE
CurrentUserName:"admin@123.com"
至此,使用WebApi 的两种授权方式发放Token和两种授权方式区别,以及使用Token调用受保护的api已经介绍完了,Oauth其实还有一个refresh token,refresh token 是专用于刷新 access token 的 token。一是因为 access token 是有过期时间的,到了过期时间这个 access token 就失效,需要刷新;二是因为一个 access token 会关联一定的用户权限,如果用户授权更改了,这个 access token 需要被刷新以关联新的权限。
这个就不单独介绍了,有兴趣的可以自己研究下。
Asp.Net 高级技术群 89336052,群共享有源码
感谢:晨风的指导和教育,提供demo和文章,我只是一个发布文章的战五渣!
在WebApi中基于Owin OAuth使用授权发放Token的更多相关文章
- 在ASP.NET中基于Owin OAuth使用Client Credentials Grant授权发放Token
OAuth真是一个复杂的东东,即使你把OAuth规范倒背如流,在具体实现时也会无从下手.因此,Microsoft.Owin.Security.OAuth应运而生(它的实现代码在Katana项目中),帮 ...
- 在ASP.NET Web API 2中使用Owin OAuth 刷新令牌(示例代码)
在上篇文章介绍了Web Api中使用令牌进行授权的后端实现方法,基于WebApi2和OWIN OAuth实现了获取access token,使用token访问需授权的资源信息.本文将介绍在Web Ap ...
- 使用Resource Owner Password Credentials Grant授权发放Token
对应的应用场景是:为自家的网站开发手机 App(非第三方 App),只需用户在 App 上登录,无需用户对 App 所能访问的数据进行授权. 客户端获取Token: public string Get ...
- OAuth在WebApi中的使用,前后台分离的调用方式
前段时间由于公司架构服务层向WebApi转换,就研究了OAuth在WebApi中的使用,这中间遇到了很多坑,在此记录一下OAuth的正确使用方式. 1. OAuth是做什么的? 在网上浏览时,大家都 ...
- owin Oauth
原文:http://www.cnblogs.com/richieyang/p/4918819.html 一.什么是OAuth OAuth是一个关于授权(Authorization)的开放网络标准,目前 ...
- ASP.NET Web API与Owin OAuth:调用与用户相关的Web API
在前一篇博文中,我们通过以 OAuth 的 Client Credential Grant 授权方式(只验证调用客户端,不验证登录用户)拿到的 Access Token ,成功调用了与用户无关的 We ...
- (转)基于OWIN WebAPI 使用OAuth授权服务【客户端模式(Client Credentials Grant)】
适应范围 采用Client Credentials方式,即应用公钥.密钥方式获取Access Token,适用于任何类型应用,但通过它所获取的Access Token只能用于访问与用户无关的Open ...
- 基于OWIN WebAPI 使用OAuth授权服务【客户端验证授权(Resource Owner Password Credentials Grant)】
适用范围 前面介绍了Client Credentials Grant ,只适合客户端的模式来使用,不涉及用户相关.而Resource Owner Password Credentials Grant模 ...
- 基于OWIN WebAPI 使用OAuth授权服务【客户端模式(Client Credentials Grant)】
适应范围 采用Client Credentials方式,即应用公钥.密钥方式获取Access Token,适用于任何类型应用,但通过它所获取的Access Token只能用于访问与用户无关的Open ...
随机推荐
- DEV设计之自动流水号,DEV专家解答,自己折腾了半天也没有搞定,怪英文不好
() 老外专家给了回答,结果没有全到懂,又折腾了20分钟朋友提示才搞定 获取一个自动增加1的流水号值, 第一个参数是本事的数据库连接对象,第2个参数是也这个值为唯一标识返回来一个增量的值,第三个好像没 ...
- python json模块
import json dic = {"name":"liao","age":18} data = json.dumps(dic) #转化为 ...
- javascript里的封装
用javascript闭包的特性,可以模拟实现私有变量.私有方法. var myObject = =(function(){ var privateValue; function privateMet ...
- button、label、textfield、页面跳转、传值
.AppDelegate.m #import “OneViewController.h” //一打开就运行的 -(BOOL)application:(UIApplication *)applicati ...
- spring官网改版后,如何下载jar包
http://repo.springsource.org/libs-release-local/org/springframework 通过以上地址,可树型查看所有发布的包 问题是,官网页面上为何找不 ...
- 堆排序Heap sort
堆排序有点小复杂,分成三块 第一块,什么是堆,什么是最大堆 第二块,怎么将堆调整为最大堆,这部分是重点 第三块,堆排序介绍 第一块,什么是堆,什么是最大堆 什么是堆 这里的堆(二叉堆),指得不是堆栈的 ...
- JavaScript的Ajax请求示例
//创建XMLHttpRequest对象 var request = false; try { request = new ...
- 46. Permutations 回溯算法
https://leetcode.com/problems/permutations/ 求数列的所有排列组合.思路很清晰,将后面每一个元素依次同第一个元素交换,然后递归求接下来的(n-1)个元素的全排 ...
- 基于Chrome内核(WebKit.net)定制开发DoNet浏览器
1. 源起 a) 定制.Net浏览器 本人是一名C#开发者,而作为C#开发者,做客户端应用中最头痛的一件事就是没有一个好的UI解决方案, WinFrom嘛,效率虽然还不错,但是做一些特殊 ...
- [UCSD白板题] Compute the Edit Distance Between Two Strings
Problem Introduction The edit distinct between two strings is the minimum number of insertions, dele ...