Google Code Prettify

顯示具有 TOKEN 標籤的文章。 顯示所有文章
顯示具有 TOKEN 標籤的文章。 顯示所有文章

JWT 作為身份驗證要如何作登入、登出及重新驗證?

使用 JWT(JSON Web Token)作為身份驗證的系統中,登錄、登出和重新驗證的實現涉及生
成、驗證和管理令牌。以下是實現這些功能的詳細步驟。

登錄
登錄過程包括驗證用戶憑證並生成一個 JWT,該 JWT 會包含用戶身份信息和其他聲明(claims)。

1. 建立登入端點
創建一個登入端點,該端點接受用戶憑證(例如,電子郵件和密碼)並返回一個 JWT。

csharp

[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest loginRequest)
{
    // 1. 驗證用戶憑證
    var user = _userService.ValidateUser(loginRequest.Email, loginRequest.Password);
    if (user == null)
    {
        return Unauthorized();
    }

    // 2. 生成 JWT
    var token = GenerateJwtToken(user);

    // 3. 返回 JWT
    return Ok(new { Token = token });
}

private string GenerateJwtToken(User user)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes("Your_Secret_Key");
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(new Claim[]
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Name, user.Username)
        }),
        Expires = DateTime.UtcNow.AddHours(1),
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
        Issuer = "your_issuer",
        Audience = "your_audience"
    };
    var token = tokenHandler.CreateToken(tokenDescriptor);
    return tokenHandler.WriteToken(token);
}

2. 定義登入請求模型
csharp

public class LoginRequest
{
    public string Email { get; set; }
    public string Password { get; set; }
}

登出
由於 JWT 是無狀態的,不需要服務器端存儲,因此登出通常是前端的操作。登出只需要在前端刪除 JWT。

前端處理登出
javascript

function logout() {
    localStorage.removeItem("jwtToken");
    window.location.href = "/login";
}



重新驗證
重新驗證(令牌刷新)需要提供一個新的 JWT,這通常涉及到一個刷新令牌。以下是實現令牌刷新的步驟。

1. 創建刷新令牌
每當用戶登錄時,除了返回 JWT,還返回一個刷新令牌。

csharp

[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequest loginRequest)
{
    var user = _userService.ValidateUser(loginRequest.Email, loginRequest.Password);
    if (user == null)
    {
        return Unauthorized();
    }

    var token = GenerateJwtToken(user);
    var refreshToken = GenerateRefreshToken();

    // 將刷新令牌保存到數據庫或內存中,關聯用戶
    _userService.SaveRefreshToken(user.Id, refreshToken);

    return Ok(new { Token = token, RefreshToken = refreshToken });
}

private string GenerateRefreshToken()
{
    var randomNumber = new byte[32];
    using (var rng = RandomNumberGenerator.Create())
    {
        rng.GetBytes(randomNumber);
        return Convert.ToBase64String(randomNumber);
    }
}

2. 創建刷新令牌請求端點
csharp

[HttpPost("refresh")]
public IActionResult Refresh([FromBody] RefreshRequest refreshRequest)
{
    var principal = GetPrincipalFromExpiredToken(refreshRequest.Token);
    var userId = principal.FindFirst(ClaimTypes.NameIdentifier).Value;
    
    var savedRefreshToken = _userService.GetRefreshToken(userId); // 從數據庫中獲取保存的刷新令牌

    if (savedRefreshToken != refreshRequest.RefreshToken)
    {
        return Unauthorized();
    }

    var newJwtToken = GenerateJwtToken(principal);
    var newRefreshToken = GenerateRefreshToken();
    
    _userService.SaveRefreshToken(userId, newRefreshToken); // 更新刷新令牌

    return Ok(new { Token = newJwtToken, RefreshToken = newRefreshToken });
}

private ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
{
    var tokenValidationParameters = new TokenValidationParameters
    {
        ValidateAudience = false,
        ValidateIssuer = false,
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Your_Secret_Key")),
        ValidateLifetime = false // 我們正在驗證過期的令牌,因此這裡不驗證過期時間
    };

    var tokenHandler = new JwtSecurityTokenHandler();
    var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out SecurityToken securityToken);
    var jwtSecurityToken = securityToken as JwtSecurityToken;

    if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
    {
        throw new SecurityTokenException("Invalid token");
    }

    return principal;
}

3. 定義刷新請求模型
csharp

public class RefreshRequest
{
    public string Token { get; set; }
    public string RefreshToken { get; set; }
}

總結
這樣的設計使得 JWT 可以用於用戶身份驗證,並且支持登錄、登出和重新驗證功能。JWT 的優點在於
其無狀態性,適合分布式系統中的身份驗證,但同時也需要考慮安全性問題,例如令牌過期時間、刷新
令牌的安全管理等。

在 ASP.NET Core MVC 應用中實現使用 JWT 的登入功能並將 JWT token 保存到 Session 中

在 ASP.NET Core MVC 應用中實現使用 JWT 的登入功能並將 JWT token 保存到 Session 中,涉及以下步驟:

創建登入頁面:用戶輸入他們的憑證(如用戶名和密碼)。
發送登入請求:從 MVC 應用發送一個請求到 API 來獲取 JWT。
存儲 JWT:將 JWT 存儲在 Session 中。
設置授權標頭:在後續請求中使用 JWT 進行身份驗證。



步驟 1:創建登入頁面
首先,在你的 MVC 應用中創建一個簡單的登入頁面,讓用戶輸入用戶名和密碼。

html

<!-- Views/Account/Login.cshtml -->
@{
    ViewData["Title"] = "Login";
}

<h2>@ViewData["Title"]</h2>

<form asp-action="Login" method="post">
    <div>
        <label asp-for="Username"></label>
        <input asp-for="Username" />
    </div>
    <div>
        <label asp-for="Password"></label>
        <input asp-for="Password" type="password" />
    </div>
    <button type="submit">Login</button>
</form>



步驟 2:創建登入模型
在 MVC 應用中創建一個模型來綁定登入頁面的輸入。

csharp

// Models/LoginViewModel.cs
using System.ComponentModel.DataAnnotations;

public class LoginViewModel
{
    [Required]
    public string Username { get; set; }

    [Required]
    public string Password { get; set; }
}



步驟 3:創建控制器來處理登入請求
創建一個控制器來處理登入邏輯,包括發送請求到 API 並獲取 JWT,然後將 JWT 存儲在 Session 中。

csharp

// Controllers/AccountController.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using YourNamespace.Models;

public class AccountController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;

    public AccountController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    [HttpGet]
    public IActionResult Login()
    {
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Login(LoginViewModel model)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }

        var client = _httpClientFactory.CreateClient();
        var loginUrl = "https://yourapi.com/api/auth/login";
        var loginData = new
        {
            Username = model.Username,
            Password = model.Password
        };

        var content = new StringContent(JsonSerializer.Serialize(loginData), Encoding.UTF8, "application/json");
        var response = await client.PostAsync(loginUrl, content);

        if (response.IsSuccessStatusCode)
        {
            var responseString = await response.Content.ReadAsStringAsync();
            var jwt = JsonSerializer.Deserialize<JwtResponse>(responseString);

            // 存儲 JWT 到 Session
            HttpContext.Session.SetString("JWT", jwt.Token);

            return RedirectToAction("Index", "Home");
        }

        ModelState.AddModelError(string.Empty, "Invalid login attempt.");
        return View(model);
    }
}

public class JwtResponse
{
    public string Token { get; set; }
}



步驟 4:配置 Session
在 Startup.cs 中配置 Session。

csharp

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient();
    services.AddControllersWithViews();
    services.AddSession(options =>
    {
        options.IdleTimeout = TimeSpan.FromMinutes(30); // 可以根據需求調整
        options.Cookie.HttpOnly = true;
        options.Cookie.IsEssential = true;
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();

    app.UseSession(); // 必須在 UseRouting 和 UseAuthentication 之間
    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}



步驟 5:設置授權標頭
在每次發送 HTTP 請求時,從 Session 中獲取 JWT 並設置授權標頭。

csharp

public class AuthorizedHttpClientHandler : DelegatingHandler
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public AuthorizedHttpClientHandler(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var token = _httpContextAccessor.HttpContext.Session.GetString("JWT");
        if (!string.IsNullOrEmpty(token))
        {
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("AuthorizedClient")
        .AddHttpMessageHandler<AuthorizedHttpClientHandler>();

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddTransient<AuthorizedHttpClientHandler>();

    services.AddControllersWithViews();
    services.AddSession(options =>
    {
        options.IdleTimeout = TimeSpan.FromMinutes(30);
        options.Cookie.HttpOnly = true;
        options.Cookie.IsEssential = true;
    });
}

使用 AuthorizedHttpClient 發送請求
在需要授權的請求中使用 AuthorizedHttpClient。

csharp

public class SomeController : Controller
{
    private readonly IHttpClientFactory _httpClientFactory;

    public SomeController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task<IActionResult> SomeAction()
    {
        var client = _httpClientFactory.CreateClient("AuthorizedClient");
        var response = await client.GetAsync("https://yourapi.com/api/protectedresource");

        if (response.IsSuccessStatusCode)
        {
            var data = await response.Content.ReadAsStringAsync();
            // 處理數據
        }
        else
        {
            // 處理錯誤
        }

        return View();
    }
}



將這些部分組合起來,你將擁有一個完整的 MVC 應用,允許用戶登入並將 JWT 存儲在 Session 中,從而在後續的請求中使用這個 JWT 進行身份驗證。

這種方式不僅實現了基於 JWT 的身份驗證,還充分利用了 ASP.NET Core 的 Session 管理功能來安全地存儲和使用 JWT。

[ASP.NET]如何為 Web API 2 加上及使用 Refresh Token 功能

預設的 Web API 專案中並沒有提供 Refresh Token 的範例,現在我們來作一個。



新增 SimpleRefreshTokenProvider 實作 IAuthenticationTokenProvider

public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
{
 //實際放到DB去存起來
 private static ConcurrentDictionary _refreshTokens = 
  new ConcurrentDictionary();

 public async Task CreateAsync(AuthenticationTokenCreateContext context)
 {
  var guid = Guid.NewGuid().ToString();

  //copy properties and set the desired lifetime of refresh token
  var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
  {
   IssuedUtc = context.Ticket.Properties.IssuedUtc,
   //時間是3個月
   ExpiresUtc = DateTime.UtcNow.AddMonths(3) 
  };
    
  var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);

  _refreshTokens.TryAdd(guid, refreshTokenTicket);
  context.SetToken(guid);
 }

 public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
 {
  AuthenticationTicket ticket;

  if (_refreshTokens.TryRemove(context.Token, out ticket))
  {
   context.SetTicket(ticket);
  }
 }

 public void Create(AuthenticationTokenCreateContext context)
 {
  throw new NotImplementedException();
 }

 public void Receive(AuthenticationTokenReceiveContext context)
 {
  throw new NotImplementedException();
 }
}




然後在 App_Start\Startup.Auth.cs 中設定 OAuthOptions 裡的RefreshTokenProvider,如下,

App_Start\Startup.Auth.cs

OAuthOptions = new OAuthAuthorizationServerOptions
{
 TokenEndpointPath = new PathString("/Token"),
 Provider = new ApplicationOAuthProvider(PublicClientId),
 RefreshTokenProvider = new SimpleRefreshTokenProvider(),
 AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
 AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
 AllowInsecureHttp = true
};




當我們取回 Token 時會同取得 refresh_token 的內容




再用 Refresh Token 來取得新的 Access Token 及 Refresh Token,如下







用過的 Refresh Token 會從 ConcurrentDictionary 移除。所以;之前用過的 Token 也就失效了。



[Web API]從零開始建立基於 Token 的 ASP.NET Web API 2 身份驗證機制 (Visual Studio 2017)

經由這個範例為基礎,您可以建立一套自己的 ASP.NET Web API 2 身份驗證機制,本範例是以 Visual Studio 2017 製作且在本文的後半部有詳細的資料測試畫面,但畢竟只是個範例,完整性會較不足,如密碼加密處理就不在本文中介紹。


1. 建立專案 ( TokenAuthorizationExample.csproj)

    新增專案時選擇 ASP.NET Web 應用程式


    選擇 Web API


    專案建立後系統會生成下圖的內容




2.啟用 SSL

點選 [方案總管] 中的專案名稱 (TokenAuthorizationExample),然後在屬性視窗中啟用 SSL。




點選 [方案總管] 中的專案名稱 (TokenAuthorizationExample)上點擊滑鼠右鍵,然後選擇內容,在 Web 頁籤將 URL 設定為屬性中 SSL URL 屬性中的值 ,並點選 [建立虛擬目錄]。




3.加套件


在套件管理器主控台執行以下指令藉以安裝必須的套件:

  Install-Package Microsoft.Owin.Host.SystemWeb -projectname TokenAuthorizationExample
  Install-Package Microsoft.Owin.Security.OAuth -projectname TokenAuthorizationExample
  Install-Package Microsoft.Owin.Cors -projectname TokenAuthorizationExample
  Install-Package Newtonsoft.Json -projectname TokenAuthorizationExample
  Install-Package Microsoft.AspNet.Identity.EntityFramework -Version 2.2.2 -projectname TokenAuthorizationExample


4.在 App_start 中 WebApiConfig.cs 檔增加 2 行程式

預設以 Json 傳回

\App_Start\WebApiConfig.cs
using System.Linq;
using System.Web.Http;

namespace TokenAuthorizationExample
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API 設定和服務

            // Web API 路由
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
            config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
        }
    }
}




5.加 Class

5.1
\AuthorizationServerProvider.cs
using System.Security.Claims;
using System.Threading.Tasks;

using Microsoft.Owin.Security.OAuth;

namespace TokenAuthorizationExample
{
    public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
    {
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            context.Validated();
        }
        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            var identity = new ClaimsIdentity(context.Options.AuthenticationType); // 取得身份聲明資料
            if(context.UserName == "admin" && context.Password == "admin") {
                identity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
                identity.AddClaim(new Claim("username", "admin"));
                identity.AddClaim(new Claim(ClaimTypes.Name, "morseAdmin"));
                context.Validated(identity);
            }
            else if(context.UserName == "user" && context.Password == "user") {
                identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
                identity.AddClaim(new Claim("username", "user"));
                identity.AddClaim(new Claim(ClaimTypes.Name, "morseUser"));
                context.Validated(identity);
            }
            else {
                context.SetError("Invalid_grant", "Provided username and password is incorrect.");
                return;
            }
        }
    }
}


5.2
\Startup.cs
using System;
using System.Web.Http;

using Microsoft.Owin;
using Microsoft.Owin.Cors;
using Microsoft.Owin.Security.OAuth;

using Owin;

[assembly: OwinStartup(typeof(TokenAuthorizationExample.Startup))]

namespace TokenAuthorizationExample
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // 如需如何設定應用程式的詳細資訊,請瀏覽 https://go.microsoft.com/fwlink/?LinkID=316888
            // enable cors origin requests
            app.UseCors(CorsOptions.AllowAll);

            var myProvider = new AuthorizationServerProvider();
            OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions {
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
                Provider = myProvider
            };
            app.UseOAuthAuthorizationServer(options);
            app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

            HttpConfiguration config = new HttpConfiguration();
            WebApiConfig.Register(config);
        }
    }
}


5.3
\AuthorizeAttribute.cs
using System.Web;
using System.Web.Http.Controllers;

namespace TokenAuthorizationExample
{
    public class AuthorizeAttribute : System.Web.Http.AuthorizeAttribute
    {
        protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
        {
            if(!HttpContext.Current.User.Identity.IsAuthenticated)
                base.HandleUnauthorizedRequest(actionContext);
            else
                actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);
        }
    }
}




6.加 Controller

\Controllers\DataController.cs
using System;
using System.Linq;
using System.Security.Claims;
using System.Web.Http;

namespace TokenAuthorizationExample.Controllers
{
    public class DataController : ApiController
    {
        [AllowAnonymous]
        [HttpGet]
        [Route("api/data/getall")]
        public IHttpActionResult Get()
        {
            return Ok("Now server time is: " + DateTime.Now.ToString());
        }
        [Authorize]
        [HttpGet]
        [Route("api/data/hello")]
        public IHttpActionResult GetHello()
        {
            var identity = (ClaimsIdentity)User.Identity;
            return Ok("Hellow " + identity.Name);
        }
        [Authorize(Roles = "admin")]
        [HttpGet]
        [Route("api/data/GetForAdmin")]
        public IHttpActionResult GetForAdmin()
        {
            var identity = (ClaimsIdentity)User.Identity;
            var role = identity.Claims
                .Where(c => c.Type == ClaimTypes.Role)
                .Select(c => c.Value);
            return Ok("Hellow " + identity.Name + ", Role: " + string.Join(",", role.ToList()));
        }
    }
}


7.測試

AllowAnonymous (不控管權限) 可以正常取得資料



取得 admin 令牌 (token)


以 admin 令牌 (token) 取得以 Authorize 控管權限的資料


以 admin 令牌 (token) 取得以 Authorize(Roles = "admin") 控管權限的資料





取得 user 令牌 (token)



METH: POSTHEADERS:             Content-Type: application/x-www-form-urlencodedBODY:             username: 你的帳號             password: 你的密碼             grant_type: password




 以 user 令牌 (token) 取得以 Authorize 控管權限的資料




METH: GETHEADERS:             Authorization: bearer [token access code]





 以 user 令牌 (token) 取得以 Authorize(Roles = "admin") 控管權限的資料時傳回 Http 403 。






[Web API]實用的開發範例 - 前端資料存取

這個範例我在撰寫時是有分為 Web API (REST web service) 及用戶端二大模塊,現在有很多的工程在前端 (用戶端) 的實作都是以 Javascript 這類的前端工程工具來製作,這些前端工具是有它的好處,至少在視覺上就能提高很多,但是;將它拿來向後端 (資料庫或Web服務) 拿資料,總覺得不太安全,由其是客戶的經營及生產方面的工程。


聲明:
1. 本範例屬中高階工程技術範疇,不適合初學者。
2. 學習本範例需要對 Web API 有基礎的概念。


以下的範例主要是示範前端 (C#) 向後端 Web API (REST web service) 拿資料,而我們會提供需權限認證及免權限認證二類的資料來源,而權限認證我們是以令牌 (Token) 的方式進行控管的。


修改 Web API 的控制器 (ValuesController.cs) :

\Controllers\ValuesController.cs
using System;
using System.Web.Http;

namespace LocalAccountsApp.Controllers
{
    [Authorize]
    public class ValuesController : ApiController
    {
        // GET api/values
        [AllowAnonymous]
        [HttpGet]
        [Route("api/GetValues")]
        public string GetValues()
        {
            return "嗨, 您來了.(http get)";
        }
        [HttpGet]
        [Route("api/GetAuthValues")]
        public string GetAuthValues()
        {
            var userName = this.RequestContext.Principal.Identity.Name;
            return String.Format("嗨, {0} 您來了.(http get)[by auth]", userName);
        }
        [AllowAnonymous]
        [HttpPost]
        [Route("api/PostValues")]
        public string PostValues()
        {
            return "嗨, 您來了.(http post)";
        }
        [HttpPost]
        [Route("api/PostAuthValues")]
        public string PostAuthValues()
        {
            var userName = this.RequestContext.Principal.Identity.Name;
            return String.Format("嗨, {0} 您來了.(http post)[by auth]", userName);
        }
    }
}



前端建立主控台應用程式專案,並修改 Program.cs :

\Program.cs
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;

using Newtonsoft.Json;

namespace HttpWebRequestGetTokenByPasswordExample
{
    public class MorseDataControl
    {
        MorseHttpWeb morseHttpWeb = new MorseHttpWeb();
        #region Get
        public string GetValues(string url)
        {
            return morseHttpWeb.GetHttpWeb(url);
        }
        public string GetAuthValues(string url, string accessToken)
        {
            return morseHttpWeb.GetHttpWeb(url, accessToken);
        }
        public async Task<string> GetValuesAsync(string url)
        {
            return await morseHttpWeb.GetHttpWebAsync(url);
        }
        public async Task<string> GetAuthValuesAsync(string url, string accessToken)
        {
            return await morseHttpWeb.GetHttpWebAsync(url, accessToken);
        }
        #endregion
        #region Post
        public string PostValues(string url, string query)
        {
            return morseHttpWeb.PostHttpWeb(url, query);
        }
        public async Task<string> PostValuesAsync(string url, string query)
        {
            return await morseHttpWeb.PostHttpWebAsync(url, query);
        }
        public string PostAuthValues(string url, string query, string accessToken)
        {
            return morseHttpWeb.PostHttpWeb(url, query, accessToken);
        }
        public async Task<string> PostAuthValuesAsync(string url, string query, string accessToken)
        {
            return await morseHttpWeb.PostHttpWebAsync(url, query, accessToken);
        }
        #endregion
    }
    public class MorseHttpWeb
    {
        /// <summary>
        /// 取得令牌
        /// </summary>
        /// <param name="url"></param>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <returns>令牌 (Token)</returns>
        public accessToken GetAccessToken(string url, string username, string password)
        {
            accessToken ret = new accessToken();
            WebRequest request = HttpWebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";

            // 方法1:
            // NameValueCollection outgoingQueryString = HttpUtility.ParseQueryString(String.Empty);
            // outgoingQueryString.Add("grant_type", "password");
            // outgoingQueryString.Add("username", username);
            // outgoingQueryString.Add("password", password);
            // byte[] postBytes = new ASCIIEncoding().GetBytes(outgoingQueryString.ToString()); // 轉成 byte[]

            //方法2:
            string s = string.Format("username={0}&password={1}&grant_type=password", username, password);
            byte[] postBytes = new ASCIIEncoding().GetBytes(s); // 轉成 byte[]

            Stream postStream = request.GetRequestStream();//取得用以寫入要求資料的 Stream 物件。
            postStream.Write(postBytes, 0, postBytes.Length);
            postStream.Flush();
            postStream.Close();

            using(WebResponse response = request.GetResponse()) {
                using(StreamReader streamReader = new StreamReader(response.GetResponseStream())) {
                    ret = JsonConvert.DeserializeObject<accessToken>(streamReader.ReadToEnd().Replace(".", ""));
                }
            }
            return ret;
        }
        /// <summary>
        /// 取得令牌(非同步)
        /// </summary>
        /// <param name="url"></param>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <returns>令牌 (Token)</returns>
        public async Task<accessToken> GetAccessTokenAsync(string url, string username, string password)
        {
            accessToken ret = new accessToken();
            WebRequest request = HttpWebRequest.Create(url);
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";

            // 方法1:
            // NameValueCollection outgoingQueryString = HttpUtility.ParseQueryString(String.Empty);
            // outgoingQueryString.Add("grant_type", "password");
            // outgoingQueryString.Add("username", username);
            // outgoingQueryString.Add("password", password);
            // byte[] postBytes = new ASCIIEncoding().GetBytes(outgoingQueryString.ToString()); // 轉成 byte[]

            //方法2:
            string s = string.Format("username={0}&password={1}&grant_type=password", username, password);
            byte[] postBytes = new ASCIIEncoding().GetBytes(s); // 轉成 byte[]

            Stream postStream = await request.GetRequestStreamAsync(); //傳回以非同步作業方式將資料寫入網際網路資源的 Stream
            postStream.Write(postBytes, 0, postBytes.Length);
            postStream.Flush();
            postStream.Close();

            using(WebResponse response = await request.GetResponseAsync()) {
                using(StreamReader streamReader = new StreamReader(response.GetResponseStream())) {
                    ret = JsonConvert.DeserializeObject<accessToken>((await streamReader.ReadToEndAsync()).Replace(".", ""));
                }
            }
            return ret;
        }
        /// <summary>
        /// HTTP GET Method Process
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="accessToken">令牌 (Token)</param>
        /// <returns></returns>
        public string GetHttpWeb(string uri, string accessToken = null)
        {
            string retVal = "";
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
            request.Method = "GET";
            if(accessToken != null)
                request.Headers.Add("Authorization", "Bearer " + accessToken);
            try {
                using(var response = request.GetResponse())
                    if(response != null) {
                        var reader = new StreamReader(response.GetResponseStream());
                        if(reader != null)
                            retVal = reader.ReadToEnd();
                    }
            }
            catch(Exception ex) {
                Console.WriteLine("Error: " + ex.ToString());
            }
            return retVal;
        }
        /// <summary>
        /// HTTP GET Method Process(非同步)
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="accessToken">令牌 (Token)</param>
        /// <returns></returns>
        public async Task<string> GetHttpWebAsync(string uri, string accessToken = null)
        {
            string retVal = "";
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
            request.Method = "GET";
            if(accessToken != null)
                request.Headers.Add("Authorization", "Bearer " + accessToken);
            try {
                using(var response = await request.GetResponseAsync())
                    if(response != null) {
                        var reader = new StreamReader(response.GetResponseStream());
                        if(reader != null)
                            retVal = await reader.ReadToEndAsync();
                    }
            }
            catch(Exception ex) {
                Console.WriteLine("Error: " + ex.ToString());
            }
            return retVal;
        }
        /// <summary>
        /// HTTP POST Method Process
        /// </summary>
        /// <param name="post_url"></param>
        /// <param name="post_query_string"></param>
        /// <returns></returns>
        public string PostHttpWeb(string post_url, string post_query_string, string accessToken = null)
        {
            var ret = "";
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(post_url);
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            if(accessToken != null)
                request.Headers.Add("Authorization", "Bearer " + accessToken);
            try {
                using(var writer = new StreamWriter(request.GetRequestStream())) writer.Write(post_query_string);
                using(var response = request.GetResponse()) {
                    var reader = new StreamReader(response.GetResponseStream());
                    if(reader != null)
                        ret = reader.ReadToEnd();
                }
            }
            catch(Exception ex) {
                Console.WriteLine("Error: " + ex.ToString());
            }
            return ret;
        }
        /// <summary>
        /// HTTP POST Method Process(非同步)
        /// </summary>
        /// <param name="post_url"></param>
        /// <param name="post_query_string"></param>
        /// <param name="accessToken">令牌 (Token)</param>
        /// <returns></returns>
        public async Task<string> PostHttpWebAsync(string post_url, string post_query_string, string accessToken = null)
        {
            var ret = "";
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(post_url);
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            if(accessToken != null)
                request.Headers.Add("Authorization", "Bearer " + accessToken);
            try {
                using(var writer = new StreamWriter(await request.GetRequestStreamAsync())) writer.Write(post_query_string);
                using(var response = await request.GetResponseAsync()) {
                    var reader = new StreamReader(response.GetResponseStream());
                    if(reader != null)
                        ret = await reader.ReadToEndAsync();
                }
            }
            catch(Exception ex) {
                Console.WriteLine("Error: " + ex.ToString());
            }
            return ret;
        }
    }
    public class MorseProcess
    {
        public MorseProcess()
        {
        }
        public async Task HttpWebTest() { 
            string userName = "xxxxxx@gmail.com";
            string userPassword = "xxxxxx";
            string urlString = "https://localhost:8087/token";

            Console.WriteLine((new MorseDataControl()).GetValues("https://localhost:8087/api/GetValues"));
            Console.WriteLine(await (new MorseDataControl()).GetValuesAsync("https://localhost:8087/api/GetValues"));
            Console.WriteLine((new MorseDataControl()).PostValues("https://localhost:8087/api/PostValues", ""));
            Console.WriteLine(await (new MorseDataControl()).PostValuesAsync("https://localhost:8087/api/PostValues", ""));

            accessToken t = (new MorseHttpWeb()).GetAccessToken(urlString, userName, userPassword); // 令牌
            accessToken tAsync = (new MorseHttpWeb()).GetAccessToken(urlString, userName, userPassword); // 令牌
            Console.WriteLine("Token 1: " + t.access_token);
            Console.WriteLine("Token 2: " + tAsync.access_token);

            Console.WriteLine((new MorseDataControl()).GetAuthValues("https://localhost:8087/api/GetAuthValues", t.access_token));
            Console.WriteLine(await (new MorseDataControl()).GetAuthValuesAsync("https://localhost:8087/api/GetAuthValues", t.access_token));
            Console.WriteLine((new MorseDataControl()).PostAuthValues("https://localhost:8087/api/PostAuthValues", "", t.access_token));
            Console.WriteLine(await (new MorseDataControl()).PostAuthValuesAsync("https://localhost:8087/api/PostAuthValues", "", t.access_token));
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            (new MorseProcess()).HttpWebTest().Wait();
            Console.Write("Press any key...");
            Console.ReadKey();
        }
    }
    public class accessToken
    {
        public dynamic access_token { get; set; }
        public string token_type { get; set; }
        public int expires_in { get; set; }
        public string userName { get; set; }
        public DateTime issued { get; set; }
        public DateTime expires { get; set; }
    }
}


執行結果: