JWT(json web token)是一种基于json的身份验证机制,流程如下:

通过登录,来获取Token,再在之后每次请求的Header中追加Authorization为Token的凭据,服务端验证通过即可能获取想要访问的资源。关于JWT的技术,可参考网络上文章,这里不作详细说明,

这篇博文,主要说明在asp.net core 2.0中,基于jwt的web api的权限设置,即在asp.net core中怎么用JWT,再次就是不同用户或角色因为权限问题,即使援用Token,也不能访问不该访问的资源。

基本思路是我们自定义一个策略,来验证用户,和验证用户授权,PermissionRequirement是验证传输授权的参数。在Startup的ConfigureServices注入验证(Authentication),授权(Authorization),和JWT(JwtBearer)

自定义策略:

已封闭成AuthorizeRolicy.JWT nuget包,并发布到nuget上:

https://www.nuget.org/packages/AuthorizePolicy.JWT/

源码如下:

JwtToken.cs

        /// <summary>
/// 获取基于JWT的Token
/// </summary>
/// <param name="username"></param>
/// <returns></returns>
public static dynamic BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement)
{
var now = DateTime.UtcNow;
var jwt = new JwtSecurityToken(
issuer: permissionRequirement.Issuer,
audience: permissionRequirement.Audience,
claims: claims,
notBefore: now,
expires: now.Add(permissionRequirement.Expiration),
signingCredentials: permissionRequirement.SigningCredentials
);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
var response = new
{
Status = true,
access_token = encodedJwt,
expires_in = permissionRequirement.Expiration.TotalMilliseconds,
token_type = "Bearer"
};
return response;
}

  

Permission.cs

    /// <summary>
/// 用户或角色或其他凭据实体
/// </summary>
public class Permission
{
/// <summary>
/// 用户或角色或其他凭据名称
/// </summary>
public virtual string Name
{ get; set; }
/// <summary>
/// 请求Url
/// </summary>
public virtual string Url
{ get; set; }
}

  PermissionRequirement.cs

 using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic; namespace AuthorizePolicy.JWT
{
/// <summary>
/// 必要参数类
/// </summary>
public class PermissionRequirement : IAuthorizationRequirement
{
/// <summary>
/// 用户权限集合
/// </summary>
public List<Permission> Permissions { get; private set; }
/// <summary>
/// 无权限action
/// </summary>
public string DeniedAction { get; set; } /// <summary>
/// 认证授权类型
/// </summary>
public string ClaimType { internal get; set; }
/// <summary>
/// 请求路径
/// </summary>
public string LoginPath { get; set; } = "/Api/Login";
/// <summary>
/// 发行人
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// 订阅人
/// </summary>
public string Audience { get; set; }
/// <summary>
/// 过期时间
/// </summary>
public TimeSpan Expiration { get; set; }
/// <summary>
/// 签名验证
/// </summary>
public SigningCredentials SigningCredentials { get; set; } /// <summary>
/// 构造
/// </summary>
/// <param name="deniedAction">无权限action</param>
/// <param name="userPermissions">用户权限集合</param> /// <summary>
/// 构造
/// </summary>
/// <param name="deniedAction">拒约请求的url</param>
/// <param name="permissions">权限集合</param>
/// <param name="claimType">声明类型</param>
/// <param name="issuer">发行人</param>
/// <param name="audience">订阅人</param>
/// <param name="signingCredentials">签名验证实体</param>
public PermissionRequirement(string deniedAction, List<Permission> permissions, string claimType, string issuer, string audience, SigningCredentials signingCredentials, TimeSpan expiration)
{
ClaimType = claimType;
DeniedAction = deniedAction;
Permissions = permissions;
Issuer = issuer;
Audience = audience;
Expiration = expiration;
SigningCredentials = signingCredentials;
}
}
}

 自定义策略类PermissionHandler.cs

