国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

ASP.NET Core高級之認證與授權(二)--JWT認證前后端完整實現(xiàn)

這篇具有很好參考價值的文章主要介紹了ASP.NET Core高級之認證與授權(二)--JWT認證前后端完整實現(xiàn)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

閱讀本文你的收獲

  1. 了解JWT身份認證的流程
  2. 了解基于JWT身份認證和Session身份認證的區(qū)別
  3. 學習如何在ASP.NET Core WebAPI項目中封裝JWT認證功能

在上文ASP.NET Core高級之認證與授權(一)–JWT入門-頒發(fā)、驗證令牌中演示了JWT認證的一個入門案例,本文是一個基于JWT認證的完整的前后端實現(xiàn)代碼案例。

一、基于JWT的用戶認證

JWT身份認證的流程

在認證的時候,當用戶用他們的憑證成功登錄以后,一個JSON Web Token將會被返回。此后,token就是用戶憑證了,你必須非常小心以防止出現(xiàn)安全問題。一般而言,你保存令牌的時候不應該超過你所需要它的時間。

無論何時用戶想要訪問受保護的路由或者資源的時候,用戶代理(通常是瀏覽器)都應該帶上JWT,典型的,通常放在Authorization header中,用Bearer schema。

ASP.NET Core高級之認證與授權(二)--JWT認證前后端完整實現(xiàn),# ASP.NET Core,asp.net,后端,.net core上圖流程說明:

  1. 用戶攜帶用戶名和密碼請求認證
  2. 服務器校驗用戶賬號密碼,成功則提供一個token給客戶端
  3. 客戶端存儲token,并且在隨后的每一次請求中都帶著它
  4. 服務器校驗token有效則返回數(shù)據(jù),無效則返回401狀態(tài)碼;

二、基于JWT身份認證和Session身份認證的區(qū)別

基于Session的身份認證的缺點

ASP.NET Core高級之認證與授權(二)--JWT認證前后端完整實現(xiàn),# ASP.NET Core,asp.net,后端,.net core

  • 會話信息會占用服務器

    每次用戶認證通過以后,服務器需要創(chuàng)建一條記錄保存用戶信息,通常是在內存中,隨著認證通過的用戶越來越多,服務器的在這里的開銷就會越來越大。

  • 難以擴展

    由于Session是在服務器的內存中的,這就帶來一些擴展性的問題。

  • 跨域共享難

    當我們想要擴展我們的應用,讓我們的數(shù)據(jù)被多個移動設備使用時,我們必須考慮跨資源共享(如使用Redis)問題。當使用AJAX調用從另一個域名下獲取資源時,我們可能會遇到禁止請求的問題。

  • 安全性差

    客戶端要存Cookie來保存SessionId,所以 用戶很容易受到CSRF攻擊。

基于JWT令牌的身份認證的優(yōu)缺點

ASP.NET Core高級之認證與授權(二)--JWT認證前后端完整實現(xiàn),# ASP.NET Core,asp.net,后端,.net core
優(yōu)點:

  • 簡單輕巧

    JWT生成的token字符串是輕量級,json風格,比較簡單。

  • 減輕服務器壓力

    它是無狀態(tài)的,JWT方式將用戶狀態(tài)分散到了客戶端中,服務器或者Session中不會存儲任何用戶信息。明顯減輕服務端的內存壓力。

  • 容易做分布式

    沒有會話信息意味著應用程序可以根據(jù)需要擴展和添加更多的機器,而不必擔心用戶登錄的位置;

缺點:

  • JWT token一旦簽發(fā),無法修改
  • 無法更新token有效期,用戶登錄狀態(tài)刷新較難實現(xiàn)
  • 無法銷毀一個token,服務端不能對用戶狀態(tài)進行絕對控制
  • 不包含權限控制

三、JWT前后端完整實現(xiàn)關鍵代碼

開發(fā)環(huán)境:

