ASP.NET Core 實戰:基於 Jwt Token 的權限控制全揭露


 一、前言

  在涉及到后端項目的開發中,如何實現對於用戶權限的管控是需要我們首先考慮的,在實際開發過程中,我們可能會運用一些已經成熟的解決方案幫助我們實現這一功能,而在 Grapefruit.VuCore 這個項目中,我將使用 Jwt 的方式實現對於用戶的權限管控,在本章中,我將演示如何使用 Jwt 實現對於用戶的授權、鑒權。

  系列目錄地址:ASP.NET Core 項目實戰
  倉儲地址:https://github.com/Lanesra712/Grapefruit.VuCore

 二、Step by Step

  1、一些概念

  Jwt(json web token),是一種基於 Json 的無狀態授權令牌,因為 Jwt 是一種標准的數據傳輸規范,並不是某家所獨有的技術規范,因此非常適用於構建單點登錄服務,為 web、client、app 等等各種接口使用方提供授權服務。

  在使用 Jwt 進行權限控制的過程中,我們需要先請求授權服務器獲取到 token 令牌,將令牌存儲到客戶端本地(在 web 項目中,我們可以將 token 存儲到 localstorage 或是 cookie 中),之后,對於服務端的每一次請求,都需要將獲取到的 token 信息添加到 http 請求的 header 中。

$.ajax({
    url: url,
    method: "POST",
    data: JSON.stringify(data),
    beforeSend: function (xhr) {
        /* Authorization header */
        xhr.setRequestHeader("Authorization", "Bearer " + token);
    },
    success: function (data) {}
});

  當用戶擁有令牌后是否就可以訪問系統的所有功能了呢?答案當然否定的。對於一個系統來說可能會有多種用戶角色,每一個用戶角色可以訪問的資源也是不同的,所以,當用戶已經擁有令牌后,我們還需要對用戶角色進行鑒定,從而做到對用戶進行進一步的權限控制。

  在 Grapefruit.VuCore 這個項目中,我采用的是基於策略的授權方式,通過定義一個授權策略來完善 Jwt 鑒權,之后將這個自定義策略注入到 IServiceCollection 容器中,對權限控制做進一步的完善,從而達到對於用戶訪問權限的管控。

  基於策略的授權是微軟在 ASP.NET Core 中添加的一種新的授權方式,通過定義好策略(policy)的一個或多個要求(requirements),將這個自定義的授權策略在 Startup.ConfigureServices 方法中作為授權服務配置的一部分進行注冊之后即可按照我們的策略處理程序進行權限的控制。

services.AddAuthorization(options =>
{
    options.AddPolicy("Permission",
       policy => policy.Requirements.Add(new PolicyRequirement()));
})

services.AddSingleton<IAuthorizationHandler, PolicyHandler>();

  就像我在后面的代碼中一樣,我定義了一個名叫 Permission 的授權策略,它包含了一個叫做 PolicyRequirement 的鑒權要求,在實現了授權策略后,將基於這個要求的鑒權方法 PolicyHandler 以單例(AddSingleton)的形式注入到服務集合中,此時,我們就可以在需要添加驗證的 controller 上添加 attribute 即可。

[Authorize(Policy = "Permission")]
public class SecretController : ControllerBase
{}

  2、授權

  在 Grapefruit.VuCore 這個項目中,涉及到授權相關的代碼所在的位置我已在下圖中進行標示。在之前系列開篇文章(ASP.NET Core 實戰:使用 ASP.NET Core Web API 和 Vue.js,搭建前后端分離框架)進行介紹整個項目框架時曾說到, Grapefruit.Application 是項目的應用層,顧名思義,就是為了實現我們項目中的實際業務功能所划分的類庫。因此,我們實現 Jwt 的相關業務代碼應該位於此層中。同時,因為對於 Jwt 的令牌頒發與鑒權,采用的是微軟的 JwtBearer 組件,所以我們在使用前需要先通過 Nuget 將引用添加到 Grapefruit.Application 上。

Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Install-Package System.IdentityModel.Tokens.Jwt

  在 Grapefruit.Application 這個類庫下我創建了一個  Authorization 解決方案文件夾用來存儲授權相關的代碼。在 Authorization 這個解決方案文件夾中包含了兩個子文件夾 Jwt 和 Secret。Jwt 文件夾中主要包含我們對於 Jwt 的操作,而 Secret 文件夾下則是對於用戶的相關操作。

  每個子應用文件夾(Jwt、Secret)都包含了相同的結構:Dto 數據傳輸對象、功能接口,以及功能接口的實現類,這里接口的繼承采用單繼承的方式。

  在 Jwt 文件夾下創建一個 IJwtAppService 接口文件,在這里定義我們對於 Jwt 的相關操作。因為對於 Jwt 的授權、鑒權是采用微軟的 JwtBearer 組件,我們只需要進行配置即可,所以這里只定義對於 token 的生成、刷新、停用,以及判斷這個 token 是否有效這幾個方法。同時,我們需要創建 JwtAppService 這個類文件,去繼承 IJwtAppService 從而實現接口功能。