/// <summary>
/// 权限授权Handler
/// </summary>
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
/// <summary>
/// 验证方案提供对象
/// </summary>
public IAuthenticationSchemeProvider Schemes { get; set; }
/// <summary>
/// 自定义策略参数
/// </summary>
public PermissionRequirement Requirement
{ get; set; }
/// <summary>
/// 构造
/// </summary>
/// <param name="schemes"></param>
public PermissionHandler(IAuthenticationSchemeProvider schemes)
{
Schemes = schemes;
} protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
////赋值用户权限
Requirement = requirement;
//从AuthorizationHandlerContext转成HttpContext,以便取出表求信息
var httpContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext).HttpContext;
//请求Url
var questUrl = httpContext.Request.Path.Value.ToLower();
//判断请求是否停止
var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
var handler = await handlers.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler;
if (handler != null && await handler.HandleRequestAsync())
{
context.Fail();
return;
}
}
//判断请求是否拥有凭据,即有没有登录
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);
//result?.Principal不为空即登录成功
if (result?.Principal != null)
{
httpContext.User = result.Principal;
//权限中是否存在请求的url
if (Requirement.Permissions.GroupBy(g => g.Url).Where(w => w.Key.ToLower() == questUrl).Count() > 0)
{
var name = httpContext.User.Claims.SingleOrDefault(s => s.Type == requirement.ClaimType).Value;
//验证权限
if (Requirement.Permissions.Where(w => w.Name == name && w.Url.ToLower() == questUrl).Count() <= 0)
{
//无权限跳转到拒绝页面
httpContext.Response.Redirect(requirement.DeniedAction);
}
}
//判断过期时间
if (DateTime.Parse(httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration).Value) >= DateTime.Now)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return;
}
}
//判断没有登录时,是否访问登录的url,并且是Post请求,并助是form表单提交类型,否则为失败 if (!questUrl.Equals(Requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType))
{
context.Fail();
return;
}
context.Succeed(requirement);
}
}

  

新建asp.net core 2.0的web api项目,并在项目添加AuthorizePolicy.JWT如图

先设置配置文件,用户可以定义密匙和发生人,订阅人

"Audience": {

"Secret": "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890",

"Issuer": "gsw",

"Audience": "everone"

}

在ConfigureServices中注入验证(Authentication),授权(Authorization),和JWT(JwtBearer)

Startup.cs

         public void ConfigureServices(IServiceCollection services)
{
var urls = "http://localhost:39287/";
services.AddCors(options =>
options.AddPolicy("MyDomain",
builder => builder.WithOrigins(urls).AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin().AllowCredentials())); //读取配置文件
var audienceConfig = Configuration.GetSection("Audience");
var symmetricKeyAsBase64 = audienceConfig["Secret"];
var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
var signingKey = new SymmetricSecurityKey(keyByteArray);
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = audienceConfig["Issuer"],//发行人
ValidateAudience = true,
ValidAudience = audienceConfig["Audience"],//订阅人
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
RequireExpirationTime = true, };
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
//这个集合模拟用户权限表,可从数据库中查询出来
var permission = new List<Permission> {
new Permission { Url="/", Name="admin"},
new Permission { Url="/api/values", Name="admin"},
new Permission { Url="/", Name="system"},
new Permission { Url="/api/values1", Name="system"}
};
//如果第三个参数,是ClaimTypes.Role,上面集合的每个元素的Name为角色名称,如果ClaimTypes.Name,即上面集合的每个元素的Name为用户名
var permissionRequirement = new PermissionRequirement(
"/api/denied", permission,
ClaimTypes.Role,
audienceConfig["Issuer"],
audienceConfig["Audience"],
signingCredentials,
expiration: TimeSpan.FromSeconds()//设置Token过期时间
);
services.AddAuthorization(options =>
{ options.AddPolicy("Permission",
policy => policy.Requirements.Add(permissionRequirement)); }).AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
//不使用https
o.RequireHttpsMetadata = false;
o.TokenValidationParameters = tokenValidationParameters;
o.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
if (context.Request.Path.Value.ToString() == "/api/logout")
{
var token = ((context as TokenValidatedContext).SecurityToken as JwtSecurityToken).RawData;
}
return Task.CompletedTask;
}
};
});
//注入授权Handler
services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
services.AddSingleton(permissionRequirement);
services.AddMvc();
}

在需要授的Controller上添加授权特性

[Authorize("Permission")]

PermissionController类有两个方法,一个是登录,验证用户名和密码是否正确,如果正确就发放Token,如果失败,验证失败,别一个成功登后的无权限导航action。

     [Authorize("Permission")]
