一,准备内容

在之前讲过的asp.net core 实现OAuth2.0四种模式系列中的IdentityApi客户端用到了以下配置代码

  public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication("Bearer").AddJwtBearer(r => {
//认证地址
r.Authority = "http://localhost:5000";
//权限标识
r.Audience = "secretapi";
//是否必需HTTPS
r.RequireHttpsMetadata = false;
});
}
   app.UseAuthentication();

 AddJwtBearer到底起到什么作用呢。首先熟习两个概念

1,中间件(Middleware)

中间件是组装到Asp.net core应用程序管道中以处理请求和响应的软件。可以这样理解:一根管道从水源(用户)连接到家庭(资源)。水源的水是不能直接饮用的,需要重重过滤,这些过滤手段就是中间件,在处理过程中决定是否往下继续传送,可能丢弃,也可能转到其它地方。请参考我之前写的《Asp.net core之中间件》

2,身份认证执行方案(AuthenticationSchemes)

在一个启用身份认证的Asp.net core应用中可以有几个执行方案,分工不同,功能也不同。可以指定由那个方案进行身份认证,如以下代码

      [HttpGet]
[Route("api/identity")]
[Microsoft.AspNetCore.Authorization.Authorize(Roles ="admin",AuthenticationSchemes ="Bearer")]
public object GetUserClaims()

 指定了方案名为“Bearer”的方案来做这个Api接口的认证。这个"Bearer"是怎么来的呢,看一下services.AddAuthentication方法有几个重载,我们上面用的重载是传递一个字符串指定默认方案为“Bearer”,那么程序是如果根据"Bearer"这个方案名找到对应的执行方案的呢?

二,AddJwtBearer添加Jwt证书验证执行方案

AddJwtBearer是Microsoft.AspNetCore.Authentication.JwtBearer对AuthenticationBuilder的一个扩写方法,看一下源码

  public static class JwtBearerExtensions
{
public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder)
=> builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, _ => { }); public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, Action<JwtBearerOptions> configureOptions)
=> builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, configureOptions); public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, Action<JwtBearerOptions> configureOptions)
=> builder.AddJwtBearer(authenticationScheme, displayName: null, configureOptions: configureOptions); public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<JwtBearerOptions> configureOptions)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>());
return builder.AddScheme<JwtBearerOptions, JwtBearerHandler>(authenticationScheme, displayName, configureOptions);
}
}

 有四个方法重载,但最后运行的是最后一个重载,最后一个重载用了builder.AddScheme方法添加方案,所以,AddJwtBearer本质上就是添加验证方案。前二个方法重载没有传“authenticationScheme"参数,使用的是JwtBearerDefaults.AuthenticationScheme这个值,我们上边用的代码是第二个重载,传了configOptions,没传authenticationScheme,JwtBearerDefaults.AuthenticationScheme这个值预设为Bearer(见以下源码),所以根据Bearer这个方案名找到的方案就是我们运行AddJwtBearer所添加的方案。

 public static class JwtBearerDefaults
{
/// <summary>
/// Default value for AuthenticationScheme property in the JwtBearerAuthenticationOptions
/// </summary>
public const string AuthenticationScheme = "Bearer";
}

三,JwtBearer执行方案具体做了什么工作

上面说过AddJwtBearer本质上就是添加一个执行方案。先看下添加执行方案的关键源码

把方案的HandlerType指定为方法的第二个泛型,方便从根据方案实例化Hndler,并将这个泛型添加进了服务依赖。从AddJwtBearer源码可看到出这个泛型为:JwtBearerHandler

  public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<JwtBearerOptions> configureOptions)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>());
return builder.AddScheme<JwtBearerOptions, JwtBearerHandler>(authenticationScheme, displayName, configureOptions);
}

分析JwtBearerHandler源码,JwtBearerHandler主要是能干三件事

 1,HandleAuthenticateAsync:获取HTTP请求头里的Authorization头。先验证是不是Bearer格式,再用JwtSecurityTokenHandler这个工具类验证Jwt数据,包括长度,格式,是否过期,签发地址等。

触发事件:1),MessageReceived:接收到请时触发。

         2),TokenValidated:验证Jwt数据成功时触发。

        3),AuthenticationFailed:验证Jwt数据失败时触发。

附源码

  protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string token = null;
