上一篇博客中,较多篇幅提及有关JWT和Katana项目,没有看过的朋友,建议先了解下:
https://my.oschina.net/lichaoqiang/blog/867876
在上一篇的基础之上,我们进一步探索,这里以自定义的授权服务提供上为例,实现在自定义
OAuthAuthorizationServerProvider程序。代码入下:
首先,我们需要创建一个类:CustomOAuthProvider,继承自
/// <summary>
/// <![CDATA[自定义 jwt oauth 的授权验证]]>
/// </summary>
public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{}
其次,重写虚方法,实现验证逻辑。
/// <summary>
/// <![CDATA[授权证书]]>
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var username = context.UserName;
var password = context.Password;
string userid;
if (!CheckCredential(username, password, out userid))
{
context.SetError("invalid_grant", "The user name or password is incorrect");
context.Rejected();
return Task.FromResult<object>(null);
}
var ticket = new AuthenticationTicket(SetClaimsIdentity(context, userid, username), new AuthenticationProperties());
context.Validated(ticket);
return Task.FromResult<object>(context);
}
/// <summary>
/// <![CDATA[验证客户端授权]]>
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(context);
}
自定义一个AccessTokenFormat:
/// <summary>
/// 自定义 jwt token 的格式
/// </summary>
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly byte[] _secret;
private readonly string _issuer;
public CustomJwtFormat(string issuer, byte[] secret)
{
_issuer = issuer;
_secret = secret;
}
/// <summary>
/// <![CDATA[生成令牌]]>
/// </summary>
/// <param name="data"><![CDATA[票据]]></param>
/// <returns></returns>
public string Protect(AuthenticationTicket data)
{
var secret = TextEncodings.Base64Url.Decode("IxrAjDoa2FqElO7IhrSrUJELhUckePEPVpaePlS_Xaw");//秘钥
SecurityKey securityKey = new InMemorySymmetricSecurityKey(secret);
var signingKey = new InMemorySymmetricSecurityKey(_secret);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(_issuer, "Any", data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, new SigningCredentials(securityKey, "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256", "http://www.w3.org/2001/04/xmlenc#sha256")));
}
/// <summary>
/// <![CDATA[从token中解码,返回一个票据信息]]>
/// </summary>
/// <param name="protectedText"><![CDATA[受保护的文本,也就是token]]></param>
/// <returns></returns>
public AuthenticationTicket Unprotect(string protectedText)
{
var jst = new JwtSecurityTokenHandler();
var token = (JwtSecurityToken)jst.ReadToken(protectedText);
var identity = new ClaimsIdentity(token.Claims);
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties() { });
return ticket;
}
}
最后,创建一个启动类,配置中间件。
/// <summary>
/// <![CDATA[配置授权]]>
/// </summary>
/// <param name="app"></param>
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
var issuer = "http://localhost:2817/";
var audience = "fNm0EDIXbfuuDowUpAoq5GTEiywV8eg0TpiIVnV8";//观众
var secret = TextEncodings.Base64Url.Decode("IxrAjDoa2FqElO7IhrSrUJELhUckePEPVpaePlS_Xaw");//秘钥
var signingKey = new System.IdentityModel.Tokens.InMemorySymmetricSecurityKey(secret);
//配置JwtBearer授权中间件
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions()
{
AuthenticationMode = AuthenticationMode.Active,
AuthenticationType = "password",
AllowedAudiences = new[] { audience },
Provider = new OAuthBearerAuthenticationProvider()
{
OnValidateIdentity = context =>
{
context.Ticket.Identity.AddClaim(new System.Security.Claims.Claim(ClaimTypes.NameIdentifier, context.Ticket.Identity.Name));
return Task.FromResult<object>(context);
},
OnRequestToken = (context) =>
{
if (context.Token != null)
{
context.Response.Headers["WWW-Authorization"] = "Bearer";
var jst = new JwtSecurityTokenHandler();
var token = (JwtSecurityToken)jst.ReadToken(context.Token);
//创建身份
var identity = new ClaimsIdentity(token.Claims, "JWT");
context.Request.User = new ClaimsPrincipal(identity);
}
return Task.FromResult<object>(context);
}
},
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
//配置OAuth中间件
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
//生产环境设为false
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth2/token"),//请求token的路径
AccessTokenExpireTimeSpan = TimeSpan.FromDays(30),//令牌的过期时间
//请求获取token时,验证username, password
Provider = new CustomOAuthProvider(),
//定义token信息格式 --默认TicketDataFormat
AccessTokenFormat = new CustomJwtFormat(issuer, secret),
});
app.UseWebApi(config);//
}
模拟示例:
<script type="text/javascript">
$.ajax({
beforeSend: function (request) { console.log(request); },
type: "post",
data: { client_id: "fNm0EDIXbfuuDowUpAoq5GTEiywV8eg0TpiIVnV8", username: "admin", password: "admin", grant_type: "password", client_secret: "IxrAjDoa2FqElO7IhrSrUJELhUckePEPVpaePlS_Xaw" },
url: "/oauth2/token",
headers: { alg: "HS256", typ: "JWT" },
success: function (data) {
var access_token = data.access_token;
testApi(access_token);
}
});
function testApi(token) {
$.ajax({
beforeSend: function (request) { },
type: "GET",
url: "/api/values/get",
headers: { "Authorization": "Bearer " + token },
success: function (data) {
}
});
}
</script>
获取访问令牌:
{"access_token":"BLEx3eE72_P8O__wCRUEhdtMlpPq0pnswmPReBZYAVdueL1p33cfybdcd0mVBFZZFsG8OgK8LBik1ZuDADW-NHul-KM8TXbJzr_dVfs2bw9S-YUNKMkJjY7mHilbF3TEI3o54jGn11R8z2tlp_95oLNVn_D10zyAdKNGs8qtb7gMW_XUCQpkRmVNcEd58h9wREXHYnYjWYyMRYT-rttgvxU5LKU5VsUTpdIFjfx2c17ALqr7d0sqY5LZ3nDh-tNyMvNHgu9ea9G2uHE3CVbulOYb-7EVp4MkpzW8rQ0cTAI","token_type":"bearer","expires_in":2591999}
携带access_token访问资源:
[Authorize]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
var owin = HttpContext.Current.GetOwinContext();
var identity = owin.Request.User;
return new string[] { "value1", "value2" };
}
}
["value1","value2"]