ABP中使用OAuth2(Resource Owner Password Credentials Grant模式)
ABP目前的认证方式有两种,一种是基于Cookie的登录认证,一种是基于token的登录认证。使用Cookie的认证方式一般在PC端用得比较多,使用token的认证方式一般在移动端用得比较多。ABP自带的Token认证方式通过UseOAuthBearerAuthentication启用的,既然已经自带了Token的认证方式,为什么还要使用OAuth2呢?使用此方式是无法实现Token的刷新的,Token过期后必须通过用户名和密码重新登录,这样客户端会弹出登录框让用户登录,用户体验不是很好,当然也可以在客户端存储用户名和密码,发现Token过期后,在后台自动登录,这样用户也是不知道的,只是存在账号安全问题(其实这些都不是问题,主要原因是使用OAuth2后B格更高)。下面我们来看一下怎么在ABP中使用OAuth2:
1.到ABP的官网上下载一个自动生成的项目模板
2.添加OAuth相关的代码
a) 添加一个SimpleAuthorizationServerProvider类,用于验证客户端和用户名密码,网上能够找到类似的代码,直接拿来修改一下就可以
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider, ITransientDependency
{
/// <summary>
/// The _user manager
/// </summary>
private readonly UserManager _userManager; public SimpleAuthorizationServerProvider(UserManager userManager)
{
_userManager = userManager;
} public override async 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") == &&
string.CompareOrdinal(clientSecret, "app") == ;
if (isValidClient)
{
context.OwinContext.Set("as:client_id", clientId);
context.Validated(clientId);
}
else
{
context.SetError("invalid client");
}
} 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 async 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;
} // 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);
} private async Task<AbpUserManager<Tenant, Role, User>.AbpLoginResult> GetLoginResultAsync(OAuthGrantResourceOwnerCredentialsContext context, string usernameOrEmailAddress, string password, string tenancyName)
{
var loginResult = await _userManager.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);
} }
b)添加一个SimpleRefreshTokenProvider类,用于刷新Token
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider, ITransientDependency
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>(); public async 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()
};
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);
} public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
} public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
} public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
}
c)添加OAuth的配置信息
/// <summary>
/// Class OAuthOptions.
/// </summary>
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.FromDays(),
AllowInsecureHttp = true
};
}
return _serverOptions;
}
}
d)在.web项目里添加启用OAuth的代码,在Startup类的Configure方法里添加如下代码
app.UseOAuthAuthorizationServer(OAuthOptions.CreateServerOptions());
3.编写测试服务,用于测试
/// <summary>
/// 测试接口
/// </summary>
public interface ITestAppService : IApplicationService
{
/// <summary>
/// 获取测试信息,可以匿名访问
/// </summary>
/// <returns>返回测试信息</returns>
string GetTestInfo1(); /// <summary>
/// 访问此API需要用户名密码正确才行
/// </summary>
/// <returns></returns>
List<TestDto> GetTestInfo2();
}
public class TestAppService :ApplicationService, ITestAppService
{
public string GetTestInfo1()
{
return DateTime.Now.ToShortTimeString();
} [AbpAuthorize]
public List<TestDto> GetTestInfo2()
{
var list = new List<TestDto>();
for (int i = ; i < ; i++)
{
var dto = new TestDto
{
Id = i + ,
Name = "name" + i
}; list.Add(dto);
} return list;
}
}
4.测试
a) 登录,需要传递的参数如下:
grant_type:该值固定为password
client_id:客户id
client_secret:客户密钥
username:用户名
password:密码
如果已经将client_id和client_secret放到Header里,则不需要传递client_id和client_secret,后台先从Header里解析,如果没有找到,则从请求的参数里查找,但是为了更符合标准,推荐将client_id和client_secret放到Header里,服务端获取client_id和client_secret对应代码如下:
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
{
context.TryGetFormCredentials(out clientId, out clientSecret);
}
登录传递的参数信息和登录成功后返回的信息如下:
b) 刷新Token,需要传递的参数
grant_type:refresh_token
refresh_token:通过登录获取到的refresh_token
client_id:客户id
client_secret:客户密钥
和登录一样,client_id和client_secret推荐放到Header里
刷新传递的参数信息和登录成功后返回的信息如下:
c) 通过Token访问受保护的API时,需要在Header里添加对应的Token,格式化如下:
Authorization: Bearer access_token 将access_token替换为对应的值即可
access_token正确时访问api,返回的信息如下:
access_token不正确或者过期后调用受保护的API返回的信息如下:
5.问题总结
- 登录成功后需要将登录后的Identity放到ticket里面,否则使用获取到的access_token访问受保护的API时,会提示用户未登录
- 不要在.Api项目的Module里添加如下代码(网上有些使用OAuth的例子里添加了如下代码),添加了该代码后就只能使用Token的方式进行登录认证了,Cookie的认证方式会失效,最终的效果就是网站后台输入了正确的用户名和密码也没法登录。
Configuration.Modules.AbpWebApi().HttpConfiguration.SuppressDefaultHostAuthentication();
- 如果要支持多租户登录,需要将对应参数传递过去,可以直接放到QueryString里面
- 除了以上3点,其他和不在ABP里使用OAuth2是一样的
完整源代码下载地址:http://files.cnblogs.com/files/loyldg/UsingOAuth2InABP.src.rar
ABP中使用OAuth2(Resource Owner Password Credentials Grant模式)的更多相关文章
- 基于OWIN WebAPI 使用OAuth授权服务【客户端验证授权(Resource Owner Password Credentials Grant)】
适用范围 前面介绍了Client Credentials Grant ,只适合客户端的模式来使用,不涉及用户相关.而Resource Owner Password Credentials Grant模 ...
- OAuth2.0学习(1-6)授权方式3-密码模式(Resource Owner Password Credentials Grant)
授权方式3-密码模式(Resource Owner Password Credentials Grant) 密码模式(Resource Owner Password Credentials Grant ...
- 使用Resource Owner Password Credentials Grant授权发放Token
对应的应用场景是:为自家的网站开发手机 App(非第三方 App),只需用户在 App 上登录,无需用户对 App 所能访问的数据进行授权. 客户端获取Token: public string Get ...
- 基于 IdentityServer3 实现 OAuth 2.0 授权服务【密码模式(Resource Owner Password Credentials)】
密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码.客户端使用这些信息,向"服务商提供商"索要授权 ...
- IdentityServer4 之 Resource Owner Password Credentials 其实有点尴尬
前言 接着IdentityServer4的授权模式继续聊,这篇来说说 Resource Owner Password Credentials授权模式,这种模式在实际应用场景中使用的并不多,只怪其太开放 ...
- 不要使用Resource Owner Password Credentials
不要使用Resource Owner Password Credentials 文章链接在这里 前言 最近公司项目在做一些重构,因为公司多个业务系统各自实现了一套登录逻辑,比较混乱.所以,现在需要做一 ...
- asp.net core IdentityServer4 实现 resource owner password credentials(密码凭证)
前言 OAuth 2.0默认四种授权模式(GrantType) 授权码模式(authorization_code) 简化模式(implicit) 密码模式(resource owner passwor ...
- IdentityServer4之Resource Owner Password Credentials(资源拥有者密码凭据许可)
IdentityServer4之Resource Owner Password Credentials(资源拥有者密码凭据许可) 参考 官方文档:2_resource_owner_passwords ...
- Oauth2.0(六):Resource Owner Password Credentials 授权和 Client Credentials 授权
这两种简称 Password 方式和 Client 方式吧,都只适用于应用是受信任的场景.一个典型的例子是同一个企业内部的不同产品要使用本企业的 Oauth2.0 体系.在有些情况下,产品希望能够定制 ...
随机推荐
- 台式电脑、笔记本快捷选择Boot(启动项快捷键)大全
我们在安装系统时,会去设置电脑是从硬盘启动.U盘启动.光驱启动.网卡启动. 一般设置的方法有两种:一种是进BIOS主板菜单设置启动项顺序:另一种就是我在这里要介绍的快捷选择启动项. 以下是网友整理的各 ...
- 通过扩展让ASP.NET Web API支持W3C的CORS规范
让ASP.NET Web API支持JSONP和W3C的CORS规范是解决"跨域资源共享"的两种途径,在<通过扩展让ASP.NET Web API支持JSONP>中我们 ...
- PHP制作查询租房表
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 2013 duilib入门简明教程 -- XML配置界面(6)
前面那些教程都是为了让小伙伴们从win32.MFC过渡到duilib,让大家觉得duilib不是那么陌生,如果大家现在还对duilib非常陌生的话,那就说明前面的教程做得不好,请大家在下面留言 ...
- Atitit 深入理解耦合Coupling的原理与attilax总结
Atitit 深入理解耦合Coupling的原理与attilax总结 耦合是指两个或两个以上的电路元件或电网络等的输入与输出之间存在紧密配合与相互影响,并通过相互作用从一侧向另一侧传输能量的现 ...
- 设置 iOS 应用的图标和名称
Xcode 8.1 设置 iOS 应用的图标和名称的方法: 设置应用图标: 1.在 Resources 中添加图片: 2.在 Icon file 的属性值填写图标文件的名称. 设置应用名: 1.修改 ...
- MVC在添加控制器的时候发现没有添加的选项了?肿么办?
异常汇总:http://www.cnblogs.com/dunitian/p/4523006.html#efmvc 因为马上要出去,先上几个关键图简单说一下,有什么不清楚的可以直接留言 nuget包 ...
- php继承多态
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 亚马逊云架设WordPress博客
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 这篇文章介绍如何在亚马逊云架设WordPress博客.最强的云,加上最流行的建站工 ...
- 开发中常用js记录(二)
(1)获得asp.net控件的value值 document.getElementById('<%=SUKid.ClientID %>').value (2)获得选中值 $('#selec ...