操作系統(tǒng): Windows 10 專業(yè)版
平臺版本是:.NET 6
開發(fā)框架:ASP.NET Core WebApi、Vue2+ElementUI
開發(fā)工具:Visual Studio 2022

1. JWT的選項配置

在appsetting.json文件中,配置JWT選項參數(shù),這樣做的好處是,使用者在發(fā)布后任然可以修改JWT的參數(shù),如過期時間,密鑰等。

  "JWTTokenOption": {
    "Issuer": "WLW",                        //Token發(fā)布者
    "Audience": "EveryTestOne",             //Token接受者
    "IssuerSigningKey": "WLW!@#%^99825949", //秘鑰可以構建服務器認可的token;簽名秘鑰長度最少16
    "AccessTokenExpiresMinutes": "30"       //過期時間30分鐘
  }

為了在ASP.NET Core WebAPI項目中讀出JWT選項參數(shù),首先定義一個用于保存JWT選項的模型類:

/// <summary>
/// 用來保存jwt的配置信息
/// </summary>
public class JwtTokenOption
{
    /// <summary>
    /// Token發(fā)布者
    /// </summary>
    public string Issuer { get; set; }
    /// <summary>
    /// oken接受者
    /// </summary>
    public string Audience { get; set; }
    /// <summary>
    /// 秘鑰
    /// </summary>
    public string IssuerSigningKey { get; set; }
    /// <summary>
    /// 過期時間
    /// </summary>
    public int AccessTokenExpiresMinutes { get; set; }
}

在Program.cs中通過以下方式,讀取JWT配置選項:

//獲取jwt配置項
var jwtTokenConfig = builder.Configuration.GetSection("JWTTokenOption").Get<JwtTokenOption>();
builder.Services.AddSingleton(jwtTokenConfig); //注冊單例服務,以便后續(xù)調用

2. 配置Jwt身份認證方式

在Program.cs中進行服務注冊,配置身份驗證的模式為JwtBearer。

安裝Microsoft.AspNetCore.Authentication.JwtBearer這個nuget包


//配置JwtBearer身份認證服務
builder.Services.AddAuthentication(opts=>
{
    opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; //認證模式
    opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;    //質詢模式
})
.AddJwtBearer(   //對JwtBearer進行配置
    x =>
    {
        x.RequireHttpsMetadata = true; //設置元數(shù)據(jù)地址或權限是否需要HTTP
        x.SaveToken = true;
        //Token驗證參數(shù)
        x.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateIssuer = true,  //是否驗證Issuer
            ValidIssuer = jwtTokenConfig.Issuer,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtTokenConfig.IssuerSigningKey)),
            ValidateAudience = true,
            ValidAudience = jwtTokenConfig.Audience,
            ValidateLifetime = true,
            ClockSkew = TimeSpan.FromMinutes(1)  //對token過期時間驗證的允許時間
        };
        //如果jwt過期,在返回的header中加入Token-Expired字段為true,前端在獲取返回header時判斷
        x.Events = new JwtBearerEvents()
        {
            OnAuthenticationFailed = context =>
            {
                if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                {
                    context.Response.Headers.Add("Token-Expired", "true");
                }
                return Task.CompletedTask;
            }
        };
    }
);

3. 編寫生成JWT令牌的幫助類

首先,定義相關的一些模型類,如下:

/// <summary>
/// 存放Token 跟過期時間的模型類
/// </summary>
public class TnToken
{
    /// <summary>
    /// token字符串
    /// </summary>
    public string TokenStr { get; set; }
    /// <summary>
    /// token過期時間
    /// </summary>
    public DateTime Expires { get; set; }
}
/// <summary>
/// 返回信息模型類
/// </summary>
public class ResponseModel
{
    /// <summary>
    /// 返回碼
    /// </summary>
    public int Code { get; set; }
    /// <summary>
    /// 消息
    /// </summary>
    public string Msg { get; set; }
    /// <summary>
    /// 數(shù)據(jù)
    /// </summary>
    public object Data { get; set; }
    /// <summary>
    /// Token信息
    /// </summary>
    public TnToken TokenInfo { get; set; }
}
/// <summary>
/// token工具類的接口,方便使用依賴注入,很簡單提供兩個常用的方法
/// </summary>
public interface ITokenHelper
{
    /// <summary>
    /// 根據(jù)一個對象通過反射提供負載,生成token  
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="user"></param>
    /// <returns></returns>
    TnToken CreateToken<T>(T entity) where T : class;

