zoukankan      html  css  js  c++  java
  • 我的微服务项目之IdentityServer4

     2021,祝大家新年快乐!!!

     

         2021年了,新的一年应该有新的计划,我的计划是准备去学习微服务,所以我将我自己的博客项目拆分成了一个微服务项目,用来给自己学习,项目地址:http://www.ttblog.site/。之前做的一个认证服务比较简单,只是单纯的生成jwtToken,所以想改造下。看了很多资料,发现了.net下的一个基于 OpenID Connect 和 OAuth 2.0 认证框架:IdentityServer4,所以就查了一下资料就用在了我的项目上。因为我也是刚刚学习IdentityServer4,所以如果有说错的地方希望大家指出。

        我的想法是不想让我的api裸奔,所以要加个认证,不是谁都可以调用我的apide1,这里我选择了IdenttiyServer4的客户端凭据许可模式(client_credentials),这里推荐一个文章:https://www.cnblogs.com/ddrsql/p/7789064.html。因为我觉得这种模式正好符合自己的要求,对于其它的模式我也只是了解过,但是并没有实践过。

       第一步:搭建一个IdentityServer服务器

      

    添加IdentityServer4的nuget包。然后我添加了一个idenityserver.json的文件用来配置一些认证资源

    {
      "WebApi": {
        "ApiResource": "",
    "ApiScope": "", "Client": { "ClientId": "", "ClientName": "", "ClientSecrets": "", "AllowedGrantTypes": "", "AllowedScopes": "", "AccseeTokenTime": "" //单位秒 } } }

      然后添加ApiConfig类:

    using Core.Configuration;
    using IdentityServer4;
    using IdentityServer4.Models;
    using Microsoft.Extensions.Configuration;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using static IdentityServer4.IdentityServerConstants;
    
    namespace Core.Auth.IdentityServer4
    {
        public class ApiConfig
        {
            public static IConfiguration Configuration = ConfigureProvider.configuration;
            private static string[] ApiNames = { "WebApi" };
            /// <summary>
            /// Define which APIs will use this IdentityServer
            /// </summary>
            /// <returns></returns>
            public static IEnumerable<ApiResource> GetApiResources()
            {
                List<ApiResource> apiResources = new List<ApiResource>();
                foreach (var item in ApiNames)
                {
                    var configuration = Configuration.GetSection(item);
                    string name = configuration.GetSection("ApiResource").Value;
                    string[] scopes = configuration.GetSection("ApiScope").Value.Split(",");
                    ApiResource apiResource = new ApiResource(name, name) {
                        Scopes = scopes
                    };
                    apiResources.Add(apiResource);
                }
                return apiResources;
            }
            public static IEnumerable<ApiScope> GetApiScopes()
            {
                List<ApiScope> apiScopes = new List<ApiScope>();
                foreach (var item in ApiNames)
                {
                    var configuration = Configuration.GetSection(item);
                    string[] names = configuration.GetSection("ApiScope").Value.Split(",");
                    foreach(var name in names)
                    {
                        ApiScope apiScope = new ApiScope(name, name);
                        apiScopes.Add(apiScope);
                    }
                }
                return apiScopes;
            }
            /// <summary>
            /// Define which Apps will use thie IdentityServer
            /// </summary>
            /// <returns></returns>
            public static IEnumerable<Client> GetClients()
            {
                List<Client> clients = new List<Client>();
                foreach(var item in ApiNames)
                {
                    var configuration = Configuration.GetSection(item);
                    string clientId = configuration["Client:ClientId"];
                    string clientName = configuration["Client:ClientName"];
                    Secret[] secrets = configuration["Client:ClientSecrets"].Split(',').Select(s=>new Secret(s.Sha256())).ToArray() ;
                    string[] grantTypes = configuration["Client:AllowedGrantTypes"].Split(",");
                    List<string> allowedScopes = configuration["Client:AllowedScopes"].Split(',').ToList();
                    //allowedScopes.Add(IdentityServerConstants.StandardScopes.OpenId);
                    //allowedScopes.Add(IdentityServerConstants.StandardScopes.Profile);
                    allowedScopes.Add(StandardScopes.OfflineAccess);
                    int accseeTokenTime =Convert.ToInt32(configuration["Client:AccseeTokenTime"]);
                    Client client = new Client();
                    client.ClientId = clientId;
                    client.ClientName = clientName;
                    client.ClientSecrets = secrets;
                    client.AllowedGrantTypes = grantTypes;
                    client.AllowedScopes = allowedScopes;
                    client.AccessTokenLifetime = accseeTokenTime;
                    client.AllowOfflineAccess = true;
                    clients.Add(client);
    
                }
                return clients;
            }
    
            /// <summary>
            /// Define which IdentityResources will use this IdentityServer
            /// </summary>
            /// <returns></returns>
            public static IEnumerable<IdentityResource> GetIdentityResources()
            {
                return new List<IdentityResource>
                {
                    new IdentityResources.OpenId(),
                     new IdentityResources.Profile(),
                };
            }
        }
    }
    

      这里有个问题,我网上百度的很多人写的就是  ApiResource apiResource = new ApiResource(name, name);就好了,但是我这里不行,如果不像下面这样写的话就一直401,然后看控制台提示“

    IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler[7]
    WebApiAuthKey was not authenticated. Failure message: IDX10214: Audience validation failed. Audiences: 'System.String'. Did not match: validationParameters.ValidAudience: 'System.String' or validationParameters.ValidAudiences: 'System.String'.

    ”不知道是不是因为我集成了Ocelot的原因,也有人说是IdentityServer4的4.0版本之后都要这样写

    ApiResource apiResource = new ApiResource(name, name) {
    Scopes = scopes
    };
    

      然后开始添加服务认

    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.PlatformAbstractions;
    
    namespace Core.Auth.IdentityServer4
    {
        public static class ConfigureIdentityServer4
        {
            public static IServiceCollection AddIdentityServer4(this IServiceCollection services)
            {
                string basePath = PlatformServices.Default.Application.ApplicationBasePath;
                IIdentityServerBuilder identityServerBuilder = services.AddIdentityServer();
                identityServerBuilder.AddDeveloperSigningCredential();           
                identityServerBuilder.AddInMemoryIdentityResources(ApiConfig.GetIdentityResources())
                    .AddInMemoryApiResources(ApiConfig.GetApiResources())
                    .AddInMemoryClients(ApiConfig.GetClients())
                    .AddInMemoryApiScopes(ApiConfig.GetApiScopes());
                return services;
            }
        }
    }
    

      以上步骤弄完之后,你用postman访问http://localhost:5003/.well-known/openid-configuration,出现一下内容代表搭建成功:

    第二步:网关添加认证:

         最开始我想的是每一个服务都取添加注册认证服务,这样当然可以实现,但是这样网关悠悠什么用的。我对微服务的理解是,应该所有的请求通过网关,网关应该是是对外的,任何人都不应该知道具体的服务地址,包括认证授权,网关就应该起到一个对api的保护作用。所以我放弃了这种想法,转而去针对网关实现对认证服务的注册,很幸运,ocelot也支持identityserver,只需要简单的配置,如下部分截图:

    然后添加配置节点:

      "IdentityService": {
        "Uri": "http://localhost:5003",//自己的认证服务地址,网关的地址为5000
        "UseHttps": false,
        "ApiName": {
          "WebApi": "WebApi"
        }
      }
    

      并且在Startup里面添加如下代码:

     #region IdentityServerAuthenticationOptions => need to refactor
                Action<IdentityServerAuthenticationOptions> webOption = option =>
                {
                    option.Authority = Configuration["IdentityService:Uri"];
                    option.ApiName = Configuration["IdentityService:ApiName:WebApi"];
                    option.RequireHttpsMetadata = Convert.ToBoolean(Configuration["IdentityService:UseHttps"]);
                };
                services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
               .AddIdentityServerAuthentication("WebApiAuthKey", webOption);
    

     到此,可以说已经是初步完成了服务的认证,我这里并没有授权操作,所以就不谈授权。

     第三步 :请求获取token

          我们通过postman直接访问http://localhost:5003/connect/token,然后输入必要的参数,具体的操作可以网上百度,当然可以获取token。当是我这里是要结合我自己的项目,其中肯定不想让自己设定的一些关键信息如ClientId和Secret让别人知道,放到js里面,别人直接f12查看源码就知道了,所以我的想法是通过nginx设置header的方式,这样就可以了。如下:

           location /auth/credentials/token {
               proxy_set_header   client_secret   "";
               proxy_set_header  client_id   "";
               proxy_pass  http://127.0.0.1:5000;
           }     
         //我这里是通过Ocelot转发到认证服务的,不要盲目复制
    

      然后认证服务新加一个api

    using Core.Auth;
    using Core.Common.EnumExtensions;
    using Core.Common.Http;
    using IdentityModel.Client;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Options;
    using Microsoft.Extensions.Primitives;
    using Newtonsoft.Json;
    using System;
    using System.Collections.Generic;
    using System.IdentityModel.Tokens.Jwt;
    using System.Linq;
    using System.Net.Http;
    using System.Security.Claims;
    using System.Threading.Tasks;
    
    namespace BlogAuthApi
    {
        [Route("api/auth")]
        [ApiController]
        public class AuthController: ControllerBase
        {
            private IHttpClientFactory _httpClientFactory;
            public AuthController(IHttpClientFactory httpClientFactory)
            {
                _httpClientFactory = httpClientFactory;
            }
            //[Route("token")]
            //[HttpGet]
            //public ApiResult GetToken()
            //{
            //    DateTime now = DateTime.Now;
            //    var claims = new Claim[]
            //        {
            //            // 声明主题
            //            new Claim(JwtRegisteredClaimNames.Sub, "Blog"),
            //            //JWT ID 唯一标识符
            //            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            //            // 发布时间戳 issued timestamp
            //            new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(), ClaimValueTypes.Integer64)
            //        };
            //    JwtToken jwtToken = Jwt.GetToken(claims);
            //    return ApiResult.Success(jwtToken);
            //}
    
            [Route("credentials/token")]
            [HttpGet]
            public async Task<ApiResult> GetToken()
            {
                Request.Headers.TryGetValue("client_id", out StringValues clientId);
                Request.Headers.TryGetValue("client_secret", out StringValues clientSecret);
                HttpClient httpClient= _httpClientFactory.CreateClient();
                Dictionary<string, string> headers = new Dictionary<string, string>();
                headers.Add("client_id", clientId);
                headers.Add("client_secret", clientSecret);
                headers.Add("grant_type", "client_credentials");
                FormUrlEncodedContent content = new FormUrlEncodedContent(headers);
                var response=await httpClient.PostAsync("http://localhost:5003/connect/token",content);
                if (!response.IsSuccessStatusCode)
                    return ApiResult.Error(HttpStatusCode.BAD_REQUEST, response.StatusCode.GetEnumText());
                var responseString=await response.Content.ReadAsStringAsync();
                dynamic result = JsonConvert.DeserializeObject<dynamic>(responseString);
                int expires = result.expires_in;
                int minutes = new TimeSpan(0, 0, expires).Minutes;
                string token = result.access_token;
                JwtToken jwtToken = new JwtToken(token, minutes, DateTime.Now);
                return ApiResult.Success(jwtToken);          
            }
        }
    }
    

      这一样,我算是完成了自己的认证保护api的功能。

         接下来我们测试一下,访问接口,我这里nginx代理我本地的80端口

         

    获取到token,然后请求自己的接口,成功请求了:

    接下来输入一个错误的token,我在token上加上23456,或者不加token,发现直接401了

    ,

    然后过一段时间在访问,因为token已经过期了,所以也会401,我们看控制台输出

     这里有个问题,因为我设置的两分钟过期,但是两分钟过后并不会真的立刻过期,具体的原因参考:https://www.cnblogs.com/stulzq/p/8998274.html.

    我还没有具体的用在我的站点上面,因为我还没有弄IdentityServer4的证书,只是我本地用postman测试了下,我也是初步学习IdentityServer4,如果有不合理或错误的地方希望大家指出,欢迎大家访问我的站点:天天博客

  • 相关阅读:
    POJ1239
    HDU 2829 四边形不等式优化
    返回数字二进制的最高位位数o(n)
    矩阵快速幂 模板
    HDU4718 The LCIS on the Tree(LCT)
    HDU4010 Query on The Trees(LCT)
    HDU3487 Play With Chains(Splay)
    CF444C DZY Loves Colors
    HDU4836 The Query on the Tree(树状数组&&LCA)
    HDU4831&&4832&&4834
  • 原文地址:https://www.cnblogs.com/MrHanBlog/p/14334155.html
Copyright © 2011-2022 走看看