[EnableCors("MyDomain")]
public class PermissionController : Controller
{
/// <summary>
/// 自定义策略参数
/// </summary>
PermissionRequirement _requirement;
public PermissionController(PermissionRequirement requirement)
{
_requirement = requirement;
}
[AllowAnonymous]
[HttpPost("/api/login")]
public IActionResult Login(string username, string password, string role)
{
var isValidated = username == "gsw" && password == "";
if (!isValidated)
{
return new JsonResult(new
{
Status = false,
Message = "认证失败"
});
}
else
{
//如果是基于用户的授权策略,这里要添加用户;如果是基于角色的授权策略,这里要添加角色
var claims = new Claim[] { new Claim(ClaimTypes.Name, username), new Claim(ClaimTypes.Role, role), new Claim(ClaimTypes.Expiration ,DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString())};
//用户标识
var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);
identity.AddClaims(claims); var token = JwtToken.BuildJwtToken(claims, _requirement);
return new JsonResult(token);
}
} [HttpPost("/api/logout")]
public IActionResult Logout()
{
return Ok();
} [AllowAnonymous]
[HttpGet("/api/denied")]
public IActionResult Denied()
{
return new JsonResult(new
{
Status = false,
Message = "你无权限访问"
});
}
}

下面定义一个控制台(.NetFramewrok)程序,用RestSharp来访问我们定义的web api,其中1为admin角色登录,2为system角色登录,3为错误用户密码登录,4是一个查询功能,在startup.cs中,admin角色是具有查询/api/values的权限的,所以用admin登录是能正常访问的,用system登录,能成功登录,但没有权限访问/api/values,用户名密码错误,访问/aip/values,直接是没有授权的

  class Program
{
/// <summary>
/// 访问Url
/// </summary>
static string _url = "http://localhost:39286";
static void Main(string[] args)
{
dynamic token = null;
while (true)
{
Console.WriteLine("1、登录【admin】 2、登录【system】 3、登录【错误用户名密码】 4、查询数据 ");
var mark = Console.ReadLine();
var stopwatch = new Stopwatch();
stopwatch.Start();
switch (mark)
{
case "1":
token = AdminLogin();
break;
case "2":
token = SystemLogin();
break;
case "3":
token = NullLogin();
break;
case "4":
AdminInvock(token);
break;
}
stopwatch.Stop();
TimeSpan timespan = stopwatch.Elapsed;
Console.WriteLine($"间隔时间:{timespan.TotalSeconds}");
}
}
static dynamic NullLogin()
{
var loginClient = new RestClient(_url);
var loginRequest = new RestRequest("/api/login", Method.POST);
loginRequest.AddParameter("username", "gswaa");
loginRequest.AddParameter("password", "111111");
//或用用户名密码查询对应角色
loginRequest.AddParameter("role", "system");
IRestResponse loginResponse = loginClient.Execute(loginRequest);
var loginContent = loginResponse.Content;
Console.WriteLine(loginContent);
return Newtonsoft.Json.JsonConvert.DeserializeObject(loginContent);
}
static dynamic SystemLogin()
{
var loginClient = new RestClient(_url);
var loginRequest = new RestRequest("/api/login", Method.POST);
loginRequest.AddParameter("username", "gsw");
loginRequest.AddParameter("password", "111111");
//或用用户名密码查询对应角色
loginRequest.AddParameter("role", "system");
IRestResponse loginResponse = loginClient.Execute(loginRequest);
var loginContent = loginResponse.Content;
Console.WriteLine(loginContent);
return Newtonsoft.Json.JsonConvert.DeserializeObject(loginContent);
}
static dynamic AdminLogin()
{
var loginClient = new RestClient(_url);
var loginRequest = new RestRequest("/api/login", Method.POST);
loginRequest.AddParameter("username", "gsw");
loginRequest.AddParameter("password", "111111");
//或用用户名密码查询对应角色
loginRequest.AddParameter("role", "admin");
IRestResponse loginResponse = loginClient.Execute(loginRequest);
var loginContent = loginResponse.Content;
Console.WriteLine(loginContent);
return Newtonsoft.Json.JsonConvert.DeserializeObject(loginContent);
}
static void AdminInvock(dynamic token)
{
var client = new RestClient(_url);
//这里要在获取的令牌字符串前加Bearer
string tk = "Bearer " + Convert.ToString(token?.access_token);
client.AddDefaultHeader("Authorization", tk);
var request = new RestRequest("/api/values", Method.GET);
IRestResponse response = client.Execute(request);
var content = response.Content;
Console.WriteLine($"状态:{response.StatusCode} 返回结果:{content}");
}
}

  

运行结果:

源码:https://github.com/axzxs2001/AuthorizePolicy.JWT

