在ASP.NET中实现OAuth2.0(二)之打造自己的API安全策略
1、场景介绍
公司开发了一款APP产品,前期提供的api接口都是裸奔状态
举个例子:想要获取某一个用户的数据,只需要传递该用户的ID就可以拿走数据(说多了都是泪)
现在想给这些接口穿个衣服,加个壳(对客户端进行授权)
2、业务实现
> 搭建授权服务器和资源服务器
> 给App客户端发放AppId和AppSecret
> 用户向App客户端提供自己的账号和密码
> App客户端将AppId、AppSecret、账号和密码提交到授权服务器
> 授权服务器通过授权,发放token和refresh_token
> 客户端通过token与资源服务器进行对接,并对token进行管理,防止失效
3、代码实现
1)用vs2015/vs2013新建mvc或者api项目,vs会生成一堆oauth代码(备注:vs2012的项目,需要引用相关dll并手动补充相关代码)
2)打开Startup.Auth.cs,将不用代码注释,打开Startup.cs对oauth进行配置
using Microsoft.Owin;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System; [assembly: OwinStartupAttribute(typeof(OSA.Server.Startup))]
namespace OSA.Server
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app); var OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/token"),
Provider = new Code.AuthorizationServerProvider(),
RefreshTokenProvider = new Code.RefreshTokenProvider(),
//AccessTokenFormat = new Code.SecureDataFormat(),//自定义access_token信息序列化加密格式
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(),
AllowInsecureHttp = true,
}; app.UseOAuthBearerTokens(OAuthOptions);
}
}
}
3)重写OAuthAuthorizationServerProvider,搭建授权服务器,定义自己的授权方式
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OAuth;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web; namespace OSA.Server.Code
{
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
/// <summary>
/// 第三方应用身份验证
/// </summary>
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret; //1.身份验证凭证在请求头,使用context.TryGetBasicCredentials(clientId, clientSecret)获取信息
context.TryGetBasicCredentials(out clientId, out clientSecret); //2.身份验证凭证在Post参数中,使用context.TryGetFormCredentials(clientId, clientSecret)获取信息
//context.TryGetFormCredentials(out clientId, out clientSecret); //读取数据仓储,判断是否为合法的第三方应用
var client = new Data.Client().GetDetail(clientId); if (client == null || client.Secret != clientSecret)
{
context.SetError("非法的身份凭证信息!");
}
else
{
//refresh_token持久化的时候使用
context.OwinContext.Set<string>("as:client_id", clientId);
context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString()); //TODO:有疑问……
context.Validated(clientId);
} return base.ValidateClientAuthentication(context);
} /// <summary>
/// 授予第三方应用凭证
/// </summary>
public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
{
var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType); oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, "ClientCredentials")); //var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties()); //为了验证client_id,需要在 GrantClientCredentials() 重载方法中保存client_id至context.Ticket
var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties(new Dictionary<string, string>
{
{ "as:client_id", context.ClientId }
})); context.Validated(ticket); return base.GrantClientCredentials(context);
} /// <summary>
/// 授予资源所有者凭证
/// </summary>
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
//读取数据仓储,判断是否为用户账号和密码是否有效
var entity = new Data.Member().GetDetail(context.UserName, context.Password); if (entity == null)
{
context.SetError("非法的身份凭证信息!");
}
else
{
var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType); oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, entity.Id));
oAuthIdentity.AddClaim(new Claim(ClaimTypes.UserData, entity.ToJsonByJsonNet())); //为了验证client_id,需要在 GrantClientCredentials() 重载方法中保存client_id至context.Ticket
var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties(new Dictionary<string, string>
{
{ "as:client_id", context.ClientId }
})); context.Validated(ticket); await base.GrantResourceOwnerCredentials(context);
} } /// <summary>
/// 授予RefreshToken凭证
/// </summary>
public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
//验证client_id
//var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
//var currentClient = context.ClientId; //if (originalClient != currentClient)
//{
// context.Rejected();
// return;
//} var newId = new ClaimsIdentity(context.Ticket.Identity);
newId.AddClaim(new Claim("newClaim", "refreshToken")); var newTicket = new AuthenticationTicket(newId, context.Ticket.Properties);
context.Validated(newTicket); await base.GrantRefreshToken(context);
}
}
}
4)重写AuthenticationTokenProvider,搭建授权服务器,定义自己的授权方式
using Microsoft.Owin.Security.Infrastructure;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Web; namespace OSA.Server.Code
{
/// <summary>
/// refresh_token会被第三方应用使用,用来维持access_token的持续可用,
/// 因此需要将refresh_token持久化,避免服务重启后,refresh_token失效问题
/// </summary>
public class RefreshTokenProvider : AuthenticationTokenProvider
{
public override void Create(AuthenticationTokenCreateContext context)
{
var clietId = context.OwinContext.Get<string>("as:client_id");
if (string.IsNullOrEmpty(clietId)) return; var refreshTokenLifeTime = context.OwinContext.Get<string>("as:clientRefreshTokenLifeTime");
if (string.IsNullOrEmpty(refreshTokenLifeTime)) return; string tokenValue = Guid.NewGuid().ToString("n"); var refreshToken = new Data.RefreshToken()
{
Id = tokenValue,
ClientId = clietId,
UserName = context.Ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddDays(Convert.ToDouble(refreshTokenLifeTime)),
ProtectedTicket = context.SerializeTicket()
}; context.Ticket.Properties.IssuedUtc = DateTime.UtcNow;
context.Ticket.Properties.ExpiresUtc = refreshToken.ExpiresUtc; if (!refreshToken.Save()) return; context.SetToken(tokenValue);
} public override void Receive(AuthenticationTokenReceiveContext context)
{
var refreshToken = new Data.RefreshToken().GetDetail(context.Token); if (refreshToken != null)
{
context.DeserializeTicket(refreshToken.ProtectedTicket);
}
}
}
}
5)对用户、客户端、Refreshtoken进行持久化
/// <summary>
/// 持久化第三方应用类
/// </summary>
public class Client
{
/// <summary>
/// 应用Id
/// </summary>
public string Id { get; set; } /// <summary>
/// 应用秘钥
/// </summary>
public string Secret { get; set; } /// <summary>
/// 应用名称
/// </summary>
public string Name { get; set; } /// <summary>
/// 是否激活
/// </summary>
public bool IsActive { get; set; } /// <summary>
/// refresh_token使用期
/// </summary>
public int RefreshTokenLifeTime { get; set; } public Client GetDetail(string id)
{
if (id != "yk1ec4b1ff655c5709")
return null; return new Client()
{
Id = id,
Secret = "4fd823ea538dcdc90afeeeac3bfc5b70",
Name = "App应用",
IsActive = true,
RefreshTokenLifeTime = ,
};
}
}
/// <summary>
/// 持久化用户信息
/// </summary>
public class Member
{
public string Id { get; set; } public string Account { get; set; } public string Nickname { get; set; } public string Role { get; set; } public Member GetDetail(string id)
{
if (id == "1f04166e7b7441e6876916abf00c4f05")
{
return new Member()
{
Id = "1f04166e7b7441e6876916abf00c4f05",
Account = "手机号",
Nickname = "荒古禁地",
Role = "administrator"
};
} return null;
} public Member GetDetail(string account, string password)
{
if (account == "手机号" && password == "")
{
return new Member()
{
Id = "1f04166e7b7441e6876916abf00c4f05",
Account = "手机号",
Nickname = "荒古禁地",
Role = "administrator"
};
} return null;
}
}
/// <summary>
/// 持久化RefreshToken类
/// </summary>
public class RefreshToken
{
/// <summary>
/// refresh_token值
/// </summary>
public string Id { get; set; } /// <summary>
/// 用户账号
/// </summary>
public string UserName { get; set; } /// <summary>
/// 第三方应用Id
/// </summary>
public string ClientId { get; set; } /// <summary>
/// 发布时间
/// </summary>
public DateTime IssuedUtc { get; set; } /// <summary>
/// 有效时间
/// </summary>
public DateTime ExpiresUtc { get; set; } /// <summary>
/// access_token信息
/// </summary>
public string ProtectedTicket { get; set; } public RefreshToken GetDetail(string id)
{
return list.FirstOrDefault(x => x.Id == id);
} private static List<RefreshToken> list = new List<RefreshToken>(); public bool Save()
{
list.Add(this);
return true;
}
}
6)封装资源接口,获取token
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http; namespace OSA.Server.Controllers
{
public class TokenController : ApiController
{
protected HttpClient _httpClient; /// <summary>
/// 构造函数
/// </summary>
public TokenController()
{
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri("http://localhost:5000/");
} /// <summary>
/// 发放访问令牌
/// </summary>
/// <param name="grant_type">访问类型</param>
/// <param name="appid">应用Id</param>
/// <param name="secret">应用秘钥</param>
/// <returns>访问令牌</returns>
public async Task<object> GetClientCredentialsToken(string grant_type, string appid, string secret)
{
if (grant_type == "client_credentials")
return await CreateClientCredentialsAccessToken(appid, secret); return "不支持的授权类型";
} /// <summary>
/// 发放访问令牌
/// </summary>
/// <param name="grant_type">访问类型</param>
/// <param name="appid">应用Id</param>
/// <param name="secret">应用秘钥</param>
/// <param name="username">用户账号</param>
/// <param name="password">用户密码</param>
/// <returns>访问令牌</returns>
public async Task<object> GetPasswordToken(string grant_type, string appid, string secret, string username, string password)
{
if (grant_type == "password")
return await CreatePasswordAccessToken(appid, secret, username, password); return "不支持的授权类型";
} /// <summary>
/// 发放访问令牌
/// </summary>
/// <param name="appid">应用Id</param>
/// <param name="secret">应用秘钥</param>
/// <param name="refresh_token">刷新令牌</param>
/// <returns>访问令牌</returns>
public async Task<object> GetRefreshToken(string appid, string secret, string refresh_token)
{
return await GetAccessTokenByRefreshToken(appid, secret, refresh_token);
} /// <summary>
/// 发放访问令牌(client_credentials模式)
/// </summary>
private async Task<object> CreateClientCredentialsAccessToken(string appid, string secret)
{
//将身份验证凭证信息放入请求头中
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(appid + ":" + secret))); //拼装Post参数信息
var parameters = new Dictionary<string, string>();
parameters.Add("grant_type", "client_credentials"); //请求输出访问令牌
var responseResult = _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters)).Result;
var responseValue = await responseResult.Content.ReadAsStringAsync(); return responseValue;
} /// <summary>
/// 发放访问令牌(password模式)
/// </summary>
private async Task<object> CreatePasswordAccessToken(string appid, string secret, string username, string password)
{
//将身份验证凭证信息放入请求头中
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(appid + ":" + secret))); //拼装Post参数信息
var parameters = new Dictionary<string, string>();
parameters.Add("grant_type", "password");
parameters.Add("username", username);
parameters.Add("password", password); //请求输出访问令牌
var responseResult = _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters)).Result;
var responseValue = await responseResult.Content.ReadAsStringAsync(); return responseValue;
} /// <summary>
/// 发放访问令牌(refresh_token模式)
/// </summary>
private async Task<object> GetAccessTokenByRefreshToken(string appid, string secret, string refresh_token)
{
//将身份验证凭证信息放入请求头中
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(appid + ":" + secret))); //拼装Post参数信息
var parameters = new Dictionary<string, string>();
parameters.Add("grant_type", "refresh_token");
parameters.Add("refresh_token", refresh_token); //请求输出访问令牌
var response = await _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters));
var responseValue = await response.Content.ReadAsStringAsync(); return responseValue;
}
}
}
7)封装资源接口,获取登录用户信息
namespace OSA.Server.Controllers
{
[Authorize]
public class BasicController : ApiController
{
public string AuthorizationId
{
get
{
return User.Identity.Name; //var claimsIdentity = User.Identity as System.Security.Claims.ClaimsIdentity; //var claim = claimsIdentity.FindFirst(System.Security.Claims.ClaimTypes.UserData); //return claim.Value.ToObjectByJsonNet<Data.Member>().Id;
}
}
}
} namespace OSA.Server.Controllers
{
public class AccountController : BasicController
{
public object Get(string id)
{
return new Data.Member().GetDetail(AuthorizationId);
}
}
}
8)为了让做app的同学,方便调用接口,对接口进行二次封装
namespace OSA.Client.Api
{
public class BasicController : ApiController
{
protected HttpClient _httpClient; /// <summary>
/// 构造函数
/// </summary>
public BasicController()
{
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri("http://localhost:5000/");
}
}
} namespace OSA.Client.Api
{
public class TokenController : ApiController
{
protected HttpClient _httpClient; /// <summary>
/// 构造函数
/// </summary>
public TokenController()
{
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri("http://localhost:5000/");
} /// <summary>
/// client_credentials模式
/// </summary>
/// <param name="appid">应用Id</param>
/// <param name="secret">应用密钥</param>
/// <returns>访问令牌</returns>
public Task<object> GetClientCredentialsAccessToken(string appid, string secret)
{
var parameters = new Dictionary<string, string>();
parameters.Add("grant_type", "client_credentials");
parameters.Add("appid", appid);
parameters.Add("secret", secret); System.Text.StringBuilder url = new System.Text.StringBuilder(); url.Append("/api/token?"); foreach (var v in parameters)
{
url.AppendFormat("{0}={1}&", v.Key, v.Value);
} return _httpClient.GetAsync(url.ToString()).Result.Content.ReadAsAsync<object>(); //return _httpClient.PostAsync("/api/token", new FormUrlEncodedContent(parameters)).Result.Content.ReadAsStringAsync().Result;
} /// <summary>
/// 发放访问令牌
/// </summary>
/// <param name="appid">应用Id</param>
/// <param name="secret">应用秘钥</param>
/// <param name="username">用户账号</param>
/// <param name="password">用户密码</param>
/// <returns>访问令牌</returns>
public Task<object> GetPasswordToken(string appid, string secret, string username, string password)
{
var parameters = new Dictionary<string, string>();
parameters.Add("grant_type", "password");
parameters.Add("appid", appid);
parameters.Add("secret", secret);
parameters.Add("username", username);
parameters.Add("password", password); System.Text.StringBuilder url = new System.Text.StringBuilder(); url.Append("/api/token?"); foreach (var v in parameters)
{
url.AppendFormat("{0}={1}&", v.Key, v.Value);
} return _httpClient.GetAsync(url.ToString()).Result.Content.ReadAsAsync<object>(); //return _httpClient.PostAsync("/api/token", new FormUrlEncodedContent(parameters)).Result.Content.ReadAsStringAsync().Result;
} /// <summary>
/// 刷新access_token(适用于password模式)
/// </summary>
/// <param name="appid">应用Id</param>
/// <param name="secret">应用密钥</param>
/// <param name="refresh_token">refresh_token</param>
/// <returns>访问令牌</returns>
public Task<object> GetAccessTokenByRefreshToken(string appid, string secret, string refresh_token)
{
var parameters = new Dictionary<string, string>();
parameters.Add("appid", appid);
parameters.Add("secret", secret);
parameters.Add("grant_type", "refresh_token");
parameters.Add("refresh_token", refresh_token); System.Text.StringBuilder url = new System.Text.StringBuilder(); url.Append("/api/token?"); foreach (var v in parameters)
{
url.AppendFormat("{0}={1}&", v.Key, v.Value);
} return _httpClient.GetAsync(url.ToString()).Result.Content.ReadAsAsync<object>(); //return _httpClient.PostAsync("/api/token", new FormUrlEncodedContent(parameters)).Result.Content.ReadAsStringAsync().Result;
}
}
} namespace OSA.Client.Api
{
public class AccountController : BasicController
{
public Task<object> Get(string token)
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); return _httpClient.GetAsync("/api/account/1").Result.Content.ReadAsAsync<object>();
}
}
}
在ASP.NET中实现OAuth2.0(二)之打造自己的API安全策略的更多相关文章
- 在ASP.NET中实现OAuth2.0(一)之了解OAuth
1.什么是OAuth2.0 是一个开放授权标准,允许用户让第三方应用访问该用户在某一个网站或平台上的私密资源(如照片.视频.联系人等),而无须将用户名和密码提供给第三方应用 2.OAuth2.0授权模 ...
- ASP.NET Core实现OAuth2.0的ResourceOwnerPassword和ClientCredentials模式
前言 开发授权服务框架一般使用OAuth2.0授权框架,而开发Webapi的授权更应该使用OAuth2.0授权标准,OAuth2.0授权框架文档说明参考:https://tools.ietf.org/ ...
- ASP.NET WebApi 基于OAuth2.0实现Token签名认证
一.课程介绍 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将是我们需要思考的问题.为了保护我们的WebApi数 ...
- 在PHP应用中简化OAuth2.0身份验证集成:OAuth 2.0 Client
在PHP应用中简化OAuth2.0身份验证集成:OAuth 2.0 Client 阅读目录 验证代码流程 Refreshing a Token Built-In Providers 这个包能够让你 ...
- ASP.NET Core实现OAuth2.0的AuthorizationCode模式
前言 在上一篇中实现了resource owner password credentials和client credentials模式:http://www.cnblogs.com/skig/p/60 ...
- ASP.NET中进行消息处理(MSMQ) 二(转)
在我上一篇文章<ASP.NET中进行消息处理(MSMQ)一>里对MSMQ做了个通俗的介绍,最后以发送普通文本消息和复杂的对象消息为例介绍了消息队列的使用. 本文在此基础上继续介 ...
- ASP.NET中进行消息处理(MSMQ) 二
在我上一篇文章<ASP.NET中进行消息处理(MSMQ)一>里对MSMQ做了个通俗的介绍,最后以发送普通文本消息和复杂的对象消息为例介绍了消息队列的使用. 本文在此基础上继续介绍MSMQ的 ...
- ASP.NET没有魔法——ASP.NET MVC使用Oauth2.0实现身份验证
随着软件的不断发展,出现了更多的身份验证使用场景,除了典型的服务器与客户端之间的身份验证外还有,如服务与服务之间的(如微服务架构).服务器与多种客户端的(如PC.移动.Web等),甚至还有需要以服务的 ...
- ASP.NET MVC使用Oauth2.0实现身份验证
随着软件的不断发展,出现了更多的身份验证使用场景,除了典型的服务器与客户端之间的身份验证外还有,如服务与服务之间的(如微服务架构).服务器与多种客户端的(如PC.移动.Web等),甚至还有需要以服务的 ...
随机推荐
- vc不用IDE编译方法
一个EXE是如何形成的 比如一个源程序有以下两个文件. 1.c 1.rc 首先cl.exe 会把源代码编译为1.obj rc.exe会把1.rc编译为1.res link.exe会把1.obj 1.r ...
- pb中读取大文本数据
string ls_FileName,lb_FileDatas,lb_FileData long ll_FileLen,ll_Handle,ll_Loop,ll_Bytes,ll_Loops,ll_ ...
- 部署Ossim
650) this.width=650;" title="29-1.jpg" alt="095310750.jpg" src="http:/ ...
- ionic cordova plugin simple demo
要用cordova plugin 的话还是需要设置一下的 1. 下载 ng-cordova.js download the zip file here 2. 在index.html 中引用 (cord ...
- sql case when 多条件
when 'ChangeProductName'= case --联名借姓名 --when a.ChangeProductName is not null then (substr ...
- Ubuntu 14.04 – How to install xrdp in Ubuntu 14.04
http://c-nergy.be/blog/?p=5305 Hello World, Ubuntu 14.04 has been released on April 17th 2014 and we ...
- 为什么swing不适合做桌面软件
http://www.zhihu.com/question/19608871 我最近几年做的项目清一色的都是HTML5了,这篇<基于HTML5的电信网管3D机房监控应用>供参考,HTML5 ...
- CentOS 6.4安装Kangle面板
kangle web server一键安装包是一个用Linux Shell编写的可以为CentOS 6 VPS(VDS)或独立主机安装kangle web server(kangle,easypane ...
- webservice简介以及简单使用
本文主要是简单的介绍webservice是什么.webservice的由来.webservice的作用,以及后面会介绍webservice的使用. webservice是什么? 目前,Web serv ...
- 收缩SQL数据库日志
各位同学,相信大家在使用SQL数据库时,常常会遇到日志文件比数据库文件还在大的情况.以下有一简单的办法,可以快速的删除日志档.使用其大小变为540K. 供各位参考. DUMP TRANSACTION ...