try
{
// Give application opportunity to find from a different location, adjust, or reject token
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options); // event can set the token
await Events.MessageReceived(messageReceivedContext);
if (messageReceivedContext.Result != null)
{
return messageReceivedContext.Result;
} // If application retrieved token from somewhere else, use that.
token = messageReceivedContext.Token; if (string.IsNullOrEmpty(token))
{
string authorization = Request.Headers[HeaderNames.Authorization]; // If no authorization header found, nothing to process further
if (string.IsNullOrEmpty(authorization))
{
return AuthenticateResult.NoResult();
} if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
} // If no token found, no further work possible
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
} if (_configuration == null && Options.ConfigurationManager != null)
{
_configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted);
} var validationParameters = Options.TokenValidationParameters.Clone();
if (_configuration != null)
{
var issuers = new[] { _configuration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers; validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(_configuration.SigningKeys)
?? _configuration.SigningKeys;
} List<Exception> validationFailures = null;
SecurityToken validatedToken;
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception ex)
{
Logger.TokenValidationFailed(ex); // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
&& ex is SecurityTokenSignatureKeyNotFoundException)
{
Options.ConfigurationManager.RequestRefresh();
} if (validationFailures == null)
{
validationFailures = new List<Exception>(1);
}
validationFailures.Add(ex);
continue;
} Logger.TokenValidationSucceeded(); var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
{
Principal = principal,
SecurityToken = validatedToken
}; await Events.TokenValidated(tokenValidatedContext);
if (tokenValidatedContext.Result != null)
{
return tokenValidatedContext.Result;
} if (Options.SaveToken)
{
tokenValidatedContext.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = token }
});
} tokenValidatedContext.Success();
return tokenValidatedContext.Result;
}
} if (validationFailures != null)
{
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures)
}; await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
} return AuthenticateResult.Fail(authenticationFailedContext.Exception);
} return AuthenticateResult.Fail("No SecurityTokenValidator available for token: " + token ?? "[null]");
}
catch (Exception ex)
{
Logger.ErrorProcessingMessage(ex); var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = ex
}; await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
} throw;
}
}

  

   2,HandleChallengeAsync:验证失败时挑战验证结果,有点像网球比赛的挑战鹰眼功能。但Jwt的挑战验证极其简单,就是重新调用了一次HandleAuthenticateAsync,然后就是挑战失败后设置请求上下文的状态码为:401,也就是我们在前端访问的Response状态码,再往Http回应的Http Header上加上一个名为WWWAuthenticate的头。触发Challenge事件表示挑战失败。

附源码

 protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
var authResult = await HandleAuthenticateOnceSafeAsync();
var eventContext = new JwtBearerChallengeContext(Context, Scheme, Options, properties)
{
AuthenticateFailure = authResult?.Failure
}; // Avoid returning error=invalid_token if the error is not caused by an authentication failure (e.g missing token).
if (Options.IncludeErrorDetails && eventContext.AuthenticateFailure != null)
{
eventContext.Error = "invalid_token";
eventContext.ErrorDescription = CreateErrorDescription(eventContext.AuthenticateFailure);
} await Events.Challenge(eventContext);
if (eventContext.Handled)
{
return;
} Response.StatusCode = 401; if (string.IsNullOrEmpty(eventContext.Error) &&
string.IsNullOrEmpty(eventContext.ErrorDescription) &&
string.IsNullOrEmpty(eventContext.ErrorUri))
{
Response.Headers.Append(HeaderNames.WWWAuthenticate, Options.Challenge);
}
else
{
// https://tools.ietf.org/html/rfc6750#section-3.1
// WWW-Authenticate: Bearer realm="example", error="invalid_token", error_description="The access token expired"
var builder = new StringBuilder(Options.Challenge);
if (Options.Challenge.IndexOf(' ') > 0)
{
// Only add a comma after the first param, if any
builder.Append(',');
}
if (!string.IsNullOrEmpty(eventContext.Error))
{
builder.Append(" error=\"");
builder.Append(eventContext.Error);
builder.Append("\"");
}
if (!string.IsNullOrEmpty(eventContext.ErrorDescription))
{
if (!string.IsNullOrEmpty(eventContext.Error))
{
builder.Append(",");
} builder.Append(" error_description=\"");
builder.Append(eventContext.ErrorDescription);
builder.Append('\"');
}
if (!string.IsNullOrEmpty(eventContext.ErrorUri))
{
if (!string.IsNullOrEmpty(eventContext.Error) ||
!string.IsNullOrEmpty(eventContext.ErrorDescription))
{
builder.Append(",");
} builder.Append(" error_uri=\"");
builder.Append(eventContext.ErrorUri);
builder.Append('\"');
} Response.Headers.Append(HeaderNames.WWWAuthenticate, builder.ToString());
}
}

  3,HandleForbiddenAsync,验证Jwt数据成功,但授权失败时会调用这个方法,设置Response状态码为403,直接返回不再继续往下。触发Forbidden事件。