public interface IJwtAppService
{
    /// <summary>
    /// 新增 Jwt token
    /// </summary>
    /// <param name="dto">用戶信息數據傳輸對象</param>
    /// <returns></returns>
    JwtAuthorizationDto Create(UserDto dto);

    /// <summary>
    /// 刷新 Token
    /// </summary>
    /// <param name="token">Token</param>
    /// <param name="dto">用戶信息數據傳輸對象</param>
    /// <returns></returns>
    Task<JwtAuthorizationDto> RefreshAsync(string token, UserDto dto);

    /// <summary>
    /// 判斷當前 Token 是否有效
    /// </summary>
    /// <returns></returns>
    Task<bool> IsCurrentActiveTokenAsync();

    /// <summary>
    /// 停用當前 Token
    /// </summary>
    /// <returns></returns>
    Task DeactivateCurrentAsync();

    /// <summary>
    /// 判斷 Token 是否有效
    /// </summary>
    /// <param name="token">Token</param>
    /// <returns></returns>
    Task<bool> IsActiveAsync(string token);

    /// <summary>
    /// 停用 Token
    /// </summary>
    /// <returns></returns>
    Task DeactivateAsync(string token);
}

  JwtAuthorizationDto 是一個 token 信息的傳輸對象,包含我們創建好的 token 相關信息,用來將 token 信息返回給前台進行使用。而 UserDto 則是用戶登錄獲取 token 時的數據傳輸對象,用來接收登錄時的參數值。

  在創建 token 或是驗證 token 時,像 token 的頒發者、接收者之類的信息,因為會存在多個地方調用的可能性,這里我將這些信息存放在了配置文件中,后面當我們需要使用的時候,只需要通過注入 IConfiguration 進行獲取即可。關於 Jwt 的配置文件主要包含了四項:token 的頒發者,token 的接收者,加密 token 的 key 值,以及 token 的過期時間,你可以根據你自己的需求進行調整。

"Jwt": {
    "Issuer": "yuiter.com",
    "Audience": "yuiter.com",
    "SecurityKey": "a48fafeefd334237c2ca207e842afe0b",
    "ExpireMinutes": "20"
}

  在 token 的創建過程中可以簡單拆分為三個部分:根據配置信息和用戶信息創建一個 token,將加密后的用戶信息寫入到 HttpContext 上下文中,以及將創建好的 token 信息添加到靜態的 HashSet<JwtAuthorizationDto> 集合中。

  在 token 創建、校驗的整個生命周期中,都涉及到了  Scheme、Claim、ClaimsIdentity、ClaimsPrincipal 這些概念,如果你之前有使用過微軟的 Identity 權限驗證,對於這幾個名詞就會比較熟悉,可能某些小伙伴之前並沒有使用過 Identity,我來簡單介紹下這幾個名詞的含義。

  Scheme 模式,這個與其余的名詞相對獨立,它主要是指明我們是以什么授權方式進行授權的。例如,你是以 cookie 的方式授權或是以 OpenId 的方式授權,或是像這里我們使用 Jwt Bearer 的方式進行授權。

  Claim 聲明,以我們的現實生活為例,我們每個人都會有身份證,上面會包含我們的姓名、性別、民族、出生日期、家庭住址、身份證號,每一項數據的都可以看成是 type-value(數據類型-數據值),例如,姓名:張三。身份證上的每一項的信息就是我們的 Claim 聲明,姓名:張三,是一個 Claim;性別:男,也是一個 Claim。而對於 ClaimsIdentity,就像這一項項的信息最終組成了我們的身份證,這一項項的 Claim 最終組成了我們的 ClaimsIdentity。而 ClaimsPrincipal 則是 ClaimsIdentity 的持有者,就像我們擁有身份證一樣。

  從上面的文字可以總結出,Claim(每一項的證件信息)=》ClaimsIdentity(證件)=》ClaimsPrincipal(證件持有者)。其中,一個 ClaimsIdentity 可以包含多個的 Claim,而一個 ClaimsPrincipal 可以包含多個的 ClaimsIdentity。

   如果想要深入了解 ASP.NET Core 的授權策略的可以看看園子里這篇文章 =》ASP.NET Core 運行原理解剖[5]:Authentication,或是國外的這篇介紹 ASP.NET Core 授權的文章 =》Introduction to Authentication with ASP.NET Core

  實現 token 生成的最終代碼實現如下所示,可以看到,在創建 ClaimsIdentity “證件”信息時,我添加了用戶的角色信息,並把加密后的用戶信息寫入到 HttpContext 上下文中,這樣,我們在后面驗證的時候就可以通過 HttpContext 獲取到用戶的角色信息,從而判斷用戶是否可以訪問當前請求的地址。

