学习ASP.NET Core(05)-使用Swagger与Jwt授权
上一篇我们使用IOC容器解决了依赖问题,同时简单配置了WebApi环境,本章我们使用一下Swagger,并通过Jwt完成授权
一、Swagger的使用
1、什么是Swagger
前后端分离项目中,后端人员开发完成后通常会编写API接口文档,说明方法对应的功能、参数等信息,也就是说前后端唯一的联系就是API接口,书写良好规范的API接口能极大的减缓前后端人员之间扯皮的频率。swagger则是能够让后端开发人员更加便捷的书写规范API文档的一款框架。
2、Swagger相关配置
- 使用NuGet搜索Swashbuckle.AspNetCore,安装在BlogSystem.Core项目中,如下:
打开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);
});
在打开Startup类的Configure方法中添加中间件,这里可以配置在开发环境,如下:
运行项目,成功显示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的流程
用户成功登录后,服务器后端根据设定的令牌信息和用户信息进行加密生成一串加密的字符,返回给前端;
前端将后端返回的信息进行保存,一般会选择客户端浏览器中的Cookie或localStorage进行存放;
客户端发送请求前,会验证本地Cookie或localStorage中是否存在Jwt的信息,存在则加入Http请求头中;
服务器接受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授权的更多相关文章
- ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了
引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必是件很痛苦的事情吧,但文档又必须写,而且文档的格式如果没有具体要求的话,最终完成的文档则完全取决于开发者 ...
- ASP.NET Core WebApi使用Swagger生成api
引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必是件很痛苦的事情吧,但文档又必须写,而且文档的格式如果没有具体要求的话,最终完成的文档则完全取决于开发者 ...
- ASP.NET Core WebApi使用Swagger生成api说明文档
1. Swagger是什么? Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RESTful 风格的 Web 服务.总体目标是使客户端和文件系统作为服务器以同样的速度来更新.文件 ...
- 【转】ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了
原文链接:https://www.cnblogs.com/yilezhu/p/9241261.html 引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必 ...
- 学习ASP.NET Core,你必须了解无处不在的“依赖注入”
ASP.NET Core的核心是通过一个Server和若干注册的Middleware构成的管道,不论是管道自身的构建,还是Server和Middleware自身的实现,以及构建在这个管道的应用,都需要 ...
- 学习ASP.NET Core Razor 编程系列二——添加一个实体
在Razor页面应用程序中添加一个实体 在本篇文章中,学习添加用于管理数据库中的书籍的实体类.通过实体框架(EF Core)使用这些类来处理数据库.EF Core是一个对象关系映射(ORM)框架,它简 ...
- 学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面
学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...
- 学习ASP.NET Core Razor 编程系列五——Asp.Net Core Razor新建模板页面
学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...
- 学习ASP.NET Core Razor 编程系列六——数据库初始化
学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...
随机推荐
- Laravel - 基础
1.使用 composer 创建项目 composer create-project --prefer-dist laravel/laravel blog 报错1 [ErrorException]pr ...
- 详解 继承(下)—— super关键字 与 多态
接上篇博文--<详解 继承(上)-- 工具的抽象与分层> 废话不多说,进入正题: 本人在上篇"故弄玄虚",用super();解决了问题,这是为什么呢? 答曰:子类中所有 ...
- 在dwr的调用类里获取请求信息
在dwr的调用类里获取请求的相关信息HttpSession session = WebContextFactory.get().getSession();HttpServletResponse res ...
- GoJS 教程新手入门(资源整理,解决方案)
以下几个是我在百度.谷歌 上能找到的比较全的GoJs的一些东西,希望对各位有所帮助! 如有外网网站不能访问请自行FQ GoJS官网 第一个推荐的是GoJS的一个类似于社区的问题讨论区,这里面初学者的一 ...
- php时间:获取上一个月,本月天数,下一个月
时间戳转日期 date() 日期转时间戳 strtotime() 当前时间戳time() 获取当前月的天数: $i=; $y=; echo date("t",strtotime(& ...
- 2019-2020-1 20199328《Linux内核原理与分析》第四周作业
<Linux内核原理与分析>第四周作业 步骤一 首先我们指定一个内核并指定内存根文件系统,这里的bzImage是vmLinux经过gzip压缩的内核,"b"表示&quo ...
- 对于WebP格式入门解读
因为项目中需要用到大量动画效果,前期尝试过几种方案,比如GIF.帧动画.lottie.SVGA等格式的动画渲染方案,发现都存在各式各样的问题.比如: 1,GIF格式.5秒的动画,一张图大小可能就会达到 ...
- SpringBoot应用操作Rabbitmq(topic交换器高级操作)
一.topic交换器为主题交换器,可以根据路由key模糊匹配 实现模型图 二.实战 1.引入maven <dependency> <groupId>org.springfram ...
- Google Play商店为预注册的游戏和应用提供自动安装功能
谷歌 Play 商店一直在准备一项功能,它可以自动安装用户预先注册的应用程序和游戏.似乎该功能现已开始向第一批用户推出.有些人在预注册时会看到一个新选项,使他们能够利用发布时自动安装的功能. 用户在 ...
- 【集群实战】sersync
1. sersync介绍 sersync功能: 实时同步: sersync组成: sersync==inotify+rsync inotify: 监控某个目录下面"文件/目录"是否 ...