附源码

 protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
{
var forbiddenContext = new ForbiddenContext(Context, Scheme, Options);
Response.StatusCode = 403;
return Events.Forbidden(forbiddenContext);
}

  

三,JwtBearer执行方案工作流程

上边说了JwtBearerHandler的三个功能,这一小节来讲讲这三个功能在什么时候开始工作的。

上面我们使用AddAuthentication,AddJwtBearer只是把这个身份验证这个功能加入到服务,好比你买了台冰箱放在家里,还没有上电使用,占了个地方而已,怎么使用呢,这里就要用到中间件,中间件就像一个即插即用的插头。启用身份验证的中间件用UseAuthentication方法。看一下这个方法的源码,看它又做了什么事。

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Authentication
{
public class AuthenticationMiddleware
{
private readonly RequestDelegate _next; public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (schemes == null)
{
throw new ArgumentNullException(nameof(schemes));
} _next = next;
Schemes = schemes;
} public IAuthenticationSchemeProvider Schemes { get; set; } public async Task Invoke(HttpContext context)
{
context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
{
OriginalPath = context.Request.Path,
OriginalPathBase = context.Request.PathBase
}); // Give any IAuthenticationRequestHandler schemes a chance to handle the request
var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
if (handler != null && await handler.HandleRequestAsync())
{
return;
}
} var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
if (result?.Principal != null)
{
context.User = result.Principal;
}
} await _next(context);
}
}
}

 重点是Invoke方法,看来就做了二件事

1,从当前方案集合里(可添加多个方案,目前我们只用了一个Bearer)筛选出IAuthenticationRequestHandler的实现类,执行他的HandleRequestAsync方法。

2,找到默认执行方案,执行他的AuthenticateAsync方法。

第1件事,当前我添加的Bearer方案所用的JwtBearerHandler并没有继承自IAuthenticationRequestHandler,所以这一步在当前验证方案就没起作用,我们在以后讲AddOpenIdConnect时会讲到这一步,使用OpenidConnect做身份验证时,OpenidConnect所用的OpenIdConnectHandler是RemoteAuthenticationHandler的实现,而RemoteAuthenticationHandler继承了IAuthenticationRequestHandler

 public class JwtBearerHandler : AuthenticationHandler<JwtBearerOptions>
  public abstract class RemoteAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions>, IAuthenticationRequestHandler

第2件事,执行AuthenticateAsync方法,在JwtBearerHandler中没有这个方法,但他的父类 AuthenticationHandler<JwtBearerOptions>中是有的。在父类中执行AuthenticateAsync时如果没有设置ForwardAuthenticate(验证方案跳转),会执行HandleAuthenticateOnceAsync方法,这个方法要注意:他是一个类似于单例的调用方式,在生命周期内只会触发一次子类的HandleAuthenticateAsync方法。也就是JwtBearerHandler的HandleAuthenticateAsync方法。理解这个对后续的工作流很重要。

附源码

public async Task<AuthenticateResult> AuthenticateAsync()
{
var target = ResolveTarget(Options.ForwardAuthenticate);
if (target != null)
{
return await Context.AuthenticateAsync(target);
} // Calling Authenticate more than once should always return the original value.
var result = await HandleAuthenticateOnceAsync();
if (result?.Failure == null)
{
var ticket = result?.Ticket;
if (ticket?.Principal != null)
{
Logger.AuthenticationSchemeAuthenticated(Scheme.Name);
}
else
{
Logger.AuthenticationSchemeNotAuthenticated(Scheme.Name);
}
}
else
{
Logger.AuthenticationSchemeNotAuthenticatedWithFailure(Scheme.Name, result.Failure.Message);
}
return result;
} /// <summary>
/// Used to ensure HandleAuthenticateAsync is only invoked once. The subsequent calls
/// will return the same authenticate result.
/// </summary>
protected Task<AuthenticateResult> HandleAuthenticateOnceAsync()
{
if (_authenticateTask == null)
{
_authenticateTask = HandleAuthenticateAsync();
} return _authenticateTask;
}