asp.net core 2.0 web api基于JWT自定义策略授权的更多相关文章

  1. 从头编写 asp.net core 2.0 web api 基础框架 (1)

    工具: 1.Visual Studio 2017 V15.3.5+ 2.Postman (Chrome的App) 3.Chrome (最好是) 关于.net core或者.net core 2.0的相 ...

  2. 【转载】从头编写 asp.net core 2.0 web api 基础框架 (1)

    工具: 1.Visual Studio 2017 V15.3.5+ 2.Postman (Chrome的App) 3.Chrome (最好是) 关于.net core或者.net core 2.0的相 ...

  3. 从头编写 asp.net core 2.0 web api 基础框架 (3)

    第一部分:http://www.cnblogs.com/cgzl/p/7637250.html 第二部分:http://www.cnblogs.com/cgzl/p/7640077.html 之前我介 ...

  4. 【转载】从头编写 asp.net core 2.0 web api 基础框架 (3)

    Github源码地址:https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratc ...

  5. 从头编写asp.net core 2.0 web api 基础框架 (5) + 使用Identity Server 4建立Authorization Server (7) 可运行前后台源码

    前台使用angular 5, 后台是asp.net core 2.0 web api + identity server 4. 从头编写asp.net core 2.0 web api 基础框架: 第 ...

  6. asp.net core 2.0 web api + Identity Server 4 + angular 5 可运行前后台源码

    前台使用angular 5, 后台是asp.net core 2.0 web api + identity server 4. 从头编写asp.net core 2.0 web api 基础框架: 第 ...

  7. 从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置

    第1部分:http://www.cnblogs.com/cgzl/p/7637250.html 第2部分:http://www.cnblogs.com/cgzl/p/7640077.html 第3部分 ...

  8. 【转载】从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置

    Github源码地址:https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratc ...

  9. 用VSCode开发一个asp.net core2.0+angular5项目(5): Angular5+asp.net core 2.0 web api文件上传

    第一部分: http://www.cnblogs.com/cgzl/p/8478993.html 第二部分: http://www.cnblogs.com/cgzl/p/8481825.html 第三 ...

随机推荐

  1. Java虚拟机垃圾回收机制

    在Java虚拟机中,对象和数组的内存都是在堆中分配的,垃圾收集器主要回收的内存就是再堆内存中.如果在Java程序运行过程中,动态创建的对象或者数组没有及时得到回收,持续积累,最终堆内存就会被占满,导致 ...

  2. 让你的Dapper像EF一样调用 开源~!

    什么是Dapper? Dapper是一款轻量级的ORM框架,大名鼎鼎! 由谷歌Google 开发,在N款orm框架中 效率是最快的 最高的! 经过数据测试 比EF快 12-20 倍 国外最大的技术网站 ...

  3. 网站pv统计脚本

    在平时的运维工作中,我们运维人员需要清楚自己网站每天的总访问量.总带宽.ip统计和url统计等.虽然网站已经在服务商那里做了CDN加速,所以网站流量压力都在前方CDN层了像每日PV,带宽,ip统计等数 ...

  4. VS2013 C++代码运行问题

    VS2013(工具集V120)下编译的C++代码,在win7运行错误,提示缺少msvcr230.dll,但是添加dll之后无效. 解决办法: 官方下载VS2013的C++运行库: vcredist_x ...

  5. AWK求和、平均值、最值

    --AWK求和.平均值.最值------------------------2014/02/14  打包当前目录下的所有文件 ls | awk '{ print "tar zcvf &quo ...

  6. thymeleaf模板引擎入门

    ThymeLeaf是什么 Thymeleaf是一个用于服务器端的java模板引擎,它使用简单但功能强大,目前可以处理的模板类型包括:HTML.XML.TEXT.JavaScript.CSS等. 搭建t ...

  7. 详实的SQL学习笔记

    写在前面:最近在上海找工作,前前后后面试了N家单位,发现各个公司对程序员的数据库知识尤其是SQL语句的掌握程度有很高的要求,作为一名光荣的程序员,不会玩儿SQL语句走在街上根本不好意思和人打招呼!好了 ...

  8. 【Mybatis】 JdbcType 与 JavaType对应关系

    一.MyBatis 通过包含的jdbcType类型 BIT FLOAT CHAR TIMESTAMP OTHER UNDEFINED TINYINT REAL VARCHAR BINARY BLOB ...

  9. WeQuant比特币交易策略回测记录

    程序参数 PARAMS = { "start_time": "2017-02-01 00:00:00", "end_time": " ...

  10. CNN详解

    CNN详解 版权声明:本文为博主原创文章,转载请指明转载地址 http://www.cnblogs.com/fydeblog/p/7450413.html 前言 这篇博客主要就是卷积神经网络(CNN) ...