10分钟简单学习net core集成jwt权限认证,快速接入项目落地使用
什么是JWT
JSON Web Token(JWT)是目前最流行的跨域身份验证、分布式登录、单点登录等解决方案。
JWT的官网地址:https://jwt.io/
通俗地来讲,JWT是能代表用户身份的令牌,可以使用JWT令牌在api接口中校验用户的身份以确认用户是否有访问api的权限。
JWT中包含了身份认证必须的参数以及用户自定义的参数,JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
JSON Web令牌能做什么?
- 授权:这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由,服务和资源。Single Sign On是一种现在广泛使用JWT的功能,因为它的开销很小,并且能够在不同的域中轻松使用。
- 信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。因为JWT可以签名 - 例如,使用公钥/私钥对 - 您可以确定发件人是他们所说的人。此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。
JSON Web令牌如何工作?
在身份验证中,当用户使用其凭据成功登录时,将返回JSON Web令牌。由于令牌是凭证,因此必须非常小心以防止出现安全问题。一般情况下,您不应该将令牌保留的时间超过要求。
每当用户想要访问受保护的路由或资源时,用户代理应该使用承载模式发送JWT,通常在Authorization标头中,标题的内容应如下所示:
Authorization: Bearer <token>
在某些情况下,这可以是无状态授权机制。服务器的受保护路由将检查Authorization
标头中的有效JWT ,如果存在,则允许用户访问受保护资源。如果JWT包含必要的数据,则可以减少查询数据库以进行某些操作的需要,尽管可能并非总是如此。
如果在标Authorization
头中发送令牌,则跨域资源共享(CORS)将不会成为问题,因为它不使用cookie。
下图显示了如何获取JWT并用于访问API或资源:
1、应用程序向授权服务器请求授权;
2、校验用户身份,校验成功,返回token;
3、应用程序使用访问令牌访问受保护的资源。
JWT的实现方式是将用户信息存储在客户端,服务端不进行保存。每次请求都把令牌带上以校验用户登录状态,这样服务就变成了无状态的,服务器集群也很好扩展。
更多理论知识可以查看官网,或者查看相关网友的文章,如下推荐文章:
- asp.net core 集成JWT(一):https://www.cnblogs.com/7tiny/archive/2019/06/13/11012035.html
- 五分钟带你了解啥是JWT:https://zhuanlan.zhihu.com/p/86937325
- C#分布式登录——jwt:https://www.cnblogs.com/yswenli/p/13510050.html
net core 集成jwt代码实现
新建项目
首先我们新建一个ASP.NET Core Web API项目,命名为 jwtWebAPI,选择目标框架.NET Core3.1,注意,如果勾选了https配置,postman请求的时候要设置去除ssl认证才能使用,建议不配置https。
在nuget里面引用jwt集成的程序包,这里需要注意的是,如果你用的是.NET Core 3.1的框架的话,程序包版本选择3.1.10
Microsoft.AspNetCore.Authentication.JwtBearer
添加数据访问模拟api,新建控制器ValuesController
其中api/value1是可以直接访问的,api/value2添加了权限校验特性标签 [Authorize]
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks; namespace jwtWebAPI.Controllers
{
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
[Route("api/values1")]
public ActionResult<IEnumerable<string>> values1()
{
return new string[] { "value1", "value1" };
} /**
* 该接口用Authorize特性做了权限校验,如果没有通过权限校验,则http返回状态码为401
* 调用该接口的正确姿势是:
* 1.登陆,调用api/Auth接口获取到token
* 2.调用该接口 api/value2 在请求的Header中添加参数 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOiIxNTYwMzM1MzM3IiwiZXhwIjoxNTYwMzM3MTM3LCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiemhhbmdzYW4iLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAifQ.1S-40SrA4po2l4lB_QdzON_G5ZNT4P_6U25xhTcl7hI
* Bearer后面有空格,且后面是第一步中接口返回的token值
* */
[HttpGet]
[Route("api/value2")]
[Authorize]
public ActionResult<IEnumerable<string>> value2()
{
//这是获取自定义参数的方法
var auth = HttpContext.AuthenticateAsync().Result.Principal.Claims;
var userName = auth.FirstOrDefault(t => t.Type.Equals(ClaimTypes.NameIdentifier))?.Value;
return new string[] { "访问成功:这个接口登陆过的用户都可以访问", $"userName={userName}" };
} }
}
添加模拟登陆生成Token的api,新建控制器AuthController
这里模拟一下登陆校验,只验证了用户密码不为空即通过校验,真实环境完善校验用户和密码的逻辑。
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks; namespace jwtWebAPI.Controllers
{
[ApiController]
public class AuthController : Controller
{
/// <summary>
/// 通过账号+密码获取Token
/// </summary>
/// <param name="userName"></param>
/// <param name="pwd"></param>
/// <returns>Token</returns>
[AllowAnonymous]
[HttpGet]
[Route("api/auth")]
public IActionResult GetToken(string userName, string pwd)
{
if (!string.IsNullOrEmpty(userName))
{
//每次登陆动态刷新
Const.ValidAudience = userName + pwd + DateTime.Now.ToString();
// push the user’s name into a claim, so we can identify the user later on.
//这里可以随意加入自定义的参数,key可以自己随便起
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddMinutes(3)).ToUnixTimeSeconds()}"),
new Claim(ClaimTypes.NameIdentifier, userName)
};
//sign the token using a secret key.This secret will be shared between your API and anything that needs to check that the token is legit.
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
//.NET Core’s JwtSecurityToken class takes on the heavy lifting and actually creates the token.
var token = new JwtSecurityToken(
//颁发者
issuer: Const.Domain,
//接收者
audience: Const.ValidAudience,
//过期时间(可自行设定,注意和上面的claims内部Exp参数保持一致)
expires: DateTime.Now.AddMinutes(3),
//签名证书
signingCredentials: creds,
//自定义参数
claims: claims
); return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
else
{
return BadRequest(new { message = "username or password is incorrect." });
}
}
}
}
Startup添加JWT验证的相关配置
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace jwtWebAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//添加jwt验证:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = true,//是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(30), //时间偏移量(允许误差时间)
ValidateAudience = true,//是否验证Audience(验证之前的token是否失效)
//ValidAudience = Const.GetValidudience(),//Audience
//这里采用动态验证的方式,在重新登陆时,刷新token,旧token就强制失效了
AudienceValidator = (m, n, z) =>
{
return m != null && m.FirstOrDefault().Equals(Const.ValidAudience);
},
ValidateIssuer = true,//是否验证Issuer(颁发者)
ValidAudience = Const.Domain,//Audience 【Const是新建的一个常量类】 接收者
ValidIssuer = Const.Domain,//Issuer,这两项和前面签发jwt的设置一致 颁发者
ValidateIssuerSigningKey = true,//是否验证SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//拿到秘钥SecurityKey
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
//Token expired
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
}); services.AddControllers();
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//添加jwt验证
app.UseAuthentication(); if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
创建常量类Const
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace jwtWebAPI
{
public class Const
{
/// <summary>
/// 这里为了演示,写死一个密钥。实际生产环境可以从配置文件读取,这个是用网上工具随便生成的一个密钥(md5或者其他都可以)
/// </summary>
public const string SecurityKey = "48754F4C58F9EA428FE09D714E468211"; /// <summary>
/// 站点地址(颁发者、接受者),这里测试和当前本地运行网站相同,实际发到正式环境应为域名地址
/// </summary>
public const string Domain = "https://localhost:44345"; /// <summary>
/// 受理人,之所以弄成可变的是为了用接口动态更改这个值以模拟强制Token失效
/// 真实业务场景可以在数据库或者redis存一个和用户id相关的值,生成token和验证token的时候获取到持久化的值去校验
/// 如果重新登陆,则刷新这个值
/// </summary>
public static string ValidAudience;
}
}
JWT登录授权测试成功
把程序编译运行起来,打开postman,输入地址,首先测试不需要任何授权的
正确地返回了数据,那么接下来我们测试JWT的流程。
首先我们什么都不加调用接口:https://localhost:44345/api/values2,注意,我创建的时候是https的,大家注意看是http还是https
返回了状态码401,也就是未经授权:访问由于凭据无效被拒绝。 说明JWT校验生效了,我们的接口收到了保护。
调用模拟登陆授权接口:https://localhost:44345/api/auth?userName=xiongze&pwd=123456
这里的用户密码是随便写的,因为我们模拟登陆只是校验了下非空,因此写什么都能通过。
然后我们得到了一个xxx.yyy.zzz 格式的 token 值。我们把token复制出来。
在刚才401的接口(https://localhost:44345/api/values2)请求header中添加JWT的参数,把我们的token加上去
再次调用我们的模拟数据接口,但是这次我们加了一个header,KEY:Authorization Value:Bearer Tokne的值
这里需要注意 Bearer 后面是有一个空格的,然后就是我们上一步获取到的token,
得到返回值,正确授权成功,我们是支持自定义返回参数的,上面代码里面有相关内容,比如用户名这些不敏感的信息可以带着返回。
等token设置的过期时间到了,或者重新生成了新的Token,没有及时更新,那么我们的授权也到期,401,
升级操作:接口权限隔离
上面的操作是所有登录授权成功的角色都可以进行调用所有接口,那么我们现在想要进行接口隔离限制,
也就是说,虽然授权登录了,但是我这个接口是指定权限访问的。
比如说:删除接口只能管理员角色操作,那么其他角色虽然授权登录了,但是没有权限调用删除接口。
我们在原来的操作进行改造升级看一下。
添加类
新建一个AuthManagement文件夹,添加PolicyRequirement类和PolicyHandler类,
PolicyRequirement类:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace jwtWebAPI.AuthManagement
{
/// <summary>
/// 权限承载实体
/// </summary>
public class PolicyRequirement : IAuthorizationRequirement
{
/// <summary>
/// 用户权限集合
/// </summary>
public List<UserPermission> UserPermissions { get; private set; }
/// <summary>
/// 无权限action
/// </summary>
public string DeniedAction { get; set; }
/// <summary>
/// 构造
/// </summary>
public PolicyRequirement()
{
//没有权限则跳转到这个路由
DeniedAction = new PathString("/api/nopermission");
//用户有权限访问的路由配置,当然可以从数据库获取
UserPermissions = new List<UserPermission> {
new UserPermission { Url="/api/values3", UserName="admin"},
};
}
} /// <summary>
/// 用户权限承载实体
/// </summary>
public class UserPermission
{
/// <summary>
/// 用户名
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 请求Url
/// </summary>
public string Url { get; set; }
}
}
PolicyHandler类(注意2.x和3.x的区别)
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks; namespace jwtWebAPI.AuthManagement
{
public class PolicyHandler : AuthorizationHandler<PolicyRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public PolicyHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement)
{
//赋值用户权限
var userPermissions = requirement.UserPermissions;
var httpContext = _httpContextAccessor.HttpContext; //请求Url
var questUrl = httpContext.Request.Path.Value.ToUpperInvariant();
//是否经过验证
var isAuthenticated = httpContext.User.Identity.IsAuthenticated;
if (isAuthenticated)
{
if (userPermissions.GroupBy(g => g.Url).Any(w => w.Key.ToUpperInvariant() == questUrl))
{
//用户名
var userName = httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.NameIdentifier).Value;
if (userPermissions.Any(w => w.UserName == userName && w.Url.ToUpperInvariant() == questUrl))
{
context.Succeed(requirement);
}
else
{
////无权限跳转到拒绝页面
//httpContext.Response.Redirect(requirement.DeniedAction);
return Task.CompletedTask;
}
}
else
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
}
添加指定角色
在 AuthController 控制器的GetToken授权加入自定义的参数,如下
new Claim("Role", userName) //这里是角色,我使用登录账号admin代替
在 AuthController 控制器里面添加无权限访问的方法
[AllowAnonymous]
[HttpGet]
[Route("api/nopermission")]
public IActionResult NoPermission()
{
return Forbid("No Permission!");
}
修改Startup配置
在startup.cs的ConfigureServices 方法里面添加策略鉴权模式、添加JWT Scheme、注入授权Handler
修改后的文件如下
using jwtWebAPI.AuthManagement;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace jwtWebAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services
//添加策略鉴权模式
.AddAuthorization(options =>
{
options.AddPolicy("Permission", policy => policy.Requirements.Add(new PolicyRequirement()));
})
//添加JWT Scheme
.AddAuthentication(s =>
{
s.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
s.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
s.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
//添加jwt验证:
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = true,//是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(30), //时间偏移量(允许误差时间)
ValidateAudience = true,//是否验证Audience(验证之前的token是否失效)
//ValidAudience = Const.GetValidudience(),//Audience
//这里采用动态验证的方式,在重新登陆时,刷新token,旧token就强制失效了
AudienceValidator = (m, n, z) =>
{
return m != null && m.FirstOrDefault().Equals(Const.ValidAudience);
},
ValidateIssuer = true,//是否验证Issuer(颁发者)
ValidAudience = Const.Domain,//Audience 【Const是新建的一个常量类】 接收者
ValidIssuer = Const.Domain,//Issuer,这两项和前面签发jwt的设置一致 颁发者
ValidateIssuerSigningKey = true,//是否验证SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//拿到秘钥SecurityKey
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
//Token expired
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
}); //注入授权Handler
services.AddSingleton<IAuthorizationHandler, PolicyHandler>();
//注入获取HttpContext
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddControllers();
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//添加jwt验证
app.UseAuthentication(); if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
}); }
}
}
添加api访问的方法
在 ValuesController控制器添加指定权限访问的方法,如下:
/**
* 这个接口必须用admin
**/
[HttpGet]
[Route("api/values3")]
[Authorize("Permission")]
public ActionResult<IEnumerable<string>> values3()
{
//这是获取自定义参数的方法
var auth = HttpContext.AuthenticateAsync().Result.Principal.Claims;
var userName = auth.FirstOrDefault(t => t.Type.Equals(ClaimTypes.NameIdentifier))?.Value;
var role = auth.FirstOrDefault(t => t.Type.Equals("Role"))?.Value; return new string[] { "访问成功:这个接口有管理员权限才可以访问", $"userName={userName}", $"Role={role}" };
}
不同权限测试访问
我们同样的方法去模拟登录,https://localhost:44345/api/auth?userName=xiongze&pwd=123
注意,账号先不用admin登录,然后用返回的token去请求我们刚刚添加的指定权限访问的接口,这个时候是没有权限访问的,因为这个是admin权限访问。
我们同样的方法去模拟登录,https://localhost:44345/api/auth?userName=admin&pwd=123
访问成功。
源码下载地址
Gitee:https://gitee.com/xiongze/jwtWebAPI.git
参考文献
- asp.net core 集成JWT(一):https://www.cnblogs.com/7tiny/archive/2019/06/13/11012035.html
- 五分钟带你了解啥是JWT:https://zhuanlan.zhihu.com/p/86937325
- C#分布式登录——jwt:https://www.cnblogs.com/yswenli/p/13510050.html
欢迎关注订阅微信公众号【熊泽有话说】,更多好玩易学知识等你来取
作者:熊泽-学习中的苦与乐 公众号:熊泽有话说 出处: https://www.cnblogs.com/xiongze520/p/15540035.html 您可以随意转载、摘录,但请在文章内注明作者和原文链接。 |
10分钟简单学习net core集成jwt权限认证,快速接入项目落地使用的更多相关文章
- IdentityServer4实现.Net Core API接口权限认证(快速入门)
什么是IdentityServer4 官方解释:IdentityServer4是基于ASP.NET Core实现的认证和授权框架,是对OpenID Connect和OAuth 2.0协议的实现. 通俗 ...
- asp.net core 集成JWT(二)token的强制失效,基于策略模式细化api权限
[前言] 上一篇我们介绍了什么是JWT,以及如何在asp.net core api项目中集成JWT权限认证.传送门:https://www.cnblogs.com/7tiny/p/11012035.h ...
- asp.net core 集成JWT(一)
[什么是JWT] JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案. JWT的官网地址:https://jwt.io/ 通俗地来讲,JWT是能代表用户身份的令牌,可以使用JWT ...
- ASP.NET Core 基于JWT的认证(二)
ASP.NET Core 基于JWT的认证(二) 上一节我们对 Jwt 的一些基础知识进行了一个简单的介绍,这一节我们将详细的讲解,本次我们将详细的介绍一下 Jwt在 .Net Core 上的实际运用 ...
- ASP.NET Core 基于JWT的认证(一)
ASP.NET Core 基于JWT的认证(一) Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计 ...
- 【技术博客】JWT的认证机制Django项目中应用
开发组在开发过程中,都不可避免地遇到了一些困难或问题,但都最终想出办法克服了.我们认为这样的经验是有必要记录下来的,因此就有了[技术博客]. JWT的认证机制Django项目中应用 这篇技术博客基于软 ...
- ASP.NET Core集成现有系统认证
我们现在大多数转向ASP.NET Core来使用开发的团队,应该都不是从0开始搭建系统,而是老的业务系统已经在运行,ASP.NET Core用来开发新模块.那么解决用户认证的问题,成为我们的第一个拦路 ...
- ASP.NET Core 中jwt授权认证的流程原理
目录 1,快速实现授权验证 1.1 添加 JWT 服务配置 1.2 颁发 Token 1.3 添加 API访问 2,探究授权认证中间件 2.1 实现 Token 解析 2.2 实现校验认证 1,快速实 ...
- 【转】ASP.NET Core WebAPI JWT Bearer 认证失败返回自定义数据 Json
应用场景:当前我们给微信小程序提供服务接口,接口中使用了权限认证这一块,当我使用 JWT Bearer 进行接口权限认证的时候,返回的结果不是我们客户端想要的,其它我们想要给客户端返回统一的数据结构, ...
随机推荐
- AVS 端能力之音频播放模块
功能简介 音频播放 音频流播放 URL文件播放 播放控制 播放 暂停 继续 停止 其它功能(AVS服务器端实现) 支持播放列表 支持上一首下一首切换 支持电台 事件指令集 AudioPlayer 端能 ...
- 理解classpath
一.什么是classpath classpath,翻译过来就是类路径的意思,它是包含class文件的路径集合,用于指示虚拟机jvm在这些路径下搜索class文件. 类路径可以同时定义多个,多个类路径之 ...
- P5287-[HNOI2019]JOJO【KMP】
正题 题目链接:https://www.luogu.com.cn/problem/P5287 题目大意 开始一个空串,\(n\)个操作 在末尾加入\(x\)个\(c\)字符(保证和\(c\)和前面的字 ...
- Python+selenium自动化生成测试报告
批量执行完用例后,生成的测试报告是文本形式的,不够直观,为了更好的展示测试报告,最好是生成HTML格式的. unittest里面是不能生成html格式报告的,需要导入一个第三方的模块:HTMLTest ...
- Redis之品鉴之旅(四)
发布订阅,简单场景下的发布订阅完全可以使用. 可以简单的理解,将一个公众号视为发布者,关注公众号的人视作订阅者,公众号发布一条文章或者消息,凡事订阅公众号的都可以收到消息.一个人可以订阅多个公众号,一 ...
- shiro的使用与JWT整合
一.shiro入门 两大框架对比:安全框架Shiro和SpringSecurity的比较 了解shiro 什么是Shiro Apache Shiro是一个Java的安全(权限)框架.| Shiro可以 ...
- 02Prism WPF 入门实战 - 建项
1.概要 Prism介绍 Github: https://github.com/PrismLibrary/Prism 开发文档:https://prismlibrary.com/docs/ Prism ...
- 初始HTML04
HTML 列表标签 无序列表 默认用实心圆点标识列表项 1 <ul> 2 <li>list item 列表项</li> 3 <li>list item ...
- 【c++ Prime 学习笔记】第5章 语句
C++提供了一组控制流语句,包括条件执行语句.循环语句.跳转语句. 5.1 简单语句 空语句 ; ,最简单的语句 别漏写分号,也别多写 while(cin>>s && s! ...
- 【二食堂】Alpha - Scrum Meeting 8
Scrum Meeting 8 例会时间:4.18 11:40 - 12:10 进度情况 组员 昨日进度 今日任务 李健 1. 实体的添加和关系的添加实现的有bug,柴博和刘阳进行了帮助issue 1 ...