上一篇我们使用IOC容器解决了依赖问题,同时简单配置了WebApi环境,本章我们使用一下Swagger,并通过Jwt完成授权


一、Swagger的使用

1、什么是Swagger

前后端分离项目中,后端人员开发完成后通常会编写API接口文档,说明方法对应的功能、参数等信息,也就是说前后端唯一的联系就是API接口,书写良好规范的API接口能极大的减缓前后端人员之间扯皮的频率。swagger则是能够让后端开发人员更加便捷的书写规范API文档的一款框架。

2、Swagger相关配置

  1. 使用NuGet搜索Swashbuckle.AspNetCore,安装在BlogSystem.Core项目中,如下:

  1. 打开Startup类,在ConfigureServices 方法中添加服务,如下:

                //注册Swagger服务
    services.AddSwaggerGen(options =>
    {
    options.SwaggerDoc("V1",new OpenApiInfo
    {
    Version = "V1",
    Title = "BlogSystem API Doc-V1",
    Description = "BlogSystem API接口文档-V1版",
    Contact = new OpenApiContact { Name = "BlogSystem", Email = "xxx@xx.com" },
    });
    options.OrderActionsBy(x=>x.RelativePath);
    });
  2. 在打开Startup类的Configure方法中添加中间件,这里可以配置在开发环境,如下:

  3. 运行项目,成功显示Swagger页面,但是报了一个Not Found /swagger/v1/swagger.json错误,检查发现是版本号大小写不一致导致,将中间件配置中的v1改成大写后解决

3、Swagger注释功能

上面操作完成后,由于没有备注信息,不熟悉的人无法知道每个接口对应的功能,下面我们加上备注信息功能;

1、右击项目名称,选择属性—生成,勾选XML文档文件

2、保存后发现多了很多成员注释的警告,身为强迫症坚决不能忍,再次打开生成页面,在取消显示警告中添加1591,保存,解决...这里的注释需要在方法名上使用三个左斜杠进行标注,我们可以在之前添加的注册方法上添加备注

3、这个时候还没完,在ConfigureServices 方法中注册服务指向刚刚添加的生成的xml文件路径,如下图

4、这个时候运行一下,发现ViewModel的注释没有显示,直接对Model层做上述相同操作,但是输出目录要选到当前BlogSystem.Core项目对应的路径,服务注册时不用带上第二个参数。完成后运行,终于OK

二、授权验证与Jwt

1、授权与认证

1.1、授权与验证的概念

首先,要区分授权与认证的概念。简单来说,授权:通过一些信息确认其身份;认证:确认该身份对应的权限。

举个例子:一栋办公楼,只有工作人员才能进入,那么工作人员可以凭借员工卡证明自己的身份,进入大楼,即为授权;如果员工张三是办公楼内A公司的员工,那么他只能进出A公司,而李四是办公楼物管处的员工,那么他可以进出整栋办公楼的所有公司,即为验证。

1.2、实现的几种方法

Web应用程序是基于Http请求的,但是Http请求是无状态的,无法记住我们的登录状态,怎么办呢?

1、Cookie:常用的处理办法是将用户的登录信息以key-value的形式存在客户端浏览器的Cookie中,客户端每次发送请求时服务器端会判断并验证客户端有无对应的Cookie,存在则表示你是授权过的用户,可以进行授权后的操作;

2、Session方法:它是存储于服务器内存中key-value集合;客户端向服务器发送请求,如果请求头中没有SessionId,服务器会分配一个给客户端,并存放在客户端的Cookie中;如果请求头中有SessionId,则会带着该值一起发送到服务器,服务器根据Session找到授权信息,进行授权判断操作。其实现也需要基于客户端的Cookie

3、令牌验证:即在Http请求信息中加入令牌的信息,将用户的信息和令牌设定通过算法加密后存入Http请求信息中,客户端发送请求时也带上令牌信息来表明自己的身份,服务端进行权限的验证

这三种方法各有利弊,可以根据业务需求进行选择使用,这里我使用jwt仅仅是练手...

2、JWT介绍及问题

2.1、Jwt简介

