OAuth授权模式
- 授权码模式 authorization code
- 简化模式 implicit grant type
- 密码模式 resource owner password credentials
- 客户端模式 client credentials grant
四种授权模式代码实现
1.在api项目中添加Nuget引用
1)Microsoft.Owin.Security (添加该引用下面的会默认添加)
2)Microsoft.Owin.Cookies
3)Microsoft.Owin
4)Microsoft.Owin.Host.SystemWeb
2.添加OAuth文件,添加授权类
3.实现类方法
MyOAuthAuthorizationServeProvider.cs类继承 OAuthAuthorizationServerProvider,实现重构授权Provider
代码中的TestUserRepository 是定义的全局用户类,可根据自己项目的情况实现(可以直接查询数据库),实现身份校验
namespace EVMTest.OAuth
{
public class MyOAuthAuthorizationServeProvider : OAuthAuthorizationServerProvider
{
/// <summary>
/// grant_type password,code
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
#region 客户端模式校验
string clientid;
string clientsecret;
if (!context.TryGetBasicCredentials(out clientid, out clientsecret))
context.TryGetFormCredentials(out clientid, out clientsecret);
if (!string.IsNullOrEmpty(context.ClientId))
{
if (clientid != TestUserRepository.client.ClientId)
{
context.SetError("Invalid_clientid", "clientid is not valid");
return Task.FromResult<object>(null);
}
if (!string.IsNullOrWhiteSpace(clientsecret))
context.OwinContext.Set("ClientSecret", clientsecret);
//context.Validated(clientid);
//return base.ValidateClientAuthentication(context);
}
#endregion
context.Validated();
return Task.FromResult<object>(null);
//return base.ValidateClientAuthentication(context);
}
/// <summary>
/// 验证重定向redirect_url 用于验证被注册的url
/// grant_type authorization_code
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override async Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == TestUserRepository.client.ClientId)
{
context.Validated(TestUserRepository.client.RedirectUrl);
}
}
/// <summary>
/// code
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override async Task ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context)
{
if (context.AuthorizeRequest.ClientId == TestUserRepository.client.ClientId
&& (context.AuthorizeRequest.IsAuthorizationCodeGrantType //授权码模式
|| context.AuthorizeRequest.IsImplicitGrantType)) //简单模式
{
context.Validated();
}
else
{
context.Rejected();
}
//return base.ValidateAuthorizeRequest(context);
}
/// <summary>
/// 授权码模式与简单模式 code
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
//简单模式调用生成token
if (context.AuthorizeRequest.IsImplicitGrantType)
{
var identity = new ClaimsIdentity(new GenericIdentity(context.Request.Query["client_id"], OAuthDefaults.AuthenticationType));
context.OwinContext.Authentication.SignIn(identity);
context.RequestCompleted();
}
if (context.AuthorizeRequest.IsAuthorizationCodeGrantType)
{
switch (context.AuthorizeRequest.State)
{
case "login":
context.Response.Redirect("http://www.baidu.com");
context.RequestCompleted();
break;
case "validate":
var clientid = context.Request.Query["client_id"];
var redirecturl = context.Request.Query["redirect_url"];
var identity = new ClaimsIdentity(new GenericIdentity(clientid, OAuthDefaults.AuthenticationType));
var authorizecodecontext = new AuthenticationTokenCreateContext(
context.OwinContext,
context.Options.AuthorizationCodeFormat,
new AuthenticationTicket(
identity,
new AuthenticationProperties(new Dictionary<string, string> { { "client_id", clientid }, { "redirect_url", redirecturl } })
{ IssuedUtc = DateTimeOffset.UtcNow, ExpiresUtc = DateTimeOffset.UtcNow.Add(context.Options.AuthorizationCodeExpireTimeSpan) }
)
);
//生成授权码Code
await context.Options.AuthorizationCodeProvider.CreateAsync(authorizecodecontext);
//请求地址携带Code授权码
context.Response.Redirect(redirecturl + "?code=" + Uri.EscapeDataString(authorizecodecontext.Token));
context.RequestCompleted();
break;
}
}
//return base.AuthorizeEndpoint(context);
}
/// <summary>
/// 用户密码模式 token
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
//账号密码校验
var finduser = TestUserRepository.userlist.Where(_ => _.NickName == context.UserName && _.PassWord == context.Password).FirstOrDefault();
if (finduser != null)
{
var identity = new ClaimsIdentity(new GenericIdentity(context.UserName, OAuthDefaults.AuthenticationType));
//var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("username", context.UserName));
identity.AddClaim(new Claim("role", "user"));
//var identity = new ClaimsIdentity(
// new GenericIdentity(context.UserName, OAuthDefaults.AuthenticationType),
// context.Scope.Select(_ => new Claim("urn:oauth:scope", _))
// );
context.Validated(identity);
}
else
{
context.SetError("Invalid_grant", "username or passwprd is incorrect");
return Task.FromResult<object>(null);
}
return Task.FromResult<object>(0);
}
public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
var ticket = new AuthenticationTicket(identity,new AuthenticationProperties() { AllowRefresh=true});
context.Validated(ticket);
return base.GrantClientCredentials(context);
}
//public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
//{
// return base.GrantRefreshToken(context);
//}
/
//重构TokenEndpointResponse方法可以服务端获取,管理token
public ovveride TokenEndPointResponse(...)
{
}
}
}
MyRefreshTokenProvider.cs类继承AuthenticationTokenProvider,实现token刷新、获取,
上面的MyAuthenticationCodeProvider.cs与该类一致,可忽略
namespace EVMTest.OAuth
{
public class MyRefreshTokenProvider:AuthenticationTokenProvider
{
private static ConcurrentDictionary<string, string> _refreshtokens = new ConcurrentDictionary<string, string>();
/// <summary>
/// 生成refresh_token
/// </summary>
/// <param name="context"></param>
public override void Create(AuthenticationTokenCreateContext context)
{
context.Ticket.Properties.IssuedUtc = DateTime.UtcNow;
context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(20);
context.SetToken(Guid.NewGuid().ToString("n"));
//
_refreshtokens[context.Token] = context.SerializeTicket();
//base.Create(context);
}
/// <summary>
/// refresh_token解析成access_token
/// </summary>
/// <param name="context"></param>
public override void Receive(AuthenticationTokenReceiveContext context)
{
string value;
if (_refreshtokens.TryRemove(context.Token, out value))
{
context.DeserializeTicket(value);
}
}
public override async Task CreateAsync(AuthenticationTokenCreateContext context)
{
context.Ticket.Properties.IssuedUtc = DateTime.UtcNow;
context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddMinutes(20);
context.SetToken(Guid.NewGuid().ToString("n"));
_refreshtokens[context.Token] = context.SerializeTicket();
// return base.CreateAsync(context);
}
}
}
完成上面的token提供、刷新类后,修改配置文件Startup.Auth.cs
在ConfigureAuth方法中添加代码
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions()
{
//允许http请求
AllowInsecureHttp = true,
//获取token地址 http://localhost://端口/oauht2/token
TokenEndpointPath = new PathString("/oauth2/token"),
//授权码请求地址 http://localhost://端口/oauht2/oauthrize
AuthorizeEndpointPath = new PathString("/oauth2/oauthorize"),
//授权码授权
AuthorizationCodeProvider = new MyRefreshTokenProvider(),
Provider = new MyOAuthAuthorizationServeProvider(),
//token过期时间
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20),
//token提供类,create&refresh
RefreshTokenProvider = new MyRefreshTokenProvider(),
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
});
在控制器头部、控制器方法头部加 [Authorize]特性,授权控制全部或某一方法的调用请求
控制器头部加 [Authorize]特性,内部方法可以加上 [AllowAnonymous]特性,忽略授权
namespace EVMTest.Controllers
{
[Authorize]
public class HomeController : MyController
{
[AllowAnonymous]
public ActionResult Index()
{
ViewBag.Title = "Home Page";
return JsonOK("");
}
/// <summary>
/// 测试Owin授权
/// </summary>
/// <returns></returns>
//[Authorize]
public JsonResult TestAuthorize()
{
var a = HttpContext.GetOwinContext().Authentication;
string name = a.User.Identity.Name;
return JsonOK(name);
}
}
}
获取token
1.授权码模式
1) 授权码获取:
Get路径:http://localhost:端口号/oauth2/oauthorize
参数:{"grant_type":"authorization_code","response_type":"code","client_id":"C1","redirect_url":"http://www.baidu.com","state":"login"}
返回Url:http://www.baidu.com?code=XXXXXXX
2)根据授权码获取Token
Post路径:http://localhost:端口号/oauth2/token
参数:{"grant_type":"authorization_code","Code":"上个请求获取到的Code","redirect_url":"http://www.baidu.com","client_id":"C1"}
2.简单模式
Get路径:http://localhost:端口号/oauth2/token
参数:{"grant_type":"authorization_code","response_type":"token","client_id":"C1","redirect_url":"http://www.baidu.com","state":"validate"}
3.密码模式
Post路径:http://localhost:端口号/oauth2/token
参数:{"grant_type":"password","username":"Alis","password":"123456"}
4.客户端模式
Post路径:http://localhost:端口号/oauth2/token
参数:{"grant_type":"client_credentials","client_id":"C1","client_secret":"123456"}
Token刷新:
根据上面每次token返回的refresh_token,调用
Get路径:http://localhost:端口号/oauth2/token
参数:{"grant_type":"refresh_token","refresh_token":"返回的token值"} //refersh_token只能被用一次