.net core使用Ocelot+Identity Server统一网关验证

源码下载地址:下载

项目结构如下图:

在Identity Server授权中,实现IResourceOwnerPasswordValidator接口:

public class IdentityValidator : IResourceOwnerPasswordValidator
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly IHttpContextAccessor _httpContextAccessor;
        public IdentityValidator(
            UserManager<ApplicationUser> userManager,
            IHttpContextAccessor httpContextAccessor)
        {
            _userManager = userManager;
            _httpContextAccessor = httpContextAccessor;
        }

        public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
        {
            string userName = context.UserName;
            string password = context.Password;            

            var user = await _userManager.FindByNameAsync(userName);
            if (user == null)
            {
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidClient, "用户不存在!");                
                return;
            }

            var checkResult = await _userManager.CheckPasswordAsync(user, password);
            if (checkResult)
            {
                context.Result = new GrantValidationResult(
                         subject: user.Id,
                         authenticationMethod: "custom",
                         claims: _httpContextAccessor.HttpContext.User.Claims);
            }
            else
            {
                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "无效的客户身份!");
            }
        }
    }

单页面应用中,使用implicit的授权模式,需添加oidc-client.js,调用API的关键代码:

var config = {
    authority: "http://localhost:5000/",
    client_id: "JsClient",
    redirect_uri: "http://localhost:5500/callback.html",
    response_type: "id_token token",
    scope:"openid profile UserApi",
    post_logout_redirect_uri: "http://localhost:5500/index.html",
};
var mgr = new Oidc.UserManager(config);

mgr.getUser().then(function (user) {
    if (user) {
        log("User logged in", user.profile);
    }
    else {
        log("User not logged in");
    }
});

function login() {
    mgr.signinRedirect();
}

//api调用之前需登录
function api() {
    mgr.getUser().then(function (user) {
        if (user == null || user == undefined) {
            login();
        }
        var url = "http://localhost:9000/api/Values";

        var xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.onload = function () {
            log(xhr.status, JSON.parse(xhr.responseText));
            alert(xhr.responseText);
        }
        xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
        xhr.setRequestHeader("sub", user.profile.sub);//这里拿到的是用户ID,传给API端进行角色权限验证
        xhr.send();
    });
}

function logout() {
    mgr.signoutRedirect();
}

统一网关通过Ocelot实现,添加Ocelot.json文件,并修改Program.cs文件:

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .ConfigureAppConfiguration((hostingContext, builder) => {
                    builder
                    .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
                    .AddJsonFile("Ocelot.json");
                })
                .UseUrls("http://+:9000")
                .UseStartup<Startup>()
                .Build();

StartUp.cs文件修改如下:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOcelot();

            var authenticationProviderKey = "qka_api";
            services.AddAuthentication("Bearer")
                .AddIdentityServerAuthentication(authenticationProviderKey, options =>
                {
                    options.Authority = "http://localhost:5000";
                    options.RequireHttpsMetadata = false;
                    options.ApiName = "UserApi";
                });

            services.AddCors(options =>
            {
                options.AddPolicy("default", policy =>
                {
                    policy.AllowAnyOrigin()
                            .AllowAnyMethod()
                            .AllowAnyHeader()
                            .AllowCredentials();
                });
            });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseCors("default");
            app.UseOcelot().Wait();
        }

 

Ocelot.js配置文件如下:

{
  "ReRoutes": [
    {
      "DownstreamPathTemplate": "/{url}",
      "DownstreamScheme": "http",
      "ServiceName": "userapi", //consul中的userapi的service名称
      "LoadBalancer": "RoundRobin", //负载均衡算法
      "UseServiceDiscovery": true, //启用服务发现
      "UpstreamPathTemplate": "/{url}",
      "UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "qka_api",
        "AllowedScopes": []
      }
    },
    {
      "DownstreamPathTemplate": "/{url}",
      "DownstreamScheme": "http",
      "ServiceName": "identityserverapi", //consul中的userapi的service名称
      "LoadBalancer": "RoundRobin", //负载均衡算法
      "UseServiceDiscovery": true, //启用服务发现
      "UpstreamPathTemplate": "/{url}",
      "UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ],
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:9000",
    "ServiceDiscoveryProvider": {
      "Host": "192.168.2.144",//consul的地址
      "Port": 8500//consul的端口
    }
  }
}

asp.net core自带的基于角色授权需要像下图那样写死角色的名称,当角色权限发生变化时,需要修改并重新发布站点,很不方便。

所以我自定义了一个filter,实现角色授权验证:

public class UserPermissionFilterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            if (context.Filters.Any(item => item is IAllowAnonymousFilter))
            {
                return;
            }
            if (!(context.ActionDescriptor is ControllerActionDescriptor))
            {
                return;
            }

            var attributeList = new List<object>();
            attributeList.AddRange((context.ActionDescriptor as ControllerActionDescriptor).MethodInfo.GetCustomAttributes(true));
            attributeList.AddRange((context.ActionDescriptor as ControllerActionDescriptor).MethodInfo.DeclaringType.GetCustomAttributes(true));

            var authorizeAttributes = attributeList.OfType<UserPermissionFilterAttribute>().ToList();
            if (!authorizeAttributes.Any())
            {
                return;
            }

            var sub = context.HttpContext.Request.Headers["sub"];
            string path = context.HttpContext.Request.Path.Value.ToLower();
            string httpMethod = context.HttpContext.Request.Method.ToLower();

            /*todo:
            从数据库中根据role获取权限是否具有访问当前path和method的权限,
            因调用频繁,
            可考虑将角色权限缓存到redis中*/
            bool isAuthorized = true;
            if (!isAuthorized)
            {
                context.Result = new UnauthorizedResult();
                return;
            }
        }
    }

在需要授权的Action或Controller上加上该特性即可。

posted @ 2018-05-14 11:04  focus-lei  阅读(2830)  评论(1编辑  收藏  举报