好了,JwtBearerHandler的三个功能,我们已经搞清一个了,他的验证功能在请求伊始就会能过身份验证中间件触发。那另二个呢,另外二个功能的触发点需要用到另一个中间件,身份授权中间件(UseAuthorization)。这个中间件不用手动Use,AddMvc和UseMvc已经把这部份工作做了。这个中间件干了什么,看下他的中间件实现源码

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization.Policy;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Authorization
{
public class AuthorizationMiddleware
{
// Property key is used by other systems, e.g. MVC, to check if authorization middleware has run
private const string AuthorizationMiddlewareInvokedKey = "__AuthorizationMiddlewareInvoked";
private static readonly object AuthorizationMiddlewareInvokedValue = new object(); private readonly RequestDelegate _next;
private readonly IAuthorizationPolicyProvider _policyProvider; public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider)
{
_next = next ?? throw new ArgumentNullException(nameof(next));
_policyProvider = policyProvider ?? throw new ArgumentNullException(nameof(policyProvider));
} public async Task Invoke(HttpContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
} var endpoint = context.GetEndpoint(); // Flag to indicate to other systems, e.g. MVC, that authorization middleware was run for this request
context.Items[AuthorizationMiddlewareInvokedKey] = AuthorizationMiddlewareInvokedValue; // IMPORTANT: Changes to authorization logic should be mirrored in MVC's AuthorizeFilter
var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);
if (policy == null)
{
await _next(context);
return;
} // Policy evaluator has transient lifetime so it fetched from request services instead of injecting in constructor
var policyEvaluator = context.RequestServices.GetRequiredService<IPolicyEvaluator>(); var authenticateResult = await policyEvaluator.AuthenticateAsync(policy, context); // Allow Anonymous skips all authorization
if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
{
await _next(context);
return;
} // Note that the resource will be null if there is no matched endpoint
var authorizeResult = await policyEvaluator.AuthorizeAsync(policy, authenticateResult, context, resource: endpoint); if (authorizeResult.Challenged)
{
if (policy.AuthenticationSchemes.Any())
{
foreach (var scheme in policy.AuthenticationSchemes)
{
await context.ChallengeAsync(scheme);
}
}
else
{
await context.ChallengeAsync();
} return;
}
else if (authorizeResult.Forbidden)
{
if (policy.AuthenticationSchemes.Any())
{
foreach (var scheme in policy.AuthenticationSchemes)
{
await context.ForbidAsync(scheme);
}
}
else
{
await context.ForbidAsync();
} return;
} await _next(context);
}
}
}

  1,先进行策略验证,是不是该请求不需要授权,是的话就往下传递请求,不再执行后边的代码

  2,该请求需要授权访问,请调用policyEvaluator.AuthorizeAsync进行身份及授权验证

附源码

 public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)
{
if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
} var result = await _authorization.AuthorizeAsync(context.User, resource, policy);
if (result.Succeeded)
{
return PolicyAuthorizationResult.Success();
} // If authentication was successful, return forbidden, otherwise challenge
return (authenticationResult.Succeeded)
? PolicyAuthorizationResult.Forbid()
: PolicyAuthorizationResult.Challenge();
}

如果身份和授权都验证成功,则成功,如果身份验证能过,授权没通过则禁止访问,直接回应,如果身份验证没通过就去挑战验证结果,挑战成功继续来一次来,挑战失败就直接回应了。源码中的PolicyAuthorizationResult.Forbid() 和PolicyAuthorizationResult.Challenge()具体执行的是什么方法呢?看以下源码

   public virtual Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync()
=> _options.DefaultChallengeScheme != null
? GetSchemeAsync(_options.DefaultChallengeScheme)
: GetDefaultSchemeAsync();
public virtual Task<AuthenticationScheme> GetDefaultForbidSchemeAsync()
=> _options.DefaultForbidScheme != null
? GetSchemeAsync(_options.DefaultForbidScheme)
: GetDefaultChallengeSchemeAsync();

 然来如果没有指定特定的方案,就返回默认的方案。指定特定的Challenge方案和Forbid方案我们讲OpenIdConnect时再详细说。目前我们所用的只有一个默认方案:Bearer,所以会执行JwtBearerHandler的Challenge和Forbid方法。

如此一来,JwtBearerHandler的三种功能触发时机,作用都已经搞清楚了,我画了个图方便大家理理解

 

 

