无锡市网站搭建,网站建设技巧饣金手指排名27,域名租赁网站,建设银行梅州分行网站前言过年前我又来更新了~我就说了最近不是在偷懒吧#xff0c;其实这段时间还是有积累一些东西的#xff0c;不过还没去整理……所以只能发以前没写完的一些笔记出来就当做是温习一下啦PS#xff1a;之前说的红包封面我还没搞#xff0c;得抓紧时间了最近在准备搞一个我之前… 前言过年前我又来更新了~我就说了最近不是在偷懒吧其实这段时间还是有积累一些东西的不过还没去整理……所以只能发以前没写完的一些笔记出来就当做是温习一下啦PS之前说的红包封面我还没搞得抓紧时间了最近在准备搞一个我之前做的开源项目代码合集来做一期分享两种常见的认证方式先来看看两种常见的认证方式基于token的认证和传统的session认证的区别。session认证我们知道http协议本身是一种无状态的协议而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证那么下一次请求时用户还要再一次进行用户认证才行因为根据http协议我们并不能知道是哪个用户发出的请求所以为了让我们的应用能识别是哪个用户发出的请求我们只能在服务器存储一份用户登录的信息这份登录信息会在响应时传递给浏览器告诉其保存为cookie,以便下次请求时发送给我们的应用这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。但是这种基于session的认证使应用本身很难得到扩展随着不同客户端用户的增加独立的服务器已无法承载更多的用户而这时候基于session认证应用的问题就会暴露出来。弊端Session: 每个用户经过我们的应用认证之后我们的应用都要在服务端做一次记录以方便用户下次请求的鉴别通常而言session都是保存在内存中而随着认证用户的增多服务端的开销会明显增大。扩展性: 用户认证之后服务端做认证记录如果认证的记录被保存在内存中的话这意味着用户下次请求还必须要请求在这台服务器上这样才能拿到授权的资源这样在分布式的应用上相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获用户就会很容易受到跨站请求伪造的攻击。基于token的认证基于token的鉴权机制类似于http协议也是无状态的它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了这就为应用的扩展提供了便利。流程上是这样的用户使用用户名密码来请求服务器服务器进行验证用户的信息服务器通过验证发送给用户一个token客户端存储token并在每次请求时附送上这个token值服务端验证token值并返回数据这个token必须要在每次请求时传递给服务端它应该保存在请求头里 另外服务端要支持CORS(跨来源资源共享)策略一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *。OAuth2.0与OpenIDOAuth2.0和OpenID Connect是标准验证框架OAuthOpen Authorization即开放授权是一个用于代理授权的标准协议。它允许应用程序在不提供用户密码的情况下访问该用户的数据。OpenID Connect 是在 OAuth2.0 协议之上的标识层。它拓展了 OAuth2.0使得认证方式标准化。OAuth 不会立即提供用户身份而是会提供用于授权的访问令牌。OpenID Connect 使客户端能够通过认证来识别用户其中认证在授权服务端执行。它是这样实现的在向授权服务端发起用户登录和授权告知的请求时定义一个名叫openid的授权范围。在告知授权服务器需要使用 OpenID Connect 时openid是必须存在的范围。来看一看OpenID Connect的架构图可以看到JWT是作为它的底成实现支持。所以对于了解JWT来说是必要的。那么我们继续了解接下来的JWT。JWTJson web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。该token被设计为紧凑且安全的特别适用于分布式站点的单点登录SSO场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息以便于从资源服务器获取资源也可以增加一些额外的其它业务逻辑所必须的声明信息该token也可直接被用于认证也可被加密。对于我们常用的JWT是采用了JWS的签名式加密方案。所以结构就是 A.B.C的样子用Header来描述了签名加密所用的算法该描述遵循了JWA而使用Playload来包含咱们所需要的东西在JWT里面它们叫做JWT Claims Set而JWT提出了很多内置的Claim规范下面我们会看到。最后是Signature这就是基于JWS所得到的内容。JWT规范定义了七个可选的、已注册的声明Claim并允许将公共和私人声明包括在令牌中这七个已登记的声明是Claim描述iss (Issuer)确定了签发JWT的主体发行者。一般是STRING或者URI比如http://my.identityServer.com/5000sub (Subject)JWT所代表的主题。主题值必须限定为在发行者的上下文中是本地唯一的或者是全局唯一的。所以你会在某些例子中看到它保存了用户的ID等。一般是STRING或者URIaud (Audience)JWT的受众该单词我也不知道该如何翻译比较合适。一般是STRING或者URI比如http://my.clientiIp.com/5000exp (expire)JWT的过期时间nbf (not-before)JWT的生效时间iat ((issued-at)JWT的颁发时间jti (expire)JWT的唯一标识符(JWT ID)当然仅仅靠这些值我们一般是无法处理完整业务逻辑的比如我们往往需要将用户邮箱等信息放入Token中所以我们可以在荷载中放入我们自定义的一些项只要保证不要和内置的命名冲突就行啦。具体要怎么写下面有代码例子~Bearer TokenBearer是HTTP Authorization的类型规范而JWT是一个数据结构的规范。在HTTP 1.0中提出了Authorization: type credentials这样的格式。如果Basic类型的验证是Authorization : Basic那Bearer类型就是 Authorization : Bearer token。关于Bearer它是伴随OAuth2.0所提出该规范仅仅定义了Bearer Token的格式也就是需要带上Bearer关键字在header中并没有说过Token一定要使用JWT格式。再捋一遍前面介绍了这么多的概念之后可能同学们已经有点晕晕的了没事接下来重新捋一遍用户登录首先要在客户端请求服务端的登录接口把用户名和密码发给服务器然后服务器把用户名和密码拿去数据库里比对如果正确的话那就根据JWT标准生成一个JWT token返回给客户端客户端拿到了token就能以Bearer token的形式将token放在HTTP请求头中去请求那些需要登录才能访问的接口~就是这么简单~AspNetCore中的认证授权在开始写代码之前必须要了解一下AspNetCore中关于认证与授权的基础概念~认证身份认证处理程序是实现身份认证操作的核心类身份认证处理程序派生自接口IAuthenticationHandler。该接口定义了以下三种操作身份认证AuthenticateAsync、挑战ChallengeAsync和禁止ForbidAsync。其中身份认证是主要的操作。身份认证返回AuthenticateResult来表明该请求的身份认证是否成功AuthenticateResult可以返回三种类型的结果失败Fail、无结果NoResult和成功Success。如果验证成功将会通过AuthenticateResult返回AuthenticationTicket。AuthenticationTicket将会封装用户信息以便于在后续的授权中使用。挑战是指当前请求访问的资源要求身份认证但是当前请求未通过身份认证那么后续的授权阶段就需要通过指定的身份认证方案中的身份认证处理程序来提供挑战方法以便发起挑战。如果没有指定身份认证方案就会使用默认身份认证方案。举个例子来说如果我们因为长时间没有操作而导致系统登录会话超时失效那么再次对系统进行操作时系统一般会将页面重定向到登录页面这个重定向的操作就是一种挑战。禁止是指已经通过身份认证的用户尝试访问其无权访问的资源时而进入授权阶段所要执行的操作。比如某站点的普通用户想要使用VIP用户的功能如果该用户没有登录那么本次请求就是匿名访问这时授权阶段需要发起挑战操作如果该用户已经登录但是授权阶段发现该用户没有权限访问该资源那么系统可能会返回HTTP 403状态码这种返回HTTP 403状态码的操作就是一种禁止。用户信息模型身份认证通过后身份认证处理程序会返回身份认证票根即AuthenticationTicket。AuthenticationTicket是ASP.NET Core封装认证信息的类。AuthenticationTicket又包含了ClaimsPrincipalClaimsPrincipal可以理解为用户主体由一组ClaimIdentity组成。ClaimsIdentity可以理解为身份证明一个用户主体可以有多个身份证明就好比身份证、驾驶证都可以代表唯一具体的人一样。ClaimsIdentity包含了一组ClaimClaim就是好比身份证上的姓名、性别、籍贯等信息。一个用户通过身份认证后就会用以上类来组织用户信息。后续的授权等其他中间件就可以使用这些信息来进行功能设计。结构示例如下AuthenticationTicket 身份认证票根其中封装了认证信息ClaimIdentity 身份证明ClaimIdentityClaimIdentityClaimClaimClaimClaimsPrincipal 用户主体授权授权Authorization决定了一个用户在系统里能干什么。对于ASP.NET Core应用来说授权决定了一个用户能够访问哪些资源路径。授权与7.1节讲的身份认证是依赖和被依赖的关系ASP.NETCore将身份认证与授权设计成了相对独立的两个功能模块两个模块的职责分工非常明确前者解决用户是谁的问题后者解决用户能干什么的问题。从功能上来看授权是基于身份认证的结果而做出的从逻辑上来说只有知道用户是谁才能确定用户能干什么。ASP.NET Core提供了简单授权、基于角色的授权、基于策略的授权多样的授权方式在通过简单的Attribute修饰就能满足大部分应用场景。授权中重要的两个Attribute就是AuthorizeAttribute和AllowAnonymousAttribute所有的授权配置都离不开这两个Attribute。同时ASP.NET Core对授权方案的扩展也非常方便在本节的最后会介绍如何自定义授权处理程序来实现自定义授权逻辑。授权有这三种类型简单授权只要登录就能访问在Controller或者Action上加个[Authorize]就行基于角色的授权特定角色能访问基于策略的授权顾名思义基于角色的授权基于角色的授权简单来说就是一个资源必须要指定角色的用户才能够访问。基于角色的授权必须在Controller或Action上指定哪些角色可以访问该资源。AuthorizeAttribute有一个公开的string类型的属性Roles。通过这个属性可以指定哪些角色可以访问特定资源。认证用户是否属于某个角色可以通过ClaimsPrincipal类的IsInRole方法进行验证ASP.NET Core基于角色的授权就是通过这个方法来确定当前用户是否属于某个角色用户的。当前用户属于角色属性如何设置呢很简单ClaimsIdentity的属性RoleClaimType会告诉ASP.NET Core哪个Claim存储了用户的角色信息。比如某个Controller需要管理员角色才能访问[Authorize(Roles管理员)]可以指定多个角色都可以访问多个角色间用逗号分隔[Authorize(Roles人力经理,财务)]如果用多个[Authorize(RolesSomeRole)]修饰Controller或Action那么访问的用户必须是所有指定角色的成员下面的例子必须同时是“销售”和“经理”才能访问[Authorize(Roles销售)]
[Authorize(Roles经理)]基于策略的授权基于策略的授权是更灵活的授权方式我们先来了解ASP.NET Core的授权模型。与身份认证相似ASP.NET Core由授权处理程序、授权需求、授权方案、授权服务构成。其中授权服务同身份认证服务一样作为授权服务接口对外提供授权能力。授权方案是组织授权机制的概念一个授权方案可以包含多个授权需求只有满足了所有授权需求才算通过了授权方案而授权需求可以关联多个授权处理程序任意一个授权处理程序返回授权成功则表示该授权方案下的授权需求被满足了。ASP.NET Core提供了一个授权策略实现了建造者模式通过AuthorizationPolicyBuilder可以方便地构建AuthorizationPolicy。基于AuthorizationPolicyBuilder可以方便地设置授权策略的授权需求。services.AddAuthorization(config {config.AddPolicy(RequireAdmin, builder builder.RequireRole(管理员));
});除了AuthorizeAttribute上可以设置的角色外还可以设置Claim需求。该授权策略需要当前认证用户姓赵services.AddAuthorization(config {config.AddPolicy(RequireZhao, builder builder.RequireClaim(姓, 赵));
});如果被授权的姓氏规则比较复杂不利于枚举出来那么推荐使用RequireAssertion来实现。比如上面的功能还可以用如下方式来实现services.AddAuthorization(config {config.AddPolicy(RequireZhao, builder builder.RequireAssertion(context context.User.FindFirst(姓).Value赵));
});除此之外还可以通过实现了IAuthorizationRequirement的授权需求来关联自定义的授权处理程序来实现更灵活的授权规则设计。IdentityServer4IdentityServer4是ASP.NET Core平台下的一个OAuth 2.0以及OpenID Connect的实现。它非常方便地提供了身份认证、授权以及第三方认证服务对接并且支持自定义方式来满足开发者不同场景下各式各样的需求。IdentityServer4作为一个成熟的认证授权框架是受到OpenID Connect官方认证的服务端实现。IdentityServer开源且免费在重视知识产权的今天我们可以放心地基于IdentityServer4搭建认证平台开发商业应用。IdentityServer通过IdentityResource、ApiScope、ApiResource、Client这些概念来实现身份的认证和资源的权限控制。IdentityResource是指用户ID、姓名、手机号等用户信息比如OpenID Connect规范就定义了一组标准的IdentityResource。除此之外我们也可以自定义IdentityResource这些概念很像ASP.NETCore中身份认证的Claim定义了程序能访问到的用户信息。ApiScope可以认为是API的一种标签而ApiResource就是对API在授权场景下的抽象。如果需要对客户端能否访问某个API进行控制就要定义ApiScope和ApiResource。Client通过Request Token限制了哪些应用可以访问对应的API资源。每个Client都会有一个唯一的Client ID通过设置一个秘钥可以加强用户信息安全性关键的是通过设置AllowedApiScopes框架就可以控制这个Client可以访问哪些ApiResourceResource是和Scope相关联的。开始编码OK终于到了激动人心的写代码环节书读百遍不如实践一次开始吧首先根据JWT标准我们需要先定义这几个信息Issuer签发JWT的主体AudienceJWT的受众Key用来加密的秘钥本例子中我们写一个最简单的单站点登录认证所以Audience可以写死在配置文件里。定义配置类为了方便的映射appsettings.json配置文件我们定义一个类~~误是两个~~public class SecuritySettings {public Token Token { get; set; }
}
public class Token {public string Issuer { get; set; }public string Audience { get; set; }public string Key { get; set; }
}然后注册服务services.ConfigureSecuritySettings(configuration.GetSection(nameof(SecuritySettings)));添加认证服务和中间件添加认证服务services.AddAuthentication(options {options.DefaultAuthenticateScheme JwtBearerDefaults.AuthenticationScheme;options.DefaultChallengeScheme JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options {// 这里用到我们之前定义好的配置类var secSettings configuration.GetSection(nameof(SecuritySettings)).GetSecuritySettings();// 设置jwt token的各种信息用于验证options.TokenValidationParameters new TokenValidationParameters {ValidateAudience true,ValidateLifetime true,ValidateIssuer true,ValidateIssuerSigningKey true,ValidIssuer secSettings.Token.Issuer,ValidAudience secSettings.Token.Audience,IssuerSigningKey new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secSettings.Token.Key)),ClockSkew TimeSpan.Zero};});添加中间件在app.UseEndpoints前面添加这三行代码app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();用户实体类很简单不多说了public class LoginUser {public string Username { get; set; }public string Password { get; set; }
}登录接口在Controller里写一个用户登录接口[HttpPost]
public ActionResultLoginToken Login(LoginUser loginUser) {var user _authService.GetUser(loginUser.Username);if (user null) return NotFound();var md5Pwd loginUser.Password.MDString();if (md5Pwd ! user.Password) return Unauthorized();return _authService.GenerateLoginToken(user);
}这里面我封装了一个AuthService服务专门用于处理跟用户认证有关的操作其中的GetUser方法不用多介绍了就是数据库读取操作而已。我们主要看GenerateLoginToken这个方法。生成token的关键代码GenerateLoginToken方法的代码如下public LoginToken GenerateLoginToken(User user) {// 构造JWT中的claims信息var claims new ListClaim {new(username, user.Name),new(JwtRegisteredClaimNames.Name, user.Id), // User.Identity.Namenew(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), // JWT ID};// 从配置文件里读取信息var key new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secSettings.Token.Key));var signCredential new SigningCredentials(key, SecurityAlgorithms.HmacSha256);var jwtToken new JwtSecurityToken(issuer: _secSettings.Token.Issuer, // 颁发者信息audience: _secSettings.Token.Audience, // 接受者信息claims: claims, // 要放进JWT中的claims信息expires: DateTime.Now.AddDays(7), // 过期时间signingCredentials: signCredential); // 签名// 最后返回一个 LoginToken 对象其中包含JWT token和过期时间两个字段return new LoginToken {Token new JwtSecurityTokenHandler().WriteToken(jwtToken),Expiration TimeZoneInfo.ConvertTimeFromUtc(jwtToken.ValidTo, TimeZoneInfo.Local)};
}这个代码的意义我都写在注释里面了最后的LoginToken是我定义的一个类代码很简单public class LoginToken {public string? Token { get; set; }public DateTime Expiration { get; set; }
}效果完成之后访问登录接口提交正确的用户名密码就可以得到客户端要的JWT token大概是下面这样的形式{token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ5ZXpzIiwibmFtZSI6InllenMiLCJwaG9uZV9udW1iZXIiOiIxNTYwMjc3NzMwMCIsImV4cCI6MTY0MzMxMzc3OSwiaXNzIjoiZGVtb19pc3N1ZXIiLCJhdWQiOiJkZW1vX2F1ZGllbmNlIn0.7x8zfpcWWbCH6SwXOUnQKCfXRWsyUiWoB5jSxYSIq-Q,expiration: 2022-01-28T04:02:5908:00
}在需要登录的接口方法或者Controller类上加一个[Authorize]特性就OK了访问的时候如果不带上HTTP头Authorization : Bearer token就会报401 Unauthorized错误。大功告成SignalR中如何使用JWT Token接下来是一点扩展的东西AspNetCore除了可以做WebApi这种基于HTTP的接口还可以实现像websocket这样的实时通信比如SignalR技术那通过SignalR的请求能不能也加上身份验证呢答案是肯定的和controller一样只需要在Hub类或者Hub类里面的方法加上[Authorize]特性即可实现身份验证。但是客户端访问的时候要怎么提交token呢这可不是HTTP没有header的别急来看看以下两种方法都是要在前面添加服务那里配置。首先确定要添加配置的地方services.AddAuthentication(...).AddJwtBearer(options {options.TokenValidationParameters new TokenValidationParameters {...};options.Events new JwtBearerEvents {// 等会要添加的配置代码放在这里...};});官方文档的方法OnMessageReceived context {var accessToken context.Request.Query[access_token];var path context.HttpContext.Request.Path;// If the request is for our hubif (!string.IsNullOrEmpty(accessToken) path.StartsWithSegments(/hub)) {// Read the token out of the query stringcontext.Token accessToken;}return Task.CompletedTask;
}简书网友的方法OnMessageReceived context {var accessToken context.Request.Query[access_token];if (!string.IsNullOrEmpty(accessToken) (context.HttpContext.WebSockets.IsWebSocketRequest || context.Request.Headers[Accept] text/event-stream)){context.Token context.Request.Query[access_token];}return Task.CompletedTask;
}点评一下官方文档的方法有点硬编码是根据请求的路径判断的但如果我们的项目里不止一个hub那就麻烦了要多写点代码简书网友的方法是根据请求的方式来判断我们知道SignalR和普通的HTTP请求是不一样的所以感觉简书网友的这个方法更优雅一点~客户端使用差点把这个忘了放上JavaScript代码~let loginToken xxx
let connection new signalR.HubConnectionBuilder().withUrl(/hub/hub_name, {accessTokenFactory: () loginToken}).build()在建立连接的时候带上accessTokenFactory参数即可~后记呼~终于搞定了没想到这篇博客写了这么长这么久授权与认证包括好多要学的东西我目前也只是做了最基础的登录验证还没有搞身份那些所以这篇作为基础入门接下来的博客会继续深入这方面冲参考资料推荐客官来看看AspNetCore的身份验证吧https://www.cnblogs.com/uoyo/p/13209685.htmlOAuth 2.0 与 OpenID Connect 协议的完整指南https://www.infoq.cn/article/euvhttyf3jmfakmm8cmn身份验证和授权 ASP.NET CoreSignalRhttps://docs.microsoft.com/zh-cn/aspnet/core/signalr/authn-and-authz?viewaspnetcore-6.0NET CORE SignalR JWT授权认证https://www.jianshu.com/p/19a0efdc01d1什么是 JWT -- JSON WEB TOKENhttps://www.jianshu.com/p/576dbf44b2aeJSON Web Token 入门教程https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html如何在 ASP.NET Core 3 使用 Token-based 身分驗證與授權 (JWT)https://blog.miniasp.com/post/2019/12/16/How-to-use-JWT-token-based-auth-in-aspnet-core-31往期推荐记一次CTF比赛过程与解题思路-MISC部分2021-11-05 使用Flutter设计一个好看的我页面2021-06-18 PyQt5开发实践一、准备篇2021-05-05 比Django官方实现更好的分页组件Bootstrap整合2021-03-29 准备要新年快乐啦