asp.net core 2.0 web api基於JWT自定義策略受權

JWT(json web token)是一種基於json的身份驗證機制,流程以下:git

 

 

經過登陸,來獲取Token,再在以後每次請求的Header中追加Authorization爲Token的憑據,服務端驗證經過便可能獲取想要訪問的資源。關於JWT的技術,可參考網絡上文章,這裏不做詳細說明,github

這篇博文,主要說明在asp.net core 2.0中,基於jwt的web api的權限設置,即在asp.net core中怎麼用JWT,再次就是不一樣用戶或角色由於權限問題,即便援用Token,也不能訪問不應訪問的資源。web

基本思路是咱們自定義一個策略,來驗證用戶,和驗證用戶受權,PermissionRequirement是驗證傳輸受權的參數。在Startup的ConfigureServices注入驗證(Authentication),受權(Authorization),和JWT(JwtBearer)數據庫

自定義策略:json

已封閉成AuthorizeRolicy.JWT nuget包,併發布到nuget上:api

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

源碼以下:併發

JwtToken.csasp.net

        /// <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.csasync

    /// <summary>
    /// 用戶或角色或其餘憑據實體
    /// </summary>
    public class Permission
    {
        /// <summary>
        /// 用戶或角色或其餘憑據名稱
        /// </summary>
        public virtual string Name
        { get; set; }
        /// <summary>
        /// 請求Url
        /// </summary>
        public virtual string Url
        { get; set; }
    }

  PermissionRequirement.cs

 1 using Microsoft.AspNetCore.Authorization;
 2 using Microsoft.IdentityModel.Tokens;
 3 using System;
 4 using System.Collections.Generic;
 5 
 6 namespace AuthorizePolicy.JWT
 7 {
 8     /// <summary>
 9     /// 必要參數類
10     /// </summary>
11     public class PermissionRequirement : IAuthorizationRequirement
12     {
13         /// <summary>
14         /// 用戶權限集合
15         /// </summary>
16         public List<Permission> Permissions { get; private set; }
17         /// <summary>
18         /// 無權限action
19         /// </summary>
20         public string DeniedAction { get; set; }
21 
22         /// <summary>
23         /// 認證受權類型
24         /// </summary>
25         public string ClaimType { internal get; set; }
26         /// <summary>
27         /// 請求路徑
28         /// </summary>
29         public string LoginPath { get; set; } = "/Api/Login";
30         /// <summary>
31         /// 發行人
32         /// </summary>
33         public string Issuer { get; set; }
34         /// <summary>
35         /// 訂閱人
36         /// </summary>
37         public string Audience { get; set; }
38         /// <summary>
39         /// 過時時間
40         /// </summary>
41         public TimeSpan Expiration { get; set; }
42         /// <summary>
43         /// 簽名驗證
44         /// </summary>
45         public SigningCredentials SigningCredentials { get; set; }
46 
47         /// <summary>
48         /// 構造
49         /// </summary>
50         /// <param name="deniedAction">無權限action</param>
51         /// <param name="userPermissions">用戶權限集合</param>
52 
53         /// <summary>
54         /// 構造
55         /// </summary>
56         /// <param name="deniedAction">拒約請求的url</param>
57         /// <param name="permissions">權限集合</param>
58         /// <param name="claimType">聲明類型</param>
59         /// <param name="issuer">發行人</param>
60         /// <param name="audience">訂閱人</param>
61         /// <param name="signingCredentials">簽名驗證明體</param>
62         public PermissionRequirement(string deniedAction, List<Permission> permissions, string claimType, string issuer, string audience, SigningCredentials signingCredentials, TimeSpan expiration)
63         {
64             ClaimType = claimType;
65             DeniedAction = deniedAction;
66             Permissions = permissions;
67             Issuer = issuer;
68             Audience = audience;
69             Expiration = expiration;
70             SigningCredentials = signingCredentials;
71         }
72     }
73 }

 自定義策略類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

 1         public void ConfigureServices(IServiceCollection services)
 2         {
 3             var urls = "http://localhost:39287/";
 4             services.AddCors(options =>
 5             options.AddPolicy("MyDomain",
 6         builder => builder.WithOrigins(urls).AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin().AllowCredentials()));
 7 
 8             //讀取配置文件
 9             var audienceConfig = Configuration.GetSection("Audience");
10             var symmetricKeyAsBase64 = audienceConfig["Secret"];
11             var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
12             var signingKey = new SymmetricSecurityKey(keyByteArray);
13             var tokenValidationParameters = new TokenValidationParameters
14             {
15                 ValidateIssuerSigningKey = true,
16                 IssuerSigningKey = signingKey,
17                 ValidateIssuer = true,
18                 ValidIssuer = audienceConfig["Issuer"],//發行人
19                 ValidateAudience = true,
20                 ValidAudience = audienceConfig["Audience"],//訂閱人
21                 ValidateLifetime = true,
22                 ClockSkew = TimeSpan.Zero,
23                 RequireExpirationTime = true,
24 
25             };
26             var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
27             //這個集合模擬用戶權限表,可從數據庫中查詢出來
28             var permission = new List<Permission> {
29                               new Permission {  Url="/", Name="admin"},
30                               new Permission {  Url="/api/values", Name="admin"},
31                               new Permission {  Url="/", Name="system"},
32                               new Permission {  Url="/api/values1", Name="system"}
33                           };
34             //若是第三個參數,是ClaimTypes.Role,上面集合的每一個元素的Name爲角色名稱,若是ClaimTypes.Name,即上面集合的每一個元素的Name爲用戶名
35             var permissionRequirement = new PermissionRequirement(
36                 "/api/denied", permission,
37                 ClaimTypes.Role,
38                 audienceConfig["Issuer"],
39                 audienceConfig["Audience"],
40                 signingCredentials,
41                 expiration: TimeSpan.FromSeconds(10)//設置Token過時時間
42                 );
43             services.AddAuthorization(options =>
44             {
45 
46                 options.AddPolicy("Permission",
47                           policy => policy.Requirements.Add(permissionRequirement));
48 
49             }).AddAuthentication(options =>
50             {
51                 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
52                 options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
53             })
54             .AddJwtBearer(o =>
55             {
56                 //不使用https
57                 o.RequireHttpsMetadata = false;
58                 o.TokenValidationParameters = tokenValidationParameters;
59                 o.Events = new JwtBearerEvents
60                 {
61                     OnTokenValidated = context =>
62                     {
63                         if (context.Request.Path.Value.ToString() == "/api/logout")
64                         {
65                             var token = ((context as TokenValidatedContext).SecurityToken as JwtSecurityToken).RawData;
66                         }
67                         return Task.CompletedTask;
68                     }
69                 };
70             });
71             //注入受權Handler
72             services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
73             services.AddSingleton(permissionRequirement);
74             services.AddMvc();
75         }

 

在須要授的Controller上添加受權特性

    [Authorize("Permission")]

 

PermissionController類有兩個方法,一個是登陸,驗證用戶名和密碼是否正確,若是正確就發放Token,若是失敗,驗證失敗,別一個成功登後的無權限導航action。

 1     [Authorize("Permission")]
 2     [EnableCors("MyDomain")]
 3     public class PermissionController : Controller
 4     {
 5         /// <summary>
 6         /// 自定義策略參數
 7         /// </summary>
 8         PermissionRequirement _requirement;
 9         public PermissionController(PermissionRequirement requirement)
10         {
11             _requirement = requirement;
12         }
13         [AllowAnonymous]
14         [HttpPost("/api/login")]
15         public IActionResult Login(string username, string password, string role)
16         {
17             var isValidated = username == "gsw" && password == "111111";
18             if (!isValidated)
19             {
20                 return new JsonResult(new
21                 {
22                     Status = false,
23                     Message = "認證失敗"
24                 });
25             }
26             else
27             {               
28                 //若是是基於用戶的受權策略,這裏要添加用戶;若是是基於角色的受權策略,這裏要添加角色
29                 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())};
30                 //用戶標識
31                 var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);
32                 identity.AddClaims(claims);
33              
34                 var token = JwtToken.BuildJwtToken(claims, _requirement);
35                 return new JsonResult(token);
36             }
37         }
38 
39         [HttpPost("/api/logout")]
40         public  IActionResult Logout()
41         {
42             return Ok();
43         }
44 
45         [AllowAnonymous]
46         [HttpGet("/api/denied")]
47         public IActionResult Denied()
48         {
49             return new JsonResult(new
50             {
51                 Status = false,
52                 Message = "你無權限訪問"
53             });
54         }
55     }

 

下面定義一個控制檯(.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("一、登陸【admin】 二、登陸【system】 三、登陸【錯誤用戶名密碼】 四、查詢數據 ");
                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

相關文章
相關標籤/搜索