根据网络上以及官网的.net core web api配置identity server4和swagger结合的范例,我们会发现swagger会显示除了使用ApiExplorerSettings等属性隐藏的全部api,没有说怎么配置或者怎么改造得以根据identity后的角色来显示,因为如果api有角色权限控制,不同角色应该显示对应的api列表即可,虽然访问也是会返回401,但是还是不应该所有的api都暴露出来。
1、SwaggerGenOptions.DocumentFilter去扩展Swagger来修改SwaggerDocuments
services.AddSwaggerGen(options =>
{
options.DocumentFilter();
});
2、开发CustomSwaggerFilter根据角色过滤显示
internal class CustomSwaggerFilter: IDocumentFilter
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAuthenticationSchemeProvider _schemes;
public CustomSwaggerFilter(IHttpContextAccessor httpContextAccessor, IAuthenticationSchemeProvider provider)
{
this._httpContextAccessor = httpContextAccessor;
this._schemes = provider;
}
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
Authentication(this._httpContextAccessor.HttpContext);
var user = this._httpContextAccessor.HttpContext.User;
foreach (var apiDescription in context.ApiDescriptions)
{
var endpointMetadata = apiDescription?.ActionDescriptor?.EndpointMetadata;
if (null == endpointMetadata || endpointMetadata.Count == 0)
{
continue;
}
var allowAnonymousAttributes = endpointMetadata.OfType();
if (null != allowAnonymousAttributes && allowAnonymousAttributes.Count() > 0)
{
//AllowAnonymousAttribute is to allow AllowAnonymous whether in control or method
continue;
}
var authorizeAttributes = endpointMetadata.OfType();
if (null == authorizeAttributes || authorizeAttributes.Count() == 0)
{
continue;
}
if (!IsInRole(authorizeAttributes, user))
{
var key = "/" + apiDescription.RelativePath;
swaggerDoc.Paths.Remove(key);
}
}
swaggerDoc.Paths = swaggerDoc.Paths.Count == 0 ? null : swaggerDoc.Paths;
swaggerDoc.Components.Schemas = swaggerDoc.Paths == null ? null : swaggerDoc.Components.Schemas;
}
private bool IsInRole(IEnumerable authorizeAttributes, System.Security.Claims.ClaimsPrincipal user)
{
bool isInRole = true;
foreach(var authorizeAttribute in authorizeAttributes)
{
if (string.IsNullOrWhiteSpace(authorizeAttribute.Roles))
{
continue;
}
var roles = authorizeAttribute.Roles.Split(',');
if(roles.Any(role => user.IsInRole(role)) == false)
{
isInRole = false;
break;
}
}
return isInRole;
}
///
/// Authentication:
/// mock AuthenticationMiddleware to get user claim
///
///
private void Authentication(HttpContext httpContext)
{
if(IsAuthentication(httpContext) == false)
{
return;
}
var middleware = new AuthenticationMiddleware(c => Task.CompletedTask, this._schemes);
middleware.Invoke(httpContext).ConfigureAwait(false).GetAwaiter().GetResult();
}
private bool IsAuthentication(HttpContext httpContext)
{
if (httpContext.Request.Headers.TryGetValue("Authorization", out var authorizationValue) == false)
{
return false;
}
const string BearerScheme = "Bearer";
var authHeader = AuthenticationHeaderValue.Parse(authorizationValue);
if (authHeader.Scheme.Equals(BearerScheme, StringComparison.CurrentCultureIgnoreCase) == false)
{
return false;
}
if (string.IsNullOrWhiteSpace(authHeader.Parameter))
{
return false;
}
return true;
}
}
说明:这边说下Authentication这个方法,因为在这边获取到的httpcontext的user始终是空的,所以这边需要根据token用AuthenticationMiddleware去验证下成功后就会有user
3、以上就把按角色过滤做好了,但是我们会发现,只有页面刷新才会进入,然后我们通过swagger的Authorize验证登录后也不会有刷新的动作,而且如果我们验证登录后手动刷新又是未验证的状态(ps:目前这个暂时没有解决),但是应该是在登录以及注销应该进行刷新页面显示对应角色的列表,然后未验证状态下显示没有角色控制的api列表,根据以上参考了github上issue上的也有人咨询相关的问题,目前暂时可以通过swaggger的index页面上js开发以支持登录后注销后刷新,同时在登录刷新后再次请求swagger.json这个文件前传入token以支持上一步的手动验证
// index.html
// Parse and add interceptor functions
var interceptors = JSON.parse('%(Interceptors)');
if (interceptors.RequestInterceptorFunction) {
configObject.requestInterceptor = parseFunction(interceptors.RequestInterceptorFunction);
} else {
configObject.requestInterceptor = function (e) {
var token = configObject.system.auth().getIn(["authorized", "oauth2", "token", "access_token"]);
if (token != null && e.headers.Authorization == null) {
e.headers.Authorization = "Bearer " + token;
}
return e;
}
}
if (interceptors.ResponseInterceptorFunction)
configObject.responseInterceptor = parseFunction(interceptors.ResponseInterceptorFunction);
configObject.plugins = [
function (system) {
configObject.system = system;
return {
statePlugins: {
auth: {
wrapActions: {
authorizeOauth2: (oriAction, system) => (a) => {
var r = oriAction(a);
system.specActions.download();
return r;
},
logout: (oriAction, system) => (a) => {
var r = oriAction(a);
system.specActions.download();
return r;
}
}
}
}
}
}
];
说明:这个index.html就是拷贝swagger的index.html,可到对应版本的swashbuckle.AspNetCore下载,例如:https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/v6.1.1/src/Swashbuckle.AspNetCore.SwaggerUI/index.html
然后如下对应配置下:
app.UseSwaggerUI(option =>
{
option.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("xxx.index.html");
});
以上就是我解决这个问题的步骤,如果你也有这个问题,希望看到这篇问题能给你带来帮助,同时如果有不好的地方欢迎指出,谢谢!
参考:https://stackoverflow.com/questions/38070950/generate-swashbuckle-api-documentation-based-on-roles-api-key
https://github.com/swagger-api/swagger-ui/issues/4734