/// <summary>
/// 新增 Token
/// </summary>
/// <param name="dto">用戶信息數據傳輸對象</param>
/// <returns></returns>
public JwtAuthorizationDto Create(UserDto dto)
{
    JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
    SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:SecurityKey"]));

    DateTime authTime = DateTime.UtcNow;
    DateTime expiresAt = authTime.AddMinutes(Convert.ToDouble(_configuration["Jwt:ExpireMinutes"]));

    //將用戶信息添加到 Claim 中
    var identity = new ClaimsIdentity(JwtBearerDefaults.AuthenticationScheme);

    IEnumerable<Claim> claims = new Claim[] {
        new Claim(ClaimTypes.Name,dto.UserName),
        new Claim(ClaimTypes.Role,dto.Role.ToString()),
        new Claim(ClaimTypes.Email,dto.Email),
        new Claim(ClaimTypes.Expiration,expiresAt.ToString())
    };
    identity.AddClaims(claims);

    //簽發一個加密后的用戶信息憑證,用來標識用戶的身份
    _httpContextAccessor.HttpContext.SignInAsync(JwtBearerDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));

    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(claims),//創建聲明信息
        Issuer = _configuration["Jwt:Issuer"],//Jwt token 的簽發者
        Audience = _configuration["Jwt:Audience"],//Jwt token 的接收者
        Expires = expiresAt,//過期時間
        SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256)//創建 token
    };

    var token = tokenHandler.CreateToken(tokenDescriptor);

    //存儲 Token 信息
    var jwt = new JwtAuthorizationDto
    {
        UserId = dto.Id,
        Token = tokenHandler.WriteToken(token),
        Auths = new DateTimeOffset(authTime).ToUnixTimeSeconds(),
        Expires = new DateTimeOffset(expiresAt).ToUnixTimeSeconds(),
        Success = true
    };

    _tokens.Add(jwt);

    return jwt;
}

  當創建好 token 之后,客戶端就可以在 Http 請求的 header 中添加 token 信息,從而訪問受保護的資源。不過,在某些情況下,比如說,用戶修改了密碼之后,雖然當前的 token 信息可能還未過期,但我們也不能允許用戶再使用當前的 token 信息進行接口的訪問,這時,就涉及到了對於 token 信息的停用以及刷新。

  這里我采用是當我們停用 token 信息時,將停用的 token 信息添加到 Redis 緩存中,之后,在用戶請求時判斷這個 token 是不是存在於 Redis 中即可。

  當然,你也可以在停用當前用戶的 token 信息時,將 HashSet 中的這個 token 信息進行刪除,之后,通過判斷訪問時的 token 信息是否在 HashSet 集合中,判斷 token 是否有效。

  方法很多,看你自己的需求了。

  對於 Redis 的讀寫操作,我是使用微軟的 Redis 組件進行的,你可以按照你的喜好進行修改。如果你和我一樣,采用這個組件,你需要在 Grapefruit.Application 這個類庫中通過 Nuget 添加微軟的分布式緩存抽象接口 dll 的引用,以及在 Grapefruit.WebApi 項目中添加微軟的 Redis 實現。

Install-Package Microsoft.Extensions.Caching.Abstractions ## 分布式緩存抽象接口
Install-Package Microsoft.Extensions.Caching.Redis ## Redis 實現

  當我們停用 token 時,通過 HttpContext 上下文獲取到 HTTP Header 中的 token 信息,將該 token 信息存儲到 Redis 緩存中,這樣,我們就完成了對於 token 的停用。