Jwt是基于令牌的方法,网上资料很多,这里简单说明下,它由Header、Payload、Signature三部分组成。

  • Header包含了加密算法和加密的对象类型,它会经过BASE64编码后存入token中;

  • Payload用来存放一些声明信息,数据格式为键值对形式,官方定义了部分字段如签发人,签发时间,生效时间,过期时间等,当然我们也可以自定义添加,它同样会经过BASE64编码后存入token中;

  • Signature为密钥,这部分需要绝对保密,系统默认会使用Header中声明的算法将三者结合起来产生一串字符

2.2、使用Jwt的流程

  1. 用户成功登录后,服务器后端根据设定的令牌信息和用户信息进行加密生成一串加密的字符,返回给前端;

  2. 前端将后端返回的信息进行保存,一般会选择客户端浏览器中的Cookie或localStorage进行存放;

  3. 客户端发送请求前,会验证本地Cookie或localStorage中是否存在Jwt的信息,存在则加入Http请求头中;

  4. 服务器接受Http请求时,对Http请求头中的Jwt信息进行验证,验证通过后则授权成功

2.3、JWT存在的问题

1、安全性:有的朋友会说,将信息加入请求头,万一别人拦截复制我请求头中的信息使用怎么办呢?

  • 任何方案都是有利有弊的,传统的session+cookie 方案,如果泄露了 sessionId同样会存在此类问题。其实只要做到以下几点就可以极大程度的避免此类情况:①使用https 加密Web应用;②将jwt存入cookie中;③返回 jwt 给客户端时设置 httpOnly=true,能有效阻止XSS 攻击和 CSRF 攻击

2、注销和修改密码问题:传统的 session+cookie 方案用户点击注销,服务端清空 session 即可;但是Jwt是无状态的,且服务端没有保存,即使客户端删除了JWT,它仍然是在有效期内的,相应的解决办法如下

  • ①删除客户端的Cookie,但如果用户通过某种手段记住且在请求头中添加了JWT,在有效期内仍然是可以访问的,即使是在这段时间内修改了密码的情况下;②将JWT令牌中的Secret设置为和用户相关的动态数值,用户注销后改变Secret的值,但这样JWT是不变的,使用原先的JWT会无法登录;③借助第三方,如NoSql数据库存储JWT的状态,但这违背了JWT无状态的特性

3、续签问题:payload中会存储一个有效期时间,时间一到就无法访问了;传统的 session+cookie 是会自动续签的,所有没有这个问题。对应的几个解决方案如下:

  • ①快要过期的时候刷新 jwt,这个只有快到期时用户访问了网站才有机会触发;②第三方记录过期时间,每次访问刷新过期时间;③每次请求刷新过期时间,有点暴力...会有性能影响;④将第一、第三条方案中和一下,每次访问都判断过期时间是否在预设的一个时间段内,在就刷新

三、配置使用JWT

这里我们先完成授权部分的功能,注销、续签、以及权限验证功能等后续再进一步完善

1、配置文件

首先我们在appsettings.json文件中加上Payload需要用到的字段信息,如下:

2、注册Jwt

ASP.NET Core中已经封装了授权方法,在Action方法上方添加[Authorize],即表示需要授权才能访问;如何进行授权的验证呢?那就需要我们来定义了,使用NuGet包安装下方插件,在StartUp类中进行服务的注册,如下Microsoft.AspNetCore.Authentication.JwtBearer

3、启用中间件

同时我们需要在Startup另一个方法中启用授权中间件,这里把验证中间件一并加上,需要注意其顺序

4、JWT方法封装

接下来的问题是用户信息如何封装为Jwt令牌,我们在BlogSystem.Core项目下建一个Helpers文件夹,再新建一个JwtHelper类,添加对应的封装方法和解析方法。功能如下:

using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text; namespace BlogSystem.Mvc.Helpers
{
public static class JwtHelper
{
private static IConfiguration _configuration;
//获取Startup构造函数中的Configuration对象
public static void GetConfiguration(IConfiguration configuration)
{
_configuration = configuration;
} /// <summary>
/// Jwt加密
/// </summary>
/// <param name="tokenModel"></param>
/// <returns></returns>
public static string JwtEncrypt(TokenModelJwt tokenModel)
{
//获取配置文件中的信息
var iss = _configuration["JwtTokenManagement:issuer"];
var aud = _configuration["JwtTokenManagement:audience"];
var secret = _configuration["JwtTokenManagement:secret"]; //设置声明信息
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Jti, tokenModel.UserId.ToString()),//Jwt唯一标识Id
new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),//令牌签发时间
new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,//不早于的时间声明
new Claim(JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddHours(24)).ToUnixTimeSeconds()}"),//令牌过期时间
new Claim(ClaimTypes.Expiration, DateTime.Now.AddHours(24).ToString(CultureInfo.CurrentCulture)),//令牌截至时间
new Claim(JwtRegisteredClaimNames.Iss,iss),//发行人
new Claim(JwtRegisteredClaimNames.Aud,aud),//订阅人
new Claim(ClaimTypes.Role,tokenModel.Level)//权限——目前只支持单权限
}; //密钥处理,key和加密算法
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
var cred = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); //封装成jwt对象
var jwt = new JwtSecurityToken(
claims: claims,
signingCredentials: cred
); //生成返回jwt令牌
return new JwtSecurityTokenHandler().WriteToken(jwt);
} /// <summary>
/// Jwt解密
/// </summary>
/// <param name="jwtStr"></param>
/// <returns></returns>
public static TokenModelJwt JwtDecrypt(string jwtStr)
{
if (string.IsNullOrEmpty(jwtStr)||string.IsNullOrWhiteSpace(jwtStr))
{
return new TokenModelJwt();
}
jwtStr = jwtStr.Substring(7);//截取前面的Bearer和空格
var jwtHandler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr); jwtToken.Payload.TryGetValue(ClaimTypes.Role, out object level); var model = new TokenModelJwt
{
UserId = Guid.Parse(jwtToken.Id),
Level = level == null ? "" : level.ToString()
};
return model;
}
} /// <summary>
/// 令牌包含的信息
/// </summary>
public class TokenModelJwt
{
public Guid UserId { get; set; } public string Level { get; set; }
}
}

其中我们把Configuration对象在startup构造函数中传递了过来

5、Jwt加密功能测试

1、修改注册功能和添加登录功能,如下:

        /// <summary>
/// 用户注册
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost(nameof(Register))]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (!await _userService.Register(model))
{
return Ok("用户已存在");
}
return Ok("创建成功");
} /// <summary>
/// 用户登录
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost(nameof(Login))]
public async Task<IActionResult> Login(LoginViewModel model)
{
//判断账号密码是否正确
var userId = await _userService.Login(model);
if (userId == Guid.Empty) return Ok("账号或密码错误!"); //登录成功进行jwt加密
var user = await _userService.GetOneByIdAsync(userId);
TokenModelJwt tokenModel = new TokenModelJwt { UserId = user.Id, Level = user.Level.ToString() };
var jwtStr = JwtHelper.JwtEncrypt(tokenModel);
return Ok(jwtStr);
}

2、这里我们使用Swagger接口进行测试,输入账号密码,成功拿到加密字符,如下:

6、授权测试

1、为了测试授权,我们新增一个方法,在上方标注【Authorize】,如下:

        [Authorize]
[HttpPost("Test")]
public ActionResult Test()
{
return Ok("测试");
}

2、测试如下,401错误未授权,无法访问

3、我们使用PostMan在请求头中插入Token,注意前面加了Bearer和一个空格,返回200成功执行

7、Swagger配置验证功能

上面可以看到,需要测试授权时,我们只能通过PostMan在请求头插入Token信息,Swagger其实也是可以添加授权功能的,下面我们来配置一下环境

1、使用NuGet安装如下插件,如下:

2、在StartUp类的ConfigureService方法的Swagger注册服务中添加如下信息,其中方法名一定要为oauth2,不知道为什么

3、运行一下,右上角出现了锁,使用登录得到加密字符后,点击右上角Authorize输入Bearer+空格+加密字符,表示已授权后,选择上面建的测试方法,点击执行,成功返回200状态码

本章完~

本人知识点有限,若文中有错误的地方请及时指正,方便大家更好的学习和交流。