    /// <summary>
    /// 根據(jù)鍵值對提供負載,生成token
    /// </summary>
    /// <param name="keyValuePairs"></param>
    /// <returns></returns>
    TnToken CreateToken(Dictionary<string, string> keyValuePairs);

}

以上接口的實現(xiàn)類如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Newtonsoft.Json;
using System.Security.Cryptography;

/// <summary>
/// Token幫助類
/// </summary>
public class TokenHelper : ITokenHelper
{
    //依賴注入配置項
    //private readonly IOptions<JwtTokenOption> _options;
    private readonly JwtTokenOption _options;
    /// <summary>
    /// 構造方法
    /// </summary>
    /// <param name="options"></param>
    public TokenHelper(JwtTokenOption options)
    {
        _options = options;
    }

    /// <summary>
    /// 根據(jù)一個對象通過反射提供負載,生成token  
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="user"></param>
    /// <returns></returns>
    public TnToken CreateToken<T>(T entity) where T : class
    {
        //定義聲明的集合
        List<Claim> claims = new List<Claim>();

        //用反射把數(shù)據(jù)提供給它
        foreach (var item in entity.GetType().GetProperties())
        {
            object obj = item.GetValue(entity);
            string value = "";
            if(obj != null)
            {
                value = obj.ToString();
            }

            claims.Add(new Claim(item.Name, value));
        }

        //根據(jù)聲明 生成token字符串
        return CreateTokenString(claims);
    }

    /// <summary>
    /// 根據(jù)鍵值對提供負載,生成token
    /// </summary>
    /// <param name="keyValuePairs"></param>
    /// <returns></returns>
    public TnToken CreateToken(Dictionary<string, string> keyValuePairs)
    {
        //定義聲明的集合
        List<Claim> claims = new List<Claim>();

        foreach (var item in keyValuePairs)
        {
            claims.Add(new Claim(item.Key, item.Value));
        }

        //根據(jù)聲明 生成token字符串
        return CreateTokenString(claims);
    }

    /// <summary>
    /// 私有方法,用于生成Token字符串
    /// </summary>
    /// <param name="claims"></param>
    /// <returns></returns>
    private TnToken CreateTokenString(List<Claim> claims)
    {
        //過期時間
        DateTime expires = DateTime.Now.AddMinutes(_options.AccessTokenExpiresMinutes);

        var token = new JwtSecurityToken(
            issuer: _options.Issuer,
            audience: _options.Audience,
            claims: claims,           //攜帶的荷載
            notBefore: DateTime.Now,  //token生成時間
            expires: expires,         //token過期時間
            signingCredentials: new SigningCredentials(
                new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.IssuerSigningKey)), SecurityAlgorithms.HmacSha256
                )
            );

        return new TnToken
        {
            Expires = expires,
            TokenStr = new JwtSecurityTokenHandler().WriteToken(token)
        };
    }
}

4. 在用戶登錄方法中,簽發(fā)Token

//定義實例tokenHelper實例
private readonly ITokenHelper _tokenHelper;
//構造函數(shù)注入ITokenHelper實例 略