public class JwtAppService : IJwtAppService
{
    /// <summary>
    /// 停用 Token
    /// </summary>
    /// <param name="token">Token</param>
    /// <returns></returns>
    public async Task DeactivateAsync(string token)
    => await _cache.SetStringAsync(GetKey(token),
            " ", new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromMinutes(Convert.ToDouble(_configuration["Jwt:ExpireMinutes"]))
            });

    /// <summary>
    /// 停用當前 Token
    /// </summary>
    /// <returns></returns>
    public async Task DeactivateCurrentAsync()
    => await DeactivateAsync(GetCurrentAsync());

    /// <summary>
    /// 設置緩存中過期 Token 值的 key
    /// </summary>
    /// <param name="token">Token</param>
    /// <returns></returns>
    private static string GetKey(string token)
        => $"deactivated token:{token}";

    /// <summary>
    /// 獲取 HTTP 請求的 Token 值
    /// </summary>
    /// <returns></returns>
    private string GetCurrentAsync()
    {
        //http header
        var authorizationHeader = _httpContextAccessor
            .HttpContext.Request.Headers["authorization"];

        //token
        return authorizationHeader == StringValues.Empty
            ? string.Empty
            : authorizationHeader.Single().Split(" ").Last();// bearer tokenvalue
    }
}

  對於 token 的刷新,其實我們就可以看成重新生成了一個 token 信息,只不過我們需要將之前的 token 信息進行停用。

public class JwtAppService : IJwtAppService
{
    /// <summary>
    /// 刷新 Token
    /// </summary>
    /// <param name="token">Token</param>
    /// <param name="dto">用戶信息</param>
    /// <returns></returns>
    public async Task<JwtAuthorizationDto> RefreshAsync(string token, UserDto dto)
    {
        var jwtOld = GetExistenceToken(token);
        if (jwtOld == null)
        {
            return new JwtAuthorizationDto()
            {
                Token = "未獲取到當前 Token 信息",
                Success = false
            };
        }

        var jwt = Create(dto);

        //停用修改前的 Token 信息
        await DeactivateCurrentAsync();

        return jwt;
    }

    /// <summary>
    /// 判斷是否存在當前 Token
    /// </summary>
    /// <param name="token">Token</param>
    /// <returns></returns>
    private JwtAuthorizationDto GetExistenceToken(string token)
        => _tokens.SingleOrDefault(x => x.Token == token);
}

  至此,我們對於 token 的創建、刷新、停用的代碼就已經完成了,接下來,我們來實現對於 token 信息的驗證。PS:下面的代碼如無特殊說明外,均位於 Startup 類中。

  3、鑒權

  在 ASP.NET Core 應用中,依賴注入隨處可見,而我們對於我們的功能方法的使用,也是采用依賴注入到容器,通過功能接口進行調用的方式。因此,我們需要將我們的接口與其實現類注入到 IServiceCollection 容器中。這里,我們采用反射的方式,批量的將程序集內的接口與其實現類進行注入。

public void ConfigureServices(IServiceCollection services)
{
    Assembly assembly = Assembly.Load("Grapefruit.Application");
    foreach (var implement in assembly.GetTypes())
    {
        Type[] interfaceType = implement.GetInterfaces();
        foreach (var service in interfaceType)
        {
            services.AddTransient(service, implement);
        }
    }
}

  因為基礎的權限驗證我們是采用的微軟的 JwtBearer 權限驗證組件進行的授權和鑒權,因此對於 token 信息的基礎鑒權操作,只需要我們在中間件中進行配置即可。同時,我們也在 IJwtAppService 接口中定義了對於 token 信息的一些操作,而對於我們自定義的權限驗證策略,則需要通過基於策略的授權方式進行實現。

  首先,我們需要先定義一個繼承於 IAuthorizationRequirement 的自定義授權要求類 PolicyRequirement。在這個類中,你可以定義一些屬性,通過有參構造函數的方式進行構造,這里我不定義任何的屬性,僅是創建這個類。