本文部分内容参考了网络上的视频内容和文章,仅为学习和交流,地址如下:

老张的哲学,系列教程一目录:.netcore+vue 前后端分离

徐靖峰,深入理解JWT的使用场景和优劣

原创文章声明

学习ASP.NET Core(05)-使用Swagger与Jwt授权的更多相关文章

  1. ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了

    引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必是件很痛苦的事情吧,但文档又必须写,而且文档的格式如果没有具体要求的话,最终完成的文档则完全取决于开发者 ...

  2. ASP.NET Core WebApi使用Swagger生成api

    引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必是件很痛苦的事情吧,但文档又必须写,而且文档的格式如果没有具体要求的话,最终完成的文档则完全取决于开发者 ...

  3. ASP.NET Core WebApi使用Swagger生成api说明文档

    1. Swagger是什么? Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格的 Web 服务.总体目标是使客户端和文件系统作为服务器以同样的速度来更新.文件 ...

  4. 【转】ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了

    原文链接:https://www.cnblogs.com/yilezhu/p/9241261.html 引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必 ...

  5. 学习ASP.NET Core,你必须了解无处不在的“依赖注入”

    ASP.NET Core的核心是通过一个Server和若干注册的Middleware构成的管道,不论是管道自身的构建,还是Server和Middleware自身的实现,以及构建在这个管道的应用,都需要 ...

  6. 学习ASP.NET Core Razor 编程系列二——添加一个实体

    在Razor页面应用程序中添加一个实体 在本篇文章中,学习添加用于管理数据库中的书籍的实体类.通过实体框架(EF Core)使用这些类来处理数据库.EF Core是一个对象关系映射(ORM)框架,它简 ...

  7. 学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  8. 学习ASP.NET Core Razor 编程系列五——Asp.Net Core Razor新建模板页面

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  9. 学习ASP.NET Core Razor 编程系列六——数据库初始化

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

随机推荐

  1. [RCTF2015]EasySQL

    [RCTF2015]EasySQL EasySQL github 打开靶机,是如下界面 到注册页面,试了一下,username 和 email 处有过滤,直接 fuzz 一下哪些字符被禁了 注册成功之 ...

  2. MyBatis model、xml、mapper 自动生成,附源码

    Mybatis 代码自动生成 model.xml.mapper 代码结构图 代码地址 https://github.com/shootercheng/codegen 需要修改的地方见 readme

  3. MySQL之外键、主键、自增

    1.创建外键 create table userinfo( uid int auto_increment primary key, name varchar(32), department_id in ...

  4. C#多线程(15):任务基础③

    目录 TaskAwaiter 延续的另一种方法 另一种创建任务的方法 实现一个支持同步和异步任务的类型 Task.FromCanceled() 如何在内部取消任务 Yield 关键字 补充知识点 任务 ...

  5. PHP中的数据库操作

    PDO project data object 连接到数据库 $db=new PDO("mysql:dbname=database;host=sever","userna ...

  6. docker 部署FastDFS

    教程:https://blog.csdn.net/fangchao2011/article/details/103202591 教程:https://www.jianshu.com/p/3f80cba ...

  7. nginx开启ssl并把http重定向到https的两种方式

    1 简介 Nginx是一个非常强大和流行的高性能Web服务器.本文讲解Nginx如何整合https并将http重定向到https. https相关文章如下: (1)Springboot整合https原 ...

  8. Python开源框架总结

    Django: Python Web应用开发框架 Django 应该是最出名的Python框架,GAE甚至Erlang都有框架受它影响.Django是走大而全的方向,它最出名的是其全自动化的管理后台: ...

  9. linux系统单网卡绑定多个IP地址

    说明: 单网卡绑定两个IP地址,电信和联通,目的:是为了当电信出故障联通正常使用. 系 统 IP地址 子网掩码 网关 CentOS 6.3_64bit eth0:116.18.176.19 255.2 ...

  10. Nakamori Akina

    听过中森明菜的歌以后,一直想写点什么.恰好前段时间看过她的一个访谈https://b23.tv/av13810011,节目里已经39岁左右的她看着已经有些衰老,但是那份属于她的天真却保持的很好. 节目 ...