WeixinController:
public class WeixinController : Controller
{
private readonly IAbpWebApiClient _abpWebApiClient;
private string baseUrl = "http://shengjie.azurewebsites.net/";
private string loginUrl = "/account/login";
private string webapiUrl = "/api/services/app/User/GetUsers";
private string abpTokenUrl = "/api/Account/Authenticate";
private string oAuthTokenUrl = "/oauth/token";
private string user = "admin";
private string pwd = "123qwe";
public WeixinController()
{
_abpWebApiClient = new AbpWebApiClient();
}
}
IAbpWebApiClient是对
HttpClient的封装,用于发送 HTTP 请求和接收HTTP 响应。
CookieBasedAuth方法,来完成登录认证,代码如下:
public async Task CookieBasedAuth()
{
Uri uri = new Uri(baseUrl + loginUrl);
var handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.None, UseCookies = true };
using (var client = new HttpClient(handler))
{
client.BaseAddress = uri;
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
var content = new FormUrlEncodedContent(new Dictionary<string, string>()
{
{"TenancyName", "Default"},
{"UsernameOrEmailAddress", user},
{"Password", pwd }
});
//获取token保存到cookie,并设置token的过期日期
var result = await client.PostAsync(uri, content);
string loginResult = await result.Content.ReadAsStringAsync();
var getCookies = handler.CookieContainer.GetCookies(uri);
foreach (Cookie cookie in getCookies)
{
_abpWebApiClient.Cookies.Add(cookie);
}
}
}
HttpClientHandler属性
UseCookie = true,使用Cookie;
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));用来指定接受的返回值;
FormUrlEncodedContent进行传参;
var getCookies = handler.CookieContainer.GetCookies(uri);获取返回的Cookie,并添加到
_abpWebApiClient.Cookies的集合中,以便下次直接携带cookie信息访问webapi。
_abpWebApiClient.Cookies中,我们只需post一个请求到目标api即可。
public async Task<PartialViewResult> SendRequestBasedCookie()
{
await CookieBasedAuth();
return await GetUserList(baseUrl + webapiUrl);
}
private async Task<PartialViewResult> GetUserList(string url)
{
try
{
var users = await _abpWebApiClient.PostAsync<ListResultDto<UserListDto>>(url);
return PartialView("_UserListPartial", users.Items);
}
catch (Exception e)
{
ViewBag.ErrorMessage = e.Message;
}
return null;
}
/api/Account/Authenticate即可请求到token。然后使用token即可请求目标webapi。
public async Task<string> GetAbpToken()
{
var tokenResult = await _abpWebApiClient.PostAsync<string>(baseUrl + abpTokenUrl, new
{
TenancyName = "Default",
UsernameOrEmailAddress = user,
Password = pwd
});
this.Response.SetCookie(new HttpCookie("access_token", tokenResult));
return tokenResult;
}
Authorization = Bearer token,即可。
public async Task<PartialViewResult> SendRequest()
{
var token = Request.Cookies["access_token"]?.Value;
//将token添加到请求头
_abpWebApiClient.RequestHeaders.Add(new NameValue("Authorization", "Bearer " + token));
return await GetUserList(baseUrl + webapiUrl);
}
app.UseOAuthBearerAuthentication(AccountController.OAuthBearerOptions);使用的是
Bearer token,所以我们在请求weiapi时,要在请求头中假如
Authorization信息时,使用
Bearer token的格式传输token信息(Bearer后有一个空格!)。
refresh_token来申请token即可,不需要用户再录入用户凭证申请token。
Providers文件夹,添加
SimpleAuthorizationServerProvider和
SimpleRefreshTokenProvider类。
SimpleAuthorizationServerProvider用来验证客户端的用户名和密码来颁发token;
SimpleRefreshTokenProvider用来刷新token。
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider, ITransientDependency
{
private readonly LogInManager _logInManager;
public SimpleAuthorizationServerProvider(LogInManager logInManager)
{
_logInManager = logInManager;
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
string clientId;
string clientSecret;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
var isValidClient = string.CompareOrdinal(clientId, "app") == 0 &&
string.CompareOrdinal(clientSecret, "app") == 0;
if (isValidClient)
{
context.OwinContext.Set("as:client_id", clientId);
context.Validated(clientId);
}
else
{
context.SetError("invalid client");
}
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var tenantId = context.Request.Query["tenantId"];
var result = await GetLoginResultAsync(context, context.UserName, context.Password, tenantId);
if (result.Result == AbpLoginResultType.Success)
{
//var claimsIdentity = result.Identity;
var claimsIdentity = new ClaimsIdentity(result.Identity);
claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
var ticket = new AuthenticationTicket(claimsIdentity, new AuthenticationProperties());
context.Validated(ticket);
}
}
public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
{
var originalClient = context.OwinContext.Get<string>("as:client_id");
var currentClient = context.ClientId;
// enforce client binding of refresh token
if (originalClient != currentClient)
{
context.Rejected();
return Task.FromResult<object>(null);
}
// chance to change authentication ticket for refresh token requests
var newId = new ClaimsIdentity(context.Ticket.Identity);
newId.AddClaim(new Claim("newClaim", "refreshToken"));
var newTicket = new AuthenticationTicket(newId, context.Ticket.Properties);
context.Validated(newTicket);
return Task.FromResult<object>(null);
}
private async Task<AbpLoginResult<Tenant, User>> GetLoginResultAsync(OAuthGrantResourceOwnerCredentialsContext context,
string usernameOrEmailAddress, string password, string tenancyName)
{
var loginResult = await _logInManager.LoginAsync(usernameOrEmailAddress, password, tenancyName);
switch (loginResult.Result)
{
case AbpLoginResultType.Success:
return loginResult;
default:
CreateExceptionForFailedLoginAttempt(context, loginResult.Result, usernameOrEmailAddress, tenancyName);
//throw CreateExceptionForFailedLoginAttempt(context,loginResult.Result, usernameOrEmailAddress, tenancyName);
return loginResult;
}
}
private void CreateExceptionForFailedLoginAttempt(OAuthGrantResourceOwnerCredentialsContext context,
AbpLoginResultType result, string usernameOrEmailAddress, string tenancyName)
{
switch (result)
{
case AbpLoginResultType.Success:
throw new ApplicationException("Don't call this method with a success result!");
case AbpLoginResultType.InvalidUserNameOrEmailAddress:
case AbpLoginResultType.InvalidPassword:
context.SetError(L("LoginFailed"), L("InvalidUserNameOrPassword"));
break;
// return new UserFriendlyException(("LoginFailed"), ("InvalidUserNameOrPassword"));
case AbpLoginResultType.InvalidTenancyName:
context.SetError(L("LoginFailed"), L("ThereIsNoTenantDefinedWithName", tenancyName));
break;
// return new UserFriendlyException(("LoginFailed"), string.Format("ThereIsNoTenantDefinedWithName{0}", tenancyName));
case AbpLoginResultType.TenantIsNotActive:
context.SetError(L("LoginFailed"), L("TenantIsNotActive", tenancyName));
break;
// return new UserFriendlyException(("LoginFailed"), string.Format("TenantIsNotActive {0}", tenancyName));
case AbpLoginResultType.UserIsNotActive:
context.SetError(L("LoginFailed"), L("UserIsNotActiveAndCanNotLogin", usernameOrEmailAddress));
break;
// return new UserFriendlyException(("LoginFailed"), string.Format("UserIsNotActiveAndCanNotLogin {0}", usernameOrEmailAddress));
case AbpLoginResultType.UserEmailIsNotConfirmed:
context.SetError(L("LoginFailed"), L("UserEmailIsNotConfirmedAndCanNotLogin"));
break;
// return new UserFriendlyException(("LoginFailed"), ("UserEmailIsNotConfirmedAndCanNotLogin"));
//default: //Can not fall to default actually. But other result types can be added in the future and we may forget to handle it
// //Logger.Warn("Unhandled login fail reason: " + result);
// return new UserFriendlyException(("LoginFailed"));
}
}
private static string L(string name, params object[] args)
{
//return new LocalizedString(name);
return IocManager.Instance.Resolve<ILocalizationService>().L(name, args);
}
}
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider, ITransientDependency
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString("N");
// maybe only create a handle the first time, then re-use for same client
// copy properties and set the desired lifetime of refresh token
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddYears(1)
};
var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);
//_refreshTokens.TryAdd(guid, context.Ticket);
_refreshTokens.TryAdd(guid, refreshTokenTicket);
// consider storing only the hash of the handle
context.SetToken(guid);
return Task.FromResult<object>(null);
}
public Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
return Task.FromResult<object>(null);
}
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
OAuthOptions类用来配置OAuth认证。
public class OAuthOptions
{
/// <summary>
/// Gets or sets the server options.
/// </summary>
/// <value>The server options.</value>
private static OAuthAuthorizationServerOptions _serverOptions;
/// <summary>
/// Creates the server options.
/// </summary>
/// <returns>OAuthAuthorizationServerOptions.</returns>
public static OAuthAuthorizationServerOptions CreateServerOptions()
{
if (_serverOptions == null)
{
var provider = IocManager.Instance.Resolve<SimpleAuthorizationServerProvider>();
var refreshTokenProvider = IocManager.Instance.Resolve<SimpleRefreshTokenProvider>();
_serverOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/oauth/token"),
Provider = provider,
RefreshTokenProvider = refreshTokenProvider,
AccessTokenExpireTimeSpan = TimeSpan.FromSeconds(30),
AllowInsecureHttp = true
};
}
return _serverOptions;
}
}
Startup类中,配置使用集成的OAuth2.0,代码如下:
public void Configuration(IAppBuilder app)
{
//第一步:配置跨域访问
app.UseCors(CorsOptions.AllowAll);
app.UseOAuthBearerAuthentication(AccountController.OAuthBearerOptions);
//第二步:使用OAuth密码认证模式
app.UseOAuthAuthorizationServer(OAuthOptions.CreateServerOptions());
//第三步:使用Abp
app.UseAbp();
//省略其他代码
}
Microsoft.Owin.CorsNuget包。
/oauth/token,所以我们将用户凭证post到这个路由即可申请token:
public async Task<string> GetOAuth2Token()
{
Uri uri = new Uri(baseUrl + oAuthTokenUrl);
var handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.None };
using (var client = new HttpClient(handler))
{
client.BaseAddress = uri;
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
var content = new FormUrlEncodedContent(new Dictionary<string, string>()
{
{"grant_type", "password"},
{"username", user },
{"password", pwd },
{"client_id", "app" },
{"client_secret", "app"},
});
//获取token保存到cookie,并设置token的过期日期
var result = await client.PostAsync(uri, content);
string tokenResult = await result.Content.ReadAsStringAsync();
var tokenObj = (JObject)JsonConvert.DeserializeObject(tokenResult);
string token = tokenObj["access_token"].ToString();
string refreshToken = tokenObj["refresh_token"].ToString();
long expires = Convert.ToInt64(tokenObj["expires_in"]);
this.Response.SetCookie(new HttpCookie("access_token", token));
this.Response.SetCookie(new HttpCookie("refresh_token", refreshToken));
this.Response.Cookies["access_token"].Expires = Clock.Now.AddSeconds(expires);
return tokenResult;
}
}
grant_type = password,这说明我们使用的是OAuth提供的密码认证模式。其中
{"client_id", "app" }, {"client_secret", "app"}(搞过微信公众号开发的应该对这个很熟悉)用来指定客户端的身份和密钥,这边我们直接写死。
refresh_token来重新获取token。
public async Task<string> GetOAuth2TokenByRefreshToken(string refreshToken)
{
Uri uri = new Uri(baseUrl + oAuthTokenUrl);
var handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.None, UseCookies = true };
using (var client = new HttpClient(handler))
{
client.BaseAddress = uri;
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
var content = new FormUrlEncodedContent(new Dictionary<string, string>()
{
{"grant_type", "refresh_token"},
{"refresh_token", refreshToken},
{"client_id", "app" },
{"client_secret", "app"},
});
//获取token保存到cookie,并设置token的过期日期
var result = await client.PostAsync(uri, content);
string tokenResult = await result.Content.ReadAsStringAsync();
var tokenObj = (JObject)JsonConvert.DeserializeObject(tokenResult);
string token = tokenObj["access_token"].ToString();
string newRefreshToken = tokenObj["refresh_token"].ToString();
long expires = Convert.ToInt64(tokenObj["expires_in"]);
this.Response.SetCookie(new HttpCookie("access_token", token));
this.Response.SetCookie(new HttpCookie("refresh_token", newRefreshToken));
this.Response.Cookies["access_token"].Expires = Clock.Now.AddSeconds(expires);
return tokenResult;
}
}
{"grant_type", "refresh_token"},{"refresh_token", refreshToken}。
public async Task<ActionResult> SendRequestWithOAuth2Token()
{
var token = Request.Cookies["access_token"]?.Value;
if (token == null)
{
//throw new Exception("token已过期");
string refreshToken = Request.Cookies["refresh_token"].Value;
var tokenResult = await GetOAuth2TokenByRefreshToken(refreshToken);
var tokenObj = (JObject)JsonConvert.DeserializeObject(tokenResult);
token = tokenObj["access_token"].ToString();
}
_abpWebApiClient.RequestHeaders.Add(new NameValue("Authorization", "Bearer " + token));
return await GetUserList(baseUrl + webapiUrl);
}
access_token,若
access_token为空说明token过期,我们就从cookie中取回
refresh_token重新申请token。然后构造一个
Authorization将token信息添加到请求头即可访问目标webapi。
本文主要参考自以下文章:
使用OAuth打造webapi认证服务供自己的客户端使用
ABP中使用OAuth2(Resource Owner Password Credentials Grant模式)
Token Based Authentication using ASP.NET Web API 2, Owin, and Identity
本文为 @ 21CTO 创作并授权 21CTO 发布,未经许可,请勿转载。
内容授权事宜请您联系 webmaster@21cto.com或关注 21CTO 公众号。
该文观点仅代表作者本人,21CTO 平台仅提供信息存储空间服务。