/// <summary>
/// 登錄功能
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
[HttpPost]
public IActionResult Login(UserLoginDto user) //(1) 后端登錄:賬號密碼的驗證
{
    //對輸入?yún)?shù)user進行驗證,略
 	
    //定義一個響應信息的對象
    ResponseModel res = new ResponseModel();
    
    //檢查用戶是否存在(MD5對密碼進行加密)
    var userInfo = _userService.CheckUserAndPwd(user.LoginName, user.Pwd);
    if (userInfo != null)
    {
        Dictionary<string, string> keyValuePairs = new Dictionary<string, string>
            {
                { "LoginName", user.LoginName },
                { "SuperAdmin", "true"} //假定用戶屬于SuperAdmin角色
            };
        res.Code = 200;
        res.Msg = "登錄成功";
        
        //(2) 后端:幫助類來生成JWT字符串,JWT字符串返回給瀏覽器
        res.TokenInfo = _tokenHelper.CreateToken(keyValuePairs);
        return Ok(res);
    }
    else
    {
        res.Code = 401;
        res.Msg = "用戶名或密碼不正確";
        return Unauthorized(res);  //401的錯誤碼
    }
}

5. 給API接口加身份授權鎖

在Api控制器或者方法上,加[Authorize]特性,需要引用命名空間:

using Microsoft.AspNetCore.Authorization;

  • [Authorize]加在控制器上,則該控制器下所有API方法需要身份授權后才能訪問;
  • [Authorize]加在方法上,則僅該方法需要身份授權后才能訪問。

另外,有一個[AllowAnonymous]特性,加在Api控制器或者方法上,允許匿名訪問該Api或者控制器下所有Api方法。

Api資源被鎖保護起來之后,如果沒有登錄直接訪問,則會報401的錯誤。在Swagger中測試截圖如下:
ASP.NET Core高級之認證與授權(二)--JWT認證前后端完整實現(xiàn),# ASP.NET Core,asp.net,后端,.net core

6. Swagger中進行Token的測試

(1)在Program.cs的配置Swagger服務:

builder.Services.AddSwaggerGen(c =>{
	var basePath = AppContext.BaseDirectory;  //獲取應用程序的所在目錄
    //或者用下面的方式也能獲取
    var basePath2 =Path.GetDirectoryName(typeof(Program).Assembly.Location);
    
	var xmlPath = System.IO.Path.Combine(basePath, "XfTech.Demo.xml"); //拼接XML文件所在路徑
    
	//讓Swagger顯示方法、類的XML注釋信息
	c.IncludeXmlComments(xmlPath, true);
	//設置Swagger文檔參數(shù)
	c.SwaggerDoc("v1", new OpenApiInfo
	{
		Title = "XfTech.Demo",
		Version = "v1",
		Description = "Asp.Net Core6 WebApi開發(fā)實戰(zhàn)",  //描述信息
		Contact = new OpenApiContact()                //開發(fā)者信息
		{
			Name = "物聯(lián)網(wǎng)大聯(lián)盟",               //開發(fā)者姓名
			Email = "99825949@qq.com",    //email地址
			Url = new Uri("https://blog.csdn.net/ousetuhou?type=blog") //作者的主頁網(wǎng)站
		}
	});
	//開啟Authorize權限按鈕
	c.AddSecurityDefinition("JWTBearer", new OpenApiSecurityScheme()
	{
		Description = "這是方式一(直接在輸入框中輸入認證信息,不需要在開頭添加Bearer) ",
		Name = "Authorization",        //jwt默認的參數(shù)名稱
		In = ParameterLocation.Header,  //jwt默認存放Authorization信息的位置(請求頭中)
		Type = SecuritySchemeType.Http,
		Scheme = "Bearer"
	});
	//定義JwtBearer認證方式二
	//options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
	//{
	//    Description = "這是方式二(JWT授權(數(shù)據(jù)將在請求頭中進行傳輸) 直接在下框中輸入Bearer {token}(注意兩者之間是一個空格))",
	//    Name = "Authorization",//jwt默認的參數(shù)名稱
	//    In = ParameterLocation.Header,//jwt默認存放Authorization信息的位置(請求頭中)
	//    Type = SecuritySchemeType.ApiKey
	//});
	//聲明一個Scheme,注意下面的Id要和上面AddSecurityDefinition中的參數(shù)name一致
	var scheme = new OpenApiSecurityScheme
	{
		Reference = new OpenApiReference()
		{
			Id = "JWTBearer",  //這個名字與上面的一樣
			Type = ReferenceType.SecurityScheme
		}
	};
	//注冊全局認證(所有的接口都可以使用認證)
	c.AddSecurityRequirement(new OpenApiSecurityRequirement
	{
		{ scheme, Array.Empty<string>() }
	});
});
#endregion

