当前位置:网站首页>AspNet.WebApi.Owin 自定义Token请求参数
AspNet.WebApi.Owin 自定义Token请求参数
2022-08-01 05:11:00 【彼岸大洋】
Asp.NET WebAPI中的授权验证有很多:
例如选用Bearer Token验证:利用Asp.Net Owin实现:基本套路为:(宿主Winform程序为例)
1:nuget owin相关的安装包:
Microsoft.AspNet.WebApi.OwinSelfHost
Microsoft.Owin.Security.OAuth
Microsoft.AspNet.WebApi.Owin
其他相关依赖包自动下载;
2:开启webapi服务:
Microsoft.Owin.Hosting.WebApp.Start<Startup>("http://127.0.0.1:8996/");
Startup类如下:
public class Startup
{
public void Configuration(IAppBuilder app)
{
SimpleAuthorizationProvider provider = new SimpleAuthorizationProvider();
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/auth/signin"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = provider,
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions);
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}"
);
app.UseWebApi(config);
}
}
SimpleAuthorizationProvider .cs
/// <summary>
/// Token验证
/// </summary>
public class SimpleAuthorizationProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
await Task.Factory.StartNew(() => context.Validated());
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
await Task.Factory.StartNew(() => context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" }));
string userName = context.UserName;
string pwd = context.Password;
if (userName == "test" && pwd == "1")
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
context.Validated(identity);
}
else
{
context.SetError("invalid_grant", "用户和密码验证失败!");
}
}
}
3:Postman验证:
其中:grant_type 和 username、password为固定的参数,且grant_type固定为"password",否则请求token不成功;
至于为何是这样,参考Owin框架源码分析就可以知道原因:
源码地址:https://github.com/aspnet/AspNetKatana
源码位置:TokenEndpointRequest.cs 构造函数处
重点来了,如果希望改变获取Token的参数,该如何处理呢?
解决办法如下:
步骤1:找到源码下如下图所示两个文件:原样复制过来:
步骤2:把如上两个文件粘贴到项目中,重命名:如:MyOAuthAuthorizationServerHandler和
MyOAuthAuthorizationServerMiddleware
步骤3:修改Setup 中的Configuration方法:
public void Configuration(IAppBuilder app)
{
SimpleAuthorizationProvider provider = new SimpleAuthorizationProvider();
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/auth/signin"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
Provider = provider,
};
//app.UseOAuthAuthorizationServer(OAuthServerOptions);
//app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions() { });
app.Use(typeof(MyOAuthAuthorizationServerMiddleware), app, OAuthServerOptions);
app.UseOAuthBearerTokens(OAuthServerOptions);
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}"
);
app.UseWebApi(config);
}
步骤4:修改 MyOAuthAuthorizationServerMiddleware 下获取HttpRequest中 Body参数的代码:
如下所示:
private async Task InvokeTokenEndpointAsync()
{
DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
// remove milliseconds in case they don't round-trip
currentUtc = currentUtc.Subtract(TimeSpan.FromMilliseconds(currentUtc.Millisecond));
IDictionary<string, string[]> formDic = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
formDic.Add(Constants.Parameters.GrantType, new string[] { "password" });
using (var reader = new StreamReader(Request.Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 4 * 1024, leaveOpen: true))
{
string text = await reader.ReadToEndAsync();
var userInfo= JsonConvert.DeserializeObject<UserInfo>(text);
formDic.Add(Constants.Parameters.Username, new string[] { userInfo.User });
formDic.Add(Constants.Parameters.Password, new string[] { userInfo.Pwd });
}
IFormCollection form=new FormCollection(formDic);
//以下是原来的代码
//IFormCollection form = await Request.ReadFormAsync();
var clientContext = new OAuthValidateClientAuthenticationContext(Context, Options, form);
await Options.Provider.ValidateClientAuthentication(clientContext);
//....省略后面的代码
}
关键思路就是读取Body Stream流,然后解析得到Json字符串,转成对象后,给IFormCollection赋值实例化。此后继续Owin框架的功能流程不变;
实现效果如下图所示:
如果要自定义返回的Json格式呢,例如变成下面这样:
同理:在最后的返回结果处,修改成想要的格式:
private async Task InvokeTokenEndpointAsync()
{
DateTimeOffset currentUtc = Options.SystemClock.UtcNow;
// remove milliseconds in case they don't round-trip
currentUtc = currentUtc.Subtract(TimeSpan.FromMilliseconds(currentUtc.Millisecond));
IDictionary<string, string[]> formDic = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
formDic.Add(Constants.Parameters.GrantType, new string[] { "password" });
using (var reader = new StreamReader(Request.Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 4 * 1024, leaveOpen: true))
{
string text = await reader.ReadToEndAsync();
var userInfo= JsonConvert.DeserializeObject<UserInfo>(text);
formDic.Add(Constants.Parameters.Username, new string[] { userInfo.User });
formDic.Add(Constants.Parameters.Password, new string[] { userInfo.Pwd });
}
IFormCollection form=new FormCollection(formDic);
//以下是原来的代码
//IFormCollection form = await Request.ReadFormAsync();
var clientContext = new OAuthValidateClientAuthenticationContext(Context, Options, form);
await Options.Provider.ValidateClientAuthentication(clientContext);
if (!clientContext.IsValidated)
{
_logger.WriteError("clientID is not valid.");
if (!clientContext.HasError)
{
clientContext.SetError(Constants.Errors.InvalidClient);
}
await SendErrorAsJsonAsync(clientContext);
return;
}
var tokenEndpointRequest = new TokenEndpointRequest(form);
var validatingContext = new OAuthValidateTokenRequestContext(Context, Options, tokenEndpointRequest, clientContext);
AuthenticationTicket ticket = null;
if (tokenEndpointRequest.IsAuthorizationCodeGrantType)
{
// Authorization Code Grant http://tools.ietf.org/html/rfc6749#section-4.1
// Access Token Request http://tools.ietf.org/html/rfc6749#section-4.1.3
ticket = await InvokeTokenEndpointAuthorizationCodeGrantAsync(validatingContext, currentUtc);
}
else if (tokenEndpointRequest.IsResourceOwnerPasswordCredentialsGrantType)
{
// Resource Owner Password Credentials Grant http://tools.ietf.org/html/rfc6749#section-4.3
// Access Token Request http://tools.ietf.org/html/rfc6749#section-4.3.2
ticket = await InvokeTokenEndpointResourceOwnerPasswordCredentialsGrantAsync(validatingContext, currentUtc);
}
else if (tokenEndpointRequest.IsClientCredentialsGrantType)
{
// Client Credentials Grant http://tools.ietf.org/html/rfc6749#section-4.4
// Access Token Request http://tools.ietf.org/html/rfc6749#section-4.4.2
ticket = await InvokeTokenEndpointClientCredentialsGrantAsync(validatingContext, currentUtc);
}
else if (tokenEndpointRequest.IsRefreshTokenGrantType)
{
// Refreshing an Access Token
// http://tools.ietf.org/html/rfc6749#section-6
ticket = await InvokeTokenEndpointRefreshTokenGrantAsync(validatingContext, currentUtc);
}
else if (tokenEndpointRequest.IsCustomExtensionGrantType)
{
// Defining New Authorization Grant Types
// http://tools.ietf.org/html/rfc6749#section-8.3
ticket = await InvokeTokenEndpointCustomGrantAsync(validatingContext, currentUtc);
}
else
{
// Error Response http://tools.ietf.org/html/rfc6749#section-5.2
// The authorization grant type is not supported by the
// authorization server.
_logger.WriteError("grant type is not recognized");
validatingContext.SetError(Constants.Errors.UnsupportedGrantType);
}
if (ticket == null)
{
await SendErrorAsJsonAsync(validatingContext);
return;
}
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(Options.AccessTokenExpireTimeSpan);
var tokenEndpointContext = new OAuthTokenEndpointContext(Context, Options, ticket, tokenEndpointRequest);
await Options.Provider.TokenEndpoint(tokenEndpointContext);
if (tokenEndpointContext.TokenIssued)
{
ticket = new AuthenticationTicket(tokenEndpointContext.Identity, tokenEndpointContext.Properties);
}
else
{
_logger.WriteError("Token was not issued to tokenEndpointContext");
validatingContext.SetError("invalid_grant");
await SendErrorAsJsonAsync(validatingContext);
return;
}
var accessTokenContext = new AuthenticationTokenCreateContext(Context, Options.AccessTokenFormat, ticket);
await Options.AccessTokenProvider.CreateAsync(accessTokenContext);
string accessToken = accessTokenContext.Token;
if (string.IsNullOrEmpty(accessToken))
{
accessToken = accessTokenContext.SerializeTicket();
}
DateTimeOffset? accessTokenExpiresUtc = ticket.Properties.ExpiresUtc;
var refreshTokenCreateContext = new AuthenticationTokenCreateContext(Context, Options.RefreshTokenFormat, accessTokenContext.Ticket);
await Options.RefreshTokenProvider.CreateAsync(refreshTokenCreateContext);
string refreshToken = refreshTokenCreateContext.Token;
var tokenEndpointResponseContext = new OAuthTokenEndpointResponseContext(Context, Options, ticket, tokenEndpointRequest, accessToken, tokenEndpointContext.AdditionalResponseParameters);
await Options.Provider.TokenEndpointResponse(tokenEndpointResponseContext);
var memory = new MemoryStream();
byte[] body;
/*
*
*public T Data { get; set; }
public bool IsError { get; set; }
public string Message { get; set; }
public int ErrorCode { get; set; }
*/
var timeConverter = new IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" };
using (var writer = new JsonTextWriter(new StreamWriter(memory)))
{
writer.WriteStartObject();
writer.WritePropertyName("Data");
TokenInfo tokenInfo = new TokenInfo();
tokenInfo.Token = accessToken;
tokenInfo.TokenType = "bearer";
JsonSerializer serializer = new JsonSerializer();
//var jsn = new JObject();
//jsn["Data"] = JObject.FromObject( tokenInfo);
serializer.Serialize(writer, tokenInfo);
writer.WritePropertyName("IsError");
writer.WriteValue(false);
writer.WritePropertyName("Message");
writer.WriteValue(String.Empty);
writer.WritePropertyName("ErrorCode");
writer.WriteValue(0);
if (accessTokenExpiresUtc.HasValue)
{
TimeSpan? expiresTimeSpan = accessTokenExpiresUtc - currentUtc;
var expiresIn = (long)expiresTimeSpan.Value.TotalSeconds;
if (expiresIn > 0)
{
writer.WritePropertyName("expires_in");
writer.WriteValue(expiresIn);
}
}
if (!String.IsNullOrEmpty(refreshToken))
{
writer.WritePropertyName("refresh_token");
writer.WriteValue(refreshToken);
}
foreach (var additionalResponseParameter in tokenEndpointResponseContext.AdditionalResponseParameters)
{
writer.WritePropertyName(additionalResponseParameter.Key);
writer.WriteValue(additionalResponseParameter.Value);
}
writer.WriteEndObject();
writer.Flush();
body = memory.ToArray();
}
Response.ContentType = "application/json;charset=UTF-8";
Response.Headers.Set("Cache-Control", "no-cache");
Response.Headers.Set("Pragma", "no-cache");
Response.Headers.Set("Expires", "-1");
Response.ContentLength = body.Length;
await Response.WriteAsync(body, Request.CallCancelled);
}
参考#region 自定义格式部分代码
边栏推荐
- Selenium:上传、下载文件
- (2022牛客多校四)A-Task Computing (排序+动态规划)
- Lawyer Interpretation | Guns or Roses?Talking about Metaverse Interoperability from the Battle of Big Manufacturers
- PAT乙级 1002 写出这个数
- The difference between scheduleWithFixedDelay and scheduleAtFixedRate
- Selenium:鼠标、键盘事件
- MySQL实践总结-
- I met a shell script
- PaddleX部署推理模型和GUI界面测试结果不一致的解决方法
- USB3.0:VL817Q7-C0的LAYOUT指南(三)
猜你喜欢
USB3.0:VL817Q7-C0的LAYOUT指南(三)
II. Binary tree to Offer 68 - recent common ancestor
I met a shell script
(2022牛客多校四)H-Wall Builder II(思维)
(2022牛客多校四)N-Particle Arts(思维)
Robot_Framework:断言
Robot_Framework: commonly used built-in keywords
(2022牛客多校四)K-NIO‘s Sword(思维)
typescript24-类型推论
MySQL-DML language-database operation language-insert-update-delete-truncate
随机推荐
Selenium:元素等待
UE4 rays flashed from mouse position detection
UE4 制作遇到的问题
pytroch、tensorflow对比学习—功能组件(数据管道、回调函数、特征列处理)
MySQL-DML language-database operation language-insert-update-delete-truncate
Selenium: Introduction
MySQL Practice Summary -
pytroch、tensorflow对比学习—搭建模型范式(构建模型方法、训练模型范式)
In the shake database, I want to synchronize the data of the source db0 to the destination db5, how to set the parameters?
律师解读 | 枪炮还是玫瑰?从大厂之争谈元宇宙互操作性
LeetCode 387. 字符串中的第一个唯一字符
WPF入门项目必知必会-初步了解数据绑定 binding
类神经网络训练不起来怎么办
USB3.0:VL817Q7-C0的LAYOUT指南(三)
微信小程序获取手机号phonenumber.getPhoneNumber接口开发
[target detection] YOLOv7 theoretical introduction + practical test
API Design Notes: The pimpl trick
Risk strategy important steps of tuning method
Selenium:弹窗处理
(Codeforce 757)E. Bash Plays with Functions(积性函数)