加入收藏 | 设为首页 | 会员中心 | 我要投稿 北几岛 (https://www.beijidao.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

Dotnet core使用JWT认证授权最佳实践(一)

发布时间:2021-05-21 07:54:48 所属栏目:大数据 来源: https://www.jb51.cc
导读:@H_301_1@ 最近,团队的小伙伴们在做项目时,需要用到JWT认证。遂根据自己的经验,整理成了这篇文章,用来帮助理清JWT认证的原理和代码编写操作。 一、JWT JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对

微信公众号:老王Plus

扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

本文版权归作者所有,转载请保留此声明和原文链接

(编辑:北几岛)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

@H_301_1@

最近,团队的小伙伴们在做项目时,需要用到JWT认证。遂根据自己的经验,整理成了这篇文章,用来帮助理清JWT认证的原理和代码编写操作。

一、JWT

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

?

JWT是什么,看上面这段网上抄来的话。

关于JWT以及优缺点,网上有很多详细的说法,我这儿就不重复了。

我们只需要知道以下的事实:

在一般的系统中,我们有时候会做个用户登录。用户登录完成进到系统后,需要根据用户的权限,来控制一些功能可用,而另一些功能不可用。

在SOA/AOP架构中,做为最重要的API端,其实也需要有类似登录或认证的内容,用来区分哪些用户可以使用某个API,哪些用户不行。

同时,我们希望这个登录或类似登录的过程,只发生在一个固定位置。这样,在我们写代码时,建立好这样一个过程后,在我们后边写代码时,简单引用即可,而不需要每个API程序都开发一次认证。这个需求,其实就是OAuth的由来。

最重要的是,这样的代码写出来,显得高大上

?

下面进入正题。

认证这个操作,就像我们最近的日子。

首先,我们要有一个出入证,或者绿码。这个证,我们称作令牌(Token)。我们去领这个证,这个操作称为发行(Issue)。

我们拿着这个证,去到一个地方。有专人会检查这个证,这称为用户身份验证(Authentication)。验证通过放行,称为授权(Authorization),验证不通过,叫作未授权错误(Unauthorized)。

如果这个证过期了,你就需要去重新办一个证。这个过程叫做刷新(RefreshToken)。

简言之,这就是认证的全部流程。

?

下面,我用一个Demo项目,来逐步完成这个过程。

二、开发环境&基础项目

这个Demo的开发环境是:Mac + VS Code + Dotnet Core 3.1.2。

$?dotnet?--info
.NET?Core?SDK?(reflecting?any?global.json):
?Version:???3.1.201
?Commit:????b1768b4ae7

Runtime?Environment:
?OS?Name:?????Mac?OS?X
?OS?Version:??10.15
?OS?Platform:?Darwin
?RID:?????????osx.10.15-x64
?Base?Path:???/usr/local/share/dotnet/sdk/3.1.201/

Host?(useful?for?support):
??Version:?3.1.3
??Commit:??4a9f85e9f8

.NET?Core?SDKs?installed:
??3.1.201?[/usr/local/share/dotnet/sdk]

.NET?Core?runtimes?installed:
??Microsoft.AspNetCore.App?3.1.3?[/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
??Microsoft.NETCore.App?3.1.3?[/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

?

首先,在这个环境下建立工程:

  1. 创建Solution
%?dotnet?new?sln?-o?demo
The?template?@H_301_102@"Solution?File"?was?created?successfully.
  1. 用Webapi模板创建项目
%?cd?demo
%?dotnet?new?webapi?-o?demo
The?template?"ASP.NET?Core?Web?API"?was?created?successfully.

Processing?post-creation?actions...
Running?'dotnet?restore'?on?demo/demo.csproj...
??Restore?completed?in?179.13?ms?for?demo/demo.csproj.

Restore?succeeded.
  1. 把Demo项目加到Solution中
%?dotnet?sln?add?demo/demo.csproj
Project?`demo/demo.csproj`?added?to?the?solution.
  1. 安装Swagger(这步非必须,我习惯用Swagger,不习惯用Postman)
%?dotnet?add?package?Swashbuckle.AspNetCore
log??:?Restore?completed?in?2.75?sec?for?demo/demo.csproj.
  1. 安装JWT认证支持库(必须引入)
%?dotnet?add?package?Microsoft.AspNetCore.Authentication.JwtBearer
in?3.09?sec?for?demo/demo.csproj.

?

五步做完,基础项目就建完了。

看一下整个的目录结构:

%?tree?.
.
├──?demo
│???├──?Controllers
│???│???└──?WeatherForecastController.cs
│???├──?Program.cs
│???├──?Properties
│???│???└──?launchSettings.json
│???├──?Startup.cs
│???├──?WeatherForecast.cs
│???├──?appsettings.Development.json
│???├──?appsettings.json
│???├──?demo.csproj
│???└──?obj
│???????├──?demo.csproj.nuget.dgspec.json
│???????├──?demo.csproj.nuget.g.props
│???????├──?demo.csproj.nuget.g.targets
│???????├──?project.assets.json
│???????└──?project.nuget.cache
└──?demo.sln

?

  1. 在Startup.cs中补充代码,以启用Swagger

在ConfigureServices方法中加入以下代码:

services.AddSwaggerGen(c?=>
{
????????c.SwaggerDoc("v1",?new?OpenApiInfo?{?Title?=?"Demo",?Version?=?"V1"?});

????????c.AddSecurityDefinition("Bearer",1); word-wrap: inherit !important; word-break: inherit !important">new?OpenApiSecurityScheme
????????{
????????????????Name?=?"Authorization",
????????????????Type?=?SecuritySchemeType.ApiKey,
????????????????Scheme?=?"JWT",
????????????????In?=?ParameterLocation.Header,
????????????????Description?=?"",
????????});

????????c.AddSecurityRequirement(new?OpenApiSecurityRequirement
????????{
????????????????{
????????????????????????new?OpenApiSecurityScheme
????????????????????????{
????????????????????????????????Reference?=?new?OpenApiReference
????????????????????????????????{
????????????????????????????????????????Type?=?ReferenceType.SecurityScheme,
????????????????????????????????????????Id?=?"Bearer"
????????????????????????????????}
????????????????????????},
????????????????????????new?string[]?{}
????????????????}
????????});
});

在Configure方法中加入以下代码

app.UseSwagger();
app.UseSwaggerUI(c?=>
{
????????c.SwaggerEndpoint("/swagger/v1/swagger.json",?"Demo?V1");
});

关于Swagger的详细配置,这里不做说明,留着以后写。

三、签发Token

签发Token是认证的第一步。

用户进到系统,在验证用户帐号密码后,需要根据用户的数据,把Token返回给用户。

这个过程其实跟认证没什么关系,只是一个普通的API功能。

  1. 工程下加一个目录DTOModels,创建一个LoginRequestDTO的类,用于定义API的输入参数。
using?System;

namespace?demo.DTOModels
{
????public?class?@H_502_336@LoginRequestDTO
????{
????????public?string?username?{?get;?set;?}
????????string?password?{?get;?set;?}
????}
}
  1. 创建一个控制器AuthenticationController,并在控制器里创建一个API方法RequestToken。
using?Microsoft.AspNetCore.Mvc;
using?demo.DTOModels;

namespace?demo.Controllers
{
????AuthenticationController?:?ControllerBase
????{
????????[HttpPost,?Route("requesttoken")]
????????public?ActionResult?RequestToken([FromBody]?LoginRequestDTO?request)
????????
{
??????????????//这儿待完善
????????????return?Ok();
????????}
????}
}
  1. 生成JWT Token需要预设一些参数。我们在appsetting.json里先设置好。
{
??"Logging":?{
????"LogLevel":?{
??????"Default":?"Information",
??????"Microsoft":?"Warning",1); word-wrap: inherit !important; word-break: inherit !important">"Microsoft.Hosting.Lifetime":?"Information"
????}
??},
??"AllowedHosts":?"*",1); word-wrap: inherit !important; word-break: inherit !important">"tokenParameter":?{
????"secret":?"123456123456123456",
????"issuer":?"WangPlus",1); word-wrap: inherit !important; word-break: inherit !important">"accessExpiration":?120,1); word-wrap: inherit !important; word-break: inherit !important">"refreshExpiration":?1440
??}
}

这里,tokenParameter节是我们设置的参数。一般来说,是这几个:

secret: JWT加密的密钥。现在主流用SHA256加密,需要256位以上的密钥,unicode是16个字符以上,尽量复杂一些。密钥泄露,Token就会被破解,所以,你懂的。

issuer: 签发人的名称,如果没人注意,你可以把大名写在上面。

accessExpiration: Token的有效分钟数。过了这个时间,这个Token会过期。

refreshExpiration: refreshToken的有效分钟数。过了这个时间,用户需要重新登录。

?

Token过期后,可以让用户重新登录认证拿Token。但这个方式会比较Low。高大上的方式是签发Token的时候,同时也签发一个refreshToken给用户。用户Token过期后,可以拿refreshToken去申请新的Token,同时刷新refreshToken。如果用户长时间未使用系统,refreshToken也过期了,才让用户重新登录认证。

refreshToken可以用JWT生成,也可以自己生成,不影响认证。

  1. 建一个Models目录,创建一个映射tokenParameter的类。这个类不是必须,只是为了写着方便。不想这样写,也可以直接读配置,再转成数据。
namespace?demo.Models
{
????tokenParameter
????{
????????string?Secret?{?get;?string?Issuer?{?get;?public?int?AccessExpiration?{?get;?int?RefreshExpiration?{?get;?set;?}
????}
}
  1. 在前边建好的API - RequestToken中,完成Token和refreshToken的生成和返回。
using?demo.DTOModels;
using?Microsoft.Extensions.Configuration;
using?System;
using?System.Text;
using?demo.Models;
using?Microsoft.IdentityModel.Tokens;
using?System.Security.Claims;
using?System.IdentityModel.Tokens.Jwt;

AuthenticationController?:?ControllerBase
????{
????????private?tokenParameter?_tokenParameter?=?new?tokenParameter();
????????public?AuthenticationController()
????????{
????????????var?config?=?new?ConfigurationBuilder()
????????????????.SetBasePath(AppContext.BaseDirectory)
????????????????.AddJsonFile("appsettings.json")
????????????????.Build();

????????????_tokenParameter?=?config.GetSection("tokenParameter").Get<tokenParameter>();
????????}

????????[HttpPost,1); word-wrap: inherit !important; word-break: inherit !important">"requestToken")]
????????([FromBody]?LoginRequestDTO?request)
????????{
????????????//这儿在做用户的帐号密码校验。我这儿略过了。
????????????if?(request.username?==?null?&&?request.password?==?null)
????????????????return?BadRequest("Invalid?Request");

??????????????//生成Token和RefreshToken
????????????var?token?=?GenUserToken(request.username,1); word-wrap: inherit !important; word-break: inherit !important">"testUser");
????????????var?refreshToken?=?"123456";

????????????return?Ok(new[]?{?token,?refreshToken?});
????????}


??????????//这儿是真正的生成Token代码
??????????private?string?GenUserToken(string?username,?string?role)
????????{
????????????var?claims?=?new[]
????????????{
????????????????new?Claim(ClaimTypes.Name,?username),
????????????????new?Claim(ClaimTypes.Role,?role),
????????????};

????????????var?key?=?new?SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenParameter.Secret));
????????????var?credentials?=?new?SigningCredentials(key,?SecurityAlgorithms.HmacSha256);
????????????var?jwtToken?=?new?JwtSecurityToken(_tokenParameter.Issuer,?null,?claims,?expires:?DateTime.UtcNow.AddMinutes(_tokenParameter.AccessExpiration),?signingCredentials:?credentials);

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

????????????return?token;
????????}
????}
}

这个类里,验证帐号密码的代码我略过了。还有,refreshToken给了一个固定串。真实项目这儿就按需要做就好。

(未完待续)

?


?

    推荐文章
      热点阅读