(2) 測試登錄接口,輸入正確的賬號和密碼,接口返回JWT令牌
ASP.NET Core高級之認證與授權(二)--JWT認證前后端完整實現(xiàn),# ASP.NET Core,asp.net,后端,.net core(3)點擊“Authorize”按鈕,在對話框中輸入上一步獲取到的令牌
ASP.NET Core高級之認證與授權(二)--JWT認證前后端完整實現(xiàn),# ASP.NET Core,asp.net,后端,.net coreASP.NET Core高級之認證與授權(二)--JWT認證前后端完整實現(xiàn),# ASP.NET Core,asp.net,后端,.net core(4)輸入令牌后關閉對話框,右邊的小鎖為鎖住的狀態(tài)。接下來調用業(yè)務模塊的Api會自動講JWT令牌攜帶上。
ASP.NET Core高級之認證與授權(二)--JWT認證前后端完整實現(xiàn),# ASP.NET Core,asp.net,后端,.net core

四、Vue前臺使用JWT認證進行安全防護

登錄頁面的布局略。以下只演示如何獲取并緩存JWT令牌,以及在每個請求的時候攜帶上JWT令牌。

1. 登錄成功后,用sessionStorage保存JWT令牌

this.$http({
	url:"/api/Account/Login",
	method:"post",
	data:this.loginForm,
}).then((res)=>{
	if(res.data.code>0){
	   this.$message(res.data.msg);
	   sessionStorage.setItem('jwtToken',res.data.tokenInfo.access_token) //保存到瀏覽器緩存
	   this.$router.push('home') //跳轉到首頁
	}
})

2. 發(fā)送請求前,用axios攔截器添加以下請求頭

Authorization: Bearer jwt令牌字符串

// 在main.js中 添加請求攔截器
axios.interceptors.request.use(function (config) {
  // 在發(fā)送請求之前做些什么
  var tkn = sessionStorage.getItem("jwtToken");
  if(tkn!="")
    config.headers.Authorization = 'Bearer ' + tkn
  return config;
}, function (error) {
  // 對請求錯誤做些什么
  return Promise.reject(error);
});

本次對這個JWT認證進行了一個完整的封裝演示。如果本文對你有幫助的話,請點贊+評論+關注,或者轉發(fā)給需要的朋友。文章來源地址http://www.zghlxwxcb.cn/news/detail-789289.html

到了這里,關于ASP.NET Core高級之認證與授權(二)--JWT認證前后端完整實現(xiàn)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • .Net Core Jwt鑒權授權

    目錄 簡介 基于.Net Core 驗證方式 Jwt獲取Token 引入三方包 生成Token UserInfo JwtConfig WebApi測試(獲取Token) Program.cs appsetting.json Controller .Net Core 驗證(webApi) Progarm.cs Contorller .Net Core 授權 簡介 Program.cs JwtAuthorization.cs 注意 Autofac 注冊授權服務 Controller 注意 jwt觸發(fā)委托 Jwt分為三段 通過遠

    2024年02月13日
    瀏覽(53)
  • ASP.NET Core MVC 使用 JWT 的示例

    創(chuàng)建一個 ASP.NET Core MVC 項目。 添加 NuGet 包: Microsoft.AspNetCore.Authentication.JwtBearer:用于支持 JWT 的身份驗證。 System.IdentityModel.Tokens.Jwt:用于生成和驗證 JWT。 在 Startup.cs 文件中做如下修改: 請注意,在上述代碼中,您需要將以下參數(shù)替換為實際的值: \\\"your_issuer\\\" :發(fā)行者的標

    2024年02月13日
    瀏覽(25)
  • ASP.NET CORE WEBAPI 登錄 JWT 鑒權 ,接口權限驗證

    介紹 當今Web開發(fā)中,API的使用越來越廣泛,而API的安全性也變得越來越重要。其中,JWT(JSON Web Token)鑒權和授權是一種常見的解決方案。 本篇文章將會介紹JWT鑒權和授權的原理、實現(xiàn)方式以及注意事項。 什么是JWT? JWT是一種基于JSON格式的開放標準(RFC7519),用于在網(wǎng)絡

    2023年04月21日
    瀏覽(87)
  • ASP.NET Core 鑒權授權三(添加自定義授權策略)

    ASP.NET Core 鑒權授權三(添加自定義授權策略)

    此處鑒權給的值是6,授權用的1,嘗試訪問 基于策略的授權中有一個很重要的概念是Requirements,每一個Requirement都代表一個授權條件。 Requirement需要繼承接口IAuthorizationRequirement。 已經(jīng)內置了一些常用的實現(xiàn): AssertionRequirement :使用最原始的斷言形式來聲明授權策略。 DenyAn

    2024年02月03日
    瀏覽(22)
  • ASP.NET Core 授權二(自定義token)

    首先自定義一個類TokenAuthenticationHandler,然后需要繼承IAuthenticationHandler接口 具體代碼: 后續(xù)需要鑒權的接口,在請求上都需要加上Authorization參數(shù) Claim:相當于一個身份單元,存儲著鍵值信息 ClaimsIdentity:身份證,身份單元的集合(可以理解為身份證上有多個身份單元) Clai

    2024年02月03日
    瀏覽(24)
  • ASP.NET Core 授權一(簡單的Cookie)

    簡單的理解:鑒權衡量你能不能進一道門,授權是你進門了可以干什么

    2024年02月04日
    瀏覽(25)
  • 楊中科 ASP.NET Core前后端分離開發(fā)

    楊中科 ASP.NET Core前后端分離開發(fā)

    前后端分離 1、傳統(tǒng)MVC開發(fā)模式: 前后端的代碼被放到同一個項目中,前端人員負責編寫頁面的模板,而后端開發(fā)人員負責編寫控制器和模型的代碼并且“套模板”。 缺點: 互相依賴, 耦合性強,責任劃分不清 。 2、主流的“前后端分離”: 前端開發(fā)人員和后端開發(fā)人員分別負

    2024年01月23日
    瀏覽(18)
  • ASP.NET Core 鑒權授權一(簡單的Cookie)

    簡單的理解:鑒權衡量你能不能進一道門,授權是你進門了可以干什么

    2024年02月03日
    瀏覽(20)
  • ASP.NET Core 鑒權授權二(自定義token)

    首先自定義一個類TokenAuthenticationHandler,然后需要繼承IAuthenticationHandler接口 具體代碼: 后續(xù)需要鑒權的接口,在請求上都需要加上Authorization參數(shù) Claim:相當于一個身份單元,存儲著鍵值信息 ClaimsIdentity:身份證,身份單元的集合(可以理解為身份證上有多個身份單元) Clai

    2024年02月03日
    瀏覽(20)
  • ASP.NET Core 鑒權授權三(自定義授權策略、多授權策略、多鑒權架構)

    ASP.NET Core 鑒權授權三(自定義授權策略、多授權策略、多鑒權架構)

    此處鑒權給的值是6,授權用的1,嘗試訪問 基于策略的授權中有一個很重要的概念是Requirements,每一個Requirement都代表一個授權條件。 Requirement需要繼承接口IAuthorizationRequirement。 已經(jīng)內置了一些常用的實現(xiàn): AssertionRequirement :使用最原始的斷言形式來聲明授權策略。 DenyAn

    2024年02月03日
    瀏覽(34)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包