实战项目-双端验证
双端验证
在此项目中,User和Admin两端是分别发送了不同格式的Token以区分登陆状态
Admin: Token: <token>
User: Authorization:<token>
所以我们在配置JWT验证规则时,应该分别为其配置验证规则
// 配置Admin的验证规则
.AddJwtBearer("Admin", options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.ASCII.GetBytes(builder.Configuration["Jwt:Key"] ??
throw new InvalidOperationException())),
ValidateIssuer = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = builder.Configuration["Jwt:Audience"]
};
// 使用自定义处理请求头的方式
options.EventsType = typeof(AdminJwtBearerEvents);
})
// 配置User的验证规则
.AddJwtBearer("User", options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.ASCII.GetBytes(builder.Configuration["Jwt:Key"] ??
throw new InvalidOperationException())),
ValidateIssuer = true,
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = builder.Configuration["Jwt:Audience"]
};
// 使用自定义处理请求头的方式
options.EventsType = typeof(UserJwtBearerEvents);
});
JwtBearerEvents
分别添加Admin身份认证方案和User身份认证方案
// 配置Admin身份认证方案处理请求头中Token的方式
public class AdminJwtBearerEvents : JwtBearerEvents
{
public override Task MessageReceived(MessageReceivedContext context)
{
var token = context.Request.Headers["Token"].FirstOrDefault();
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}
return base.MessageReceived(context);
}
}
// 配置User身份认证方案处理请求头中Token的方式
public class UserJwtBearerEvents : JwtBearerEvents
{
public override Task MessageReceived(MessageReceivedContext context)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault();
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}
return base.MessageReceived(context);
}
}
AuthenticationSchemes
我们在Controller中,可以使用不同的身份认证方案来对不同的Action方法进行验证
/// <summary>
/// 员工登出
/// </summary>
/// <returns></returns>
[Authorize(AuthenticationSchemes = "Admin")]
[HttpPost("logout")]
public Result<string> Logout()
{
return Result<string>.Success();
}
我们也可以将这个方法直接添加到控制器类上,再将登陆方法暴露出来
[ApiController]
[Route("/admin/employee")]
[Authorize(AuthenticationSchemes = "Admin")]
public class EmployeeController : ControllerBase
{
/// <summary>
/// 员工登录Action
/// </summary>
/// <param name="employeeLoginReq"></param>
/// <returns></returns>
[HttpPost("login")]
[AllowAnonymous]
public Result<EmployeeLoginResp> Login([FromBody] EmployeeLoginReq employeeLoginReq)
{
}
Swagger按钮
#region MyRegion Swagger
builder.Services.AddSwaggerGen(
options =>
{
// 以下配置影响两个输入Token的按钮
// 添加一个安全定义,定义名称为"Admin"
options.AddSecurityDefinition("Admin", new OpenApiSecurityScheme
{
// 描述如何使用Token进行身份验证
Description = "Token: <token>",
// 指定认证头的名称,这里是"Token"
Name = "Token",
// 指定认证信息应该放置的位置,这里是HTTP头部
In = ParameterLocation.Header,
// 指定安全方案的类型,这里是指API密钥类型
Type = SecuritySchemeType.ApiKey,
});
// 添加安全要求,这将告诉Swagger UI哪些安全方案是必需的
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
// 引用之前定义的安全方案
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Admin" // 对应于上面定义的安全方案的Id
},
},
new List<string>() // 空列表表示不需要特定的作用域
}
});
// 添加一个安全定义,定义名称为"User"
options.AddSecurityDefinition("User", new OpenApiSecurityScheme
{
// 描述如何进行身份验证
Description = "Authorization: <token>",
// 指定认证头的名称,这里是"Authorization"
Name = "Authorization",
// 指定认证信息应该放置的位置,这里是HTTP头部
In = ParameterLocation.Header,
// 指定安全方案的类型,这里是指API密钥类型
Type = SecuritySchemeType.ApiKey,
});
// 添加安全要求,这将告诉Swagger UI哪些安全方案是必需的
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
// 引用之前定义的安全方案
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "User" // 对应于上面定义的安全方案的Id
},
},
new List<string>() // 空列表表示不需要特定的作用域
}
});
});
#endregion
Swagger文档
我们应该为Admin和User配置不同的接口文档,以方便测试使用
builder.Services.AddSwaggerGen(
options =>
{
options.SwaggerDoc("admin", new OpenApiInfo { Title = "Admin API", Version = "v1" });
options.SwaggerDoc("user", new OpenApiInfo { Title = "User API", Version = "v1" });
//文档逻辑包含
options.DocInclusionPredicate((docName, apiDesc) =>
{
// 检查 apiDesc 的 ActionDescriptor 是否是 ControllerActionDescriptor 类型
if (apiDesc.ActionDescriptor is ControllerActionDescriptor actionDescriptor)
{
// 获取控制器的命名空间
var controllerNamespace = actionDescriptor.ControllerTypeInfo.Namespace;
// 确保命名空间不为空,并且包含 "Controllers" 字符串
if (controllerNamespace != null && controllerNamespace.Contains("Controllers"))
{
// 如果文档名称是 "user"
if (docName == "user")
{
// 只有当控制器命名空间包含 "User" 时才包含该 API 描述
return controllerNamespace.Contains("User");
}
// 如果文档名称是 "admin"
if (docName == "admin")
{
// 只有当控制器命名空间包含 "Admin" 时才包含该 API 描述
return controllerNamespace.Contains("Admin");
}
}
}
// 如果上述条件都不满足,则返回 false,表示不包含该 API 描述
return false;
});
});
)
配置中间件
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1-user/swagger.json", "User API V1");
options.SwaggerEndpoint("/swagger/v1-admin/swagger.json", "Admin API V1");
}
);