zoukankan      html  css  js  c++  java
  • .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上加上该特性即可。

  • 相关阅读:
    [LeetCode]Surrounded Regions
    生产者消费者问题
    多线程试题汇总
    Linux多线程编程
    运算符优先级表
    正向代理和反向代理
    遗传算法
    嵌入式培训学习历程第十五天
    嵌入式培训学习历程第十四天
    一个猜数的游戏
  • 原文地址:https://www.cnblogs.com/focus-lei/p/9035062.html
Copyright © 2011-2022 走看看