IdentityServer4之Jwt身份验证方案分析的更多相关文章

  1. asp.net core 3.1多种身份验证方案,cookie和jwt混合认证授权

    开发了一个公司内部系统,使用asp.net core 3.1.在开发用户认证授权使用的是简单的cookie认证方式,然后开发好了要写几个接口给其它系统调用数据.并且只是几个简单的接口不准备再重新部署一 ...

  2. JWT 身份认证优缺点分析以及常见问题解决方案

    本文转载自:JWT 身份认证优缺点分析以及常见问题解决方案 Token 认证的优势 相比于 Session 认证的方式来说,使用 token 进行身份认证主要有下面三个优势: 1.无状态 token ...

  3. Asp.Net Core 5 REST API 使用 JWT 身份验证 - Step by Step

    翻译自 Mohamad Lawand 2021年1月22日的文章 <Asp Net Core 5 Rest API Authentication with JWT Step by Step> ...

  4. HTTP 请求未经客户端身份验证方案“Anonymous”授权。从服务器收到的身份验证标头为“Negotiate,NTLM”

    转自:http://www.cnblogs.com/geqinggao/p/3270499.html 近来项目需要Web Service验证授权,一般有两种解决方案: 1.通过通过SOAP Heade ...

  5. 发布Restful服务时出现IIS 指定了身份验证方案错误时的解决方案(IIS specified authentication schemes)

    发布RESTful服务,当访问.svc文件时出现如下错误时: IIS 指定了身份验证方案“IntegratedWindowsAuthentication, Anonymous”,但绑定仅支持一种身份验 ...

  6. HTTP 请求未经客户端身份验证方案“Anonymous”授权。

    今天调取WebService的时候报: HTTP 请求未经客户端身份验证方案“Anonymous”授权. 解决办法: 配置文件里改: <basicHttpBinding> <bind ...

  7. Spring Cloud系列-Zuul网关集成JWT身份验证

    前言 这两三年项目中一直在使用比较流行的spring cloud框架,也算有一定积累,打算有时间就整理一些干货与大家分享. 本次分享zuul网关集成jwt身份验证 业务背景 项目开发少不了身份认证,j ...

  8. ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程

    ASP.NET Core Web API中带有刷新令牌的JWT身份验证流程 翻译自:地址 在今年年初,我整理了有关将JWT身份验证与ASP.NET Core Web API和Angular一起使用的详 ...

  9. .netcore实现jwt身份验证

    前言 http协议本身是一种无状态的协议.所以客户端的每次请求,服务端是不清楚其身份的,需要客户端每次都要将身份信息传入,服务进行验证,才能达到安全验证的目的. 传统的Web用户验证:1.客户端传入用 ...

随机推荐

  1. Alpha冲刺(4/6)

    队名:無駄無駄 组长博客 作业博客 组员情况 张越洋 过去两天完成了哪些任务 摸鱼 提交记录(全组共用) 接下来的计划 沟通前后端成员,监督.提醒他们尽快完成各自的进度 学习如何评估代码质量 准备Al ...

  2. P3396 哈希冲突(思维+方块)

    题目 P3396 哈希冲突 做法 预处理模数\([1,\sqrt{n}]\)的内存池,\(O(n\sqrt{n})\) 查询模数在范围里则直接输出,否则模拟\(O(m\sqrt{n})\) 修改则遍历 ...

  3. YoTube 视频如何下载

    因我学习自动化测试 ,国内的C# selenium 搭建的环境的资料甚少,然后去国外网站找资料, 曹鼠给我的gogle安装一个下载YoTube视频插件,特此非常感谢他. 前提条件需要一个服务器:Sha ...

  4. Don't always upset yourself !

  5. 第10组 Beta冲刺(2/5)

    链接部分 队名:女生都队 组长博客: 博客链接 作业博客:博客链接 小组内容 恩泽(组长) 过去两天完成了哪些任务 描述 新增修改用户信息.任务完成反馈等功能API 服务器后端部署,API接口的bet ...

  6. jmeter常用四种断言

    jmeter常用四种断言 一.Response Assertion(响应断言)二.Size Assertion(数据包字节大小断言)三.Duration Assertion(持续时间断言)四.bean ...

  7. 利用C++ STL的vector模拟邻接表的代码

    关于vector的介绍请看 https://www.cnblogs.com/zsq1993/p/5929806.html https://zh.cppreference.com/w/cpp/conta ...

  8. Python实例100个(基于最新Python3.7版本)

    Python3 100例 原题地址:   http://www.runoob.com/python/python-100-examples.html    git地址:    https://gith ...

  9. ARM USB 通信(转)

    ARM USB 通信 采用ZLG的动态链接库,动态装载. ARM是Context-M3-1343. 在C++ Builder 6 中开发的上位机通信软件. USB通信代码如下: //--------- ...

  10. Excel 如何查找 问号 “?” 、星号“*” 、 “~”号

    若需要查找问号“?”,则在查找内容文本框中输入“~?”.“?”为通配符,代替单个任意字符,如果直接查找,会找到包含数据的所有单元格. 若需要查找星号“*”,则在查找内容文本框中输入“~*”.“*”为通 ...