public class PolicyRequirement : IAuthorizationRequirement
{ }

  當我們創建好 PolicyRequirement 這個權限要求類后,我們就可以通過繼承 AuthorizationHandler 來實現我們的授權邏輯。這里實現權限控制的代碼邏輯,主要是通過重寫 HandleRequirementAsync 方法來實現的。

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement)
{
    //Todo:獲取角色、Url 對應關系
    List<Menu> list = new List<Menu> {
        new Menu
        {
            Role = Guid.Empty.ToString(),
            Url = "/api/v1.0/Values"
        },
        new Menu
        {
            Role=Guid.Empty.ToString(),
            Url="/api/v1.0/secret/deactivate"
        },
        new Menu
        {
            Role=Guid.Empty.ToString(),
            Url="/api/v1.0/secret/refresh"
        }
    };

    var httpContext = (context.Resource as AuthorizationFilterContext).HttpContext;

    //獲取授權方式
    var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
    if (defaultAuthenticate != null)
    {
        //驗證簽發的用戶信息
        var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);
        if (result.Succeeded)
        {
            //判斷是否為已停用的 Token
            if (!await _jwtApp.IsCurrentActiveTokenAsync())
            {
                context.Fail();
                return;
            }

            httpContext.User = result.Principal;
            
            //判斷角色與 Url 是否對應
            //
            var url = httpContext.Request.Path.Value.ToLower();
            var role = httpContext.User.Claims.Where(c => c.Type == ClaimTypes.Role).FirstOrDefault().Value;
            var menu = list.Where(x => x.Role.Equals(role) && x.Url.ToLower().Equals(url)).FirstOrDefault();

            if (menu == null)
            {
                context.Fail();
                return;
            }

            return;
        }
    }
    context.Fail();
}

  在判斷用戶是否可以訪問當前的請求地址時,首先需要獲取到用戶角色與其允許訪問的地址列表,這里我使用的是模擬的數據。通過判斷當前登錄用戶的角色是否包含請求的地址,當用戶的角色並不包含對於訪問地址的權限時,返回 403 Forbidden 狀態碼。

  這里需要注意,如果你准備采取 RESTful 風格的 API,因為請求的地址是相同的,你需要添加一個 HTTP 謂詞參數用來指明所請求的方法,從而達到訪問權限管控的目的。。

  包含 token 的基礎驗證的授權配置的代碼如下所示。在中間件進行 Jwt 驗證的過程中,會驗證授權方式是不是 Bearer 以及通過 token 的屬性解密之后與生成時用戶數據進行比對,從而判斷這個 token 是否有效。

public void ConfigureServices(IServiceCollection services)
{
    string issuer = Configuration["Jwt:Issuer"];
    string audience = Configuration["Jwt:Audience"];
    string expire = Configuration["Jwt:ExpireMinutes"];
    TimeSpan expiration = TimeSpan.FromMinutes(Convert.ToDouble(expire));
    SecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecurityKey"]));

    services.AddAuthorization(options =>
    {
        //1、Definition authorization policy
        options.AddPolicy("Permission",
           policy => policy.Requirements.Add(new PolicyRequirement()));
    }).AddAuthentication(s =>
    {
        //2、Authentication
        s.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        s.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        s.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddJwtBearer(s =>
    {
        //3、Use Jwt bearer 
        s.TokenValidationParameters = new TokenValidationParameters
        {
            ValidIssuer = issuer,
            ValidAudience = audience,
            IssuerSigningKey = key,
            ClockSkew = expiration,
            ValidateLifetime = true
        };
        s.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                //Token expired
                if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                {
                    context.Response.Headers.Add("Token-Expired", "true");
                }
                return Task.CompletedTask;
            }
        };
    });

    //DI handler process function
    services.AddSingleton<IAuthorizationHandler, PolicyHandler>();
}

  因為我們是使用 Swagger 進行的 API 文檔的可視化,這里,我們繼續配置 Swagger 從而使 Swagger 可以支持我們的權限驗證方式。

public void ConfigureServices(IServiceCollection services)
{
    services.AddSwaggerGen(s =>
    {
        //Add Jwt Authorize to http header
        s.AddSecurityDefinition("Bearer", new ApiKeyScheme
        {
            Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
            Name = "Authorization",//Jwt default param name
            In = "header",//Jwt store address
            Type = "apiKey"//Security scheme type
        });
        //Add authentication type
        s.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
        {
            { "Bearer", new string[] { } }
        });
    });
}

  在停用 token 的代碼中,我們使用了 Redis 去保存停用的 token 信息,因此,我們需要配置我們的 Redis 連接。

public void ConfigureServices(IServiceCollection services)
{
    services.AddDistributedRedisCache(r =>
    {
        r.Configuration = Configuration["Redis:ConnectionString"];
    });
}

  現在,整個業務相關的代碼已經完成了,我們可以創建前端訪問的接口了。這里我是在 Controllers 下的 V1 文件夾下創建了一個 SecretController 用來構建前端訪問的接口。控制器中主要有三個方法,分別為 CancelAccessToken(停用 token)、Login(獲取 token)以及 RefreshAccessTokenAsync(刷新 token)。

public class SecretController : ControllerBase
{
    /// <summary>
    /// 停用 Jwt 授權數據
    /// </summary>
    /// <returns></returns>
    [HttpPost("deactivate")]
    public async Task<IActionResult> CancelAccessToken()
    {
        await _jwtApp.DeactivateCurrentAsync();
        return Ok();
    }

    /// <summary>
    /// 獲取 Jwt 授權數據
    /// </summary>
    /// <param name="dto">授權用戶信息</param>
    [HttpPost("token")]
    [AllowAnonymous]
    public IActionResult Login([FromBody] SecretDto dto)
    {
        //Todo:獲取用戶信息
        var user = new UserDto
        {
            Id = Guid.NewGuid(),
            UserName = "yuiter",
            Role = Guid.Empty,
            Email = "yuiter@yuiter.com",
            Phone = "13912345678",
        };

        if (user == null)
            return Ok(new JwtResponseDto
            {
                Access = "無權訪問",
                Type = "Bearer",
                Profile = new Profile
                {
                    Name = dto.Account,
                    Auths = 0,
                    Expires = 0
                }
            });

        var jwt = _jwtApp.Create(user);

        return Ok(new JwtResponseDto
        {
            Access = jwt.Token,
            Type = "Bearer",
            Profile = new Profile
            {
                Name = user.UserName,
                Auths = jwt.Auths,
                Expires = jwt.Expires
            }
        });
    }

    /// <summary>
    /// 刷新 Jwt 授權數據
    /// </summary>
    /// <param name="dto">刷新授權用戶信息</param>
    /// <returns></returns>
    [HttpPost("refresh")]
    public async Task<IActionResult> RefreshAccessTokenAsync([FromBody] SecretDto dto)
    {
        //Todo:獲取用戶信息
        var user = new UserDto
        {
            Id = Guid.NewGuid(),
            UserName = "yuiter",
            Role = Guid.Empty,
            Email = "yuiter@yuiter.com",
            Phone = "13912345678",
        };

        if (user == null)
            return Ok(new JwtResponseDto
            {
                Access = "無權訪問",
                Type = "Bearer",
                Profile = new Profile
                {
                    Name = dto.Account,
                    Auths = 0,
                    Expires = 0
                }
            });

        var jwt = await _jwtApp.RefreshAsync(dto.Token, user);

        return Ok(new JwtResponseDto
        {
            Access = jwt.Token,
            Type = "Bearer",
            Profile = new Profile
            {
                Name = user.UserName,
                Auths = jwt.Success ? jwt.Auths : 0,
                Expires = jwt.Success ? jwt.Expires : 0
            }
        });
    }
}

  現在,讓我們測試一下,從下圖中可以看到,當我們未獲取 token 時,訪問接口提示我們 401 Unauthorized,當我們模擬登錄獲取到 token 信息后,再次訪問受保護的資源時,已經可以獲取到響應的數據。之后,當我們刷新 token,此時再用原來的 token 信息訪問時,已經無法訪問,提示 403 Forbidden,同時,可以看到我們的 Redis 中已經存在了停用的 token 信息,此時,使用新的 token 信息又可以訪問了。

  至此,整個的 Jwt 授權鑒權相關的代碼就已經完成了,因為篇幅原因,完整的代碼請到 Github 上進行查看(電梯直達)。PS:因為博客園允許上傳的圖片限制最大尺寸為 10M,所以這里上傳的 gif 是壓縮后的,見諒見諒,如果有需要查看清晰的圖片,歡迎到我的個人博客上查看(電梯直達)。

 三、總結

   本章,主要是使用 Jwt 完成對於用戶的授權與鑒權,實現了對於用戶 token 令牌的創建、刷新、停用以及校驗。在實際的開發中,采用成熟的輪子可能是更好的方案,如果你有針對 Jwt 進行用戶授權、鑒權更好的解決方案的話,歡迎你在評論區留言指出。拖了很久,應該是年前的最后一篇了,提前祝大家新年快樂哈~~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM