HttpClient调用受JWT保护的Api

准备工作:

一个Asp.Net Core Api 程序,程序的功能大概有两个:模拟验证用户登录,权限认证模块给用户颁发Jwt,用户带token来调用Api资源。

首先简单介绍一下JWT的数据结构,JWT由头部载荷签名这三部分组成,中间以「.」分隔。

头部以 JSON 格式表示,用于指明令牌类型和加密算法。形式如下,表示使用 JWT 格式,加密算法采用 HS256。

{
  "alg": "HS256",
  "typ": "JWT"
}

载荷用来存储服务器需要的数据,比如用户信息,要注意的是重要的机密信息最好不要放到这里,比如密码。

{
    "name": "zhouxieyi",
    "introduce": "a test token"
}

另外,JWT 还规定了 7 个字段供开发者选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

签名使用HMACSHA256算法计算得出,这个方法有两个参数,前一个参数是 (base64 编码的头部 + base64 编码的载荷)用点号相连,后一个参数是自定义的字符串密钥,密钥不要暴露在客户端。

我们在appsettings.json中存放了我们自定义的token信息如下:

"tokenManagement": {
    "issuer": "webapi.cn",
    "name": "zhouxieyi",
    "introduce": "a test token",
    "audience": "WebApi",
    "secret": "qwertyuiopasdfghjklzxcvbnm",
    "accessExpiration": 30,
    "refreshExpiration": 60
  }

下一步配置StartUp.cs,将身份认证中间件加入引用,并引入NuGet:Microsoft.AspNetCore.Authentication.JwtBearer,在Configure中配置好服务。

创建用户User,UserService来模拟用户登录,这里我们假使所有的用户都合法。用户通过校验后,利用AuthenticationService颁发token,根据这两步逻辑,我们编写接口和控制器来完成。

代码如下:

using System.Text;
using authapi.Models;
using authapi.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;

namespace authapi
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.Configure<TokenManagement>(Configuration.GetSection("tokenManagement"));
            var token = Configuration.GetSection("tokenManagement").Get<TokenManagement>();

            services.AddAuthentication(x =>
            {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(options =>
            {
                options.RequireHttpsMetadata = false;
                options.SaveToken = true;

                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(token.Secret)),
                    ValidIssuer = token.Issuer,
                    ValidAudience = token.Audience,
                    ValidateIssuer = false,
                    ValidateAudience = false
                };
            });

            services.AddScoped<IUserService, UserService>();
            services.AddScoped<IAuthenticationService, TokenAuthenticateService>();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}
StartUp.cs
public interface IAuthenticationService
{
    bool IsAuthenticated(User user, out string token);
}

public class TokenAuthenticateService : IAuthenticationService
{
    private readonly IUserService _userService;
    private readonly TokenManagement _tokenManagement;

    public TokenAuthenticateService(IUserService userService,
        IOptions<TokenManagement> tokenManagement)
    {
        _userService = userService;
        _tokenManagement = tokenManagement.Value;
    }

    public bool IsAuthenticated(User user, out string token)
    {
        token = string.Empty;
        if (!_userService.Validate(user))
            return false;
        var claims = new[]
        {
            new Claim(ClaimTypes.Name,user.UserName)
        };

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenManagement.Secret));
        var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var jwtToken = new JwtSecurityToken(
            _tokenManagement.Issuer,
            _tokenManagement.Audience, 
            claims,
            expires: DateTime.Now.AddMinutes(_tokenManagement.AccessExpiration),
            signingCredentials: credentials);

        token = new JwtSecurityTokenHandler().WriteToken(jwtToken);

        return true;
    }
}
AuthenticationService
public interface IUserService
{
    bool Validate(User user);
}

public class UserService : IUserService
{
    public bool Validate(User user)
    {
        return true;
    }
}
UserService
using authapi.Models;
using authapi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace authapi.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class AuthenticationController : ControllerBase
    {
        private readonly IAuthenticationService _authenticationService;

        public AuthenticationController(IAuthenticationService authenticationService)
        {
            _authenticationService = authenticationService;
        }

        [AllowAnonymous]
        [HttpPost, Route("requestToken")]
        public ActionResult RequestToken([FromBody] User user)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest("Model Error");
            }

            if (_authenticationService.IsAuthenticated(user, out var token))
            {
                return Ok(token);
            }

            return BadRequest("Invalid Request");
        }
    }
}
AuthenticationController
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authorization;

namespace authapi.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        [Authorize]
        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            }).ToArray();
        }
    }
}
WeatherForecastController
public class TokenManagement
{
    [JsonPropertyName("secret")]
    public string Secret { get; set; }

    [JsonPropertyName("issuer")]
    public string Issuer { get; set; }

    [JsonPropertyName("audience")]
    public string Audience { get; set; }

    [JsonPropertyName("accessExpiration")]
    public int AccessExpiration { get; set; }

    [JsonPropertyName("refreshExpiration")]
    public int RefreshExpiration { get; set; }

}

public class User
{
    [Required]
    [JsonPropertyName("username")]
    public string UserName { get; set; }

    [Required]
    [JsonPropertyName("password")]
    public string PassWord { get; set; }
}

public class WeatherForecast
{
    public DateTime Date { get; set; }

    public int TemperatureC { get; set; }

    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);

    public string Summary { get; set; }
}
Models

运行程序,使用PostMan测试:

先利用用户获取Token

 

 设置Token类型,输入我们刚刚获取的Token,访问Api成功获取数据。


 

HttpClient:

创建一个Console程序,创建HttpClient,HttpClient类实例充当发送 HTTP 请求的会话。每个 HttpClient 实例都使用其自己的连接池,并从其他实例所执行的请求隔离其请求 HttpClient。

利用.NET Json处理拓展包:using System.Net.Http.Json,可以很方便的带Json对象发送请求。具体逻辑流程是使用client带用户信息请求token,获取成功后将token绑定到Header中:Authorization : Bearer + token即可,在请求Api即可获取资源。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

namespace any
{
    public class Program
    {
        static async Task Main(string[] args)
        {
            var client = new HttpClient();
            var uri = new Uri("https://localhost:5001");
            var user = new User
            {
                UserName = "client",
                PassWord = "123321"
            };

            //利用SendAsync发送请求,在Request中设置Content或Header传入。
            //var postRequest = new HttpRequestMessage(
            //    HttpMethod.Post,
            //    uri.AbsoluteUri + "api/Authentication/RequestToken")
            //{
            //    Content = JsonContent.Create(user),
            //};
            //var response = await client.SendAsync(postRequest);

            //使用PostAsJsonAsync传递json
            var response = await client.PostAsJsonAsync(uri.AbsoluteUri + "api/Authentication/RequestToken", user);
            var token = await response.Content.ReadAsStringAsync();

            Console.WriteLine($"token: {token}");

            //设置Header
            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");

            var result = await client.GetStringAsync(uri.AbsoluteUri + "api/WeatherForecast");

            Console.WriteLine($"string result: {result}");

            var jsonResponse = await client.GetAsync(uri.AbsoluteUri + "api/WeatherForecast");

            var jsonObjs = await jsonResponse.Content.ReadFromJsonAsync<List<WeatherForecast>>();

            if (jsonObjs != null)
            {
                foreach (var weatherForecast in jsonObjs)
                {
                    Console.WriteLine($"Date is {weatherForecast.Date}, is {weatherForecast.Summary} day");
                }
            }
            
        }

    }
}
Program.cs

result:

posted @ 2021-05-13 17:07  zhouslthere  阅读(882)  评论(0编辑  收藏  举报