如何在 Dotnet Core 中拦截请求 Token Jwt 以发送到其他 API?

Posted

技术标签:

【中文标题】如何在 Dotnet Core 中拦截请求 Token Jwt 以发送到其他 API?【英文标题】:How to intercept request Token Jwt in Dotnet Core to Send to other API? 【发布时间】:2020-09-09 22:35:11 【问题描述】:

我正在尝试在 Dotnet Core 中进行过滤以验证其他登录 (Java) Api 中的令牌 JWT 我做到了。基本的是,因此接收到令牌,过滤器获取令牌 Jwt 并将其发送到验证 在其他 API。我正在尝试做,但我不知道如何做到这一点。我确实用另一种方法制作 但我不知道如何做正确的。

类 SendEmailController.cs:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class SendEmailController: ControllerBase

    private readonly IEmailSender emailSender;

    public SendEmailController(IEmailSender emailSender)
        this.emailSender = emailSender;
    

    [HttpPost]
    public async Task<ActionResult>  SendEmail(GetEmailDto emailDto) 
        string header = Request.Headers["Authorization"];
        string token = null;

        if(header != null && header.Contains("Bearer"))
            string [] aux = header.Split(" ");
            token = aux.Length > 1 ? aux[1].Trim() : token;
        

        if(token != null && await LoginService.ValidateToken(token) != null)
          return  Ok(this.emailSender.SendEmailAsync(emailDto));
         else 
          return BadRequest("Email not sended >> ");
        
    

LoginService.cs:

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
public class LoginService
 public static  async Task<string> ValidateToken(string token)

    try
    string tokenUrl = Environment.GetEnvironmentVariable("TOKEN_VALIDATE_URL");
    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8");
    client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", token);
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    return  await client.GetStringAsync(tokenUrl);
    
    catch(Exception e)
    
     Console.WriteLine("ERROR >> "+e.Message);
     return null;
       
    

Startup.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using AutoMapper;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

namespace apiEmail

    public class Startup
    
        public Startup(IConfiguration configuration)
        
            Configuration = configuration;
        

        public IConfiguration Configuration  get; 

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        
            services.AddDbContext<DataContext>();
            services.AddCors();
            services.AddControllers();
            services.AddAutoMapper(typeof(Startup));
            services.AddScoped<IEmailService, EmailService>();
            services.AddScoped<IAuthRepository, AuthRepository>();
            services.AddTransient<IEmailSender, AuthMessageSender>();
            services.AddMvc();
            var key = Encoding.ASCII.GetBytes(Environment.GetEnvironmentVariable("SECRET_KEY")+" DOTNET DA DEPRESSAO");
            services.AddAuthentication(x => 
               x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
               x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            )
            .AddJwtBearer(options => 
                options.RequireHttpsMetadata = false;
                options.SaveToken = true;
                options.TokenValidationParameters = new TokenValidationParameters
                
                    ValidateIssuerSigningKey = false,
                     IssuerSigningKey = new SymmetricSecurityKey(key),
                     ValidateIssuer = false,
                     ValidateAudience = false
                ;
            );
        

         private static void UpdateDatabase(IApplicationBuilder app)
            
                using (var serviceScope = app.ApplicationServices
                     .GetRequiredService<IServiceScopeFactory>()
                     .CreateScope())
            
                using (var context = serviceScope.ServiceProvider.GetService<DataContext>())
              
                context.Database.Migrate();
              
        
    

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataContext context)
        
            UpdateDatabase(app);
            if (env.IsDevelopment())
            
                app.UseDeveloperExceptionPage();
            
            //app.UseHttpsRedirection();
            app.UseRouting();
            app.UseCors(
                options => options
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader()
            );

           // app.UseAuthentication();

           var options = new JwtBearerOptions
           
                Audience = "...",
                Authority = "...",
                Events = new JwtBearerEvents
            
                OnTokenValidated = context =>
                
            // Add the access_token as a claim, as we may actually need it
            var accessToken = context.SecurityToken as JwtSecurityToken;
            if (accessToken != null)
            
               Console.Write("Token >>", accessToken);
            
            return Task.CompletedTask;
            
        
        ;    
           app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            
                endpoints.MapControllers();
            );
             context.Database.EnsureCreated();
             context.Database.Migrate();
        
    

如何在不停留在控制器中的情况下进行此验证,仅在定义的过滤器中进行 startup.cs 与 Java 中的此类过滤器类似?

例子:

@Slf4j
@Component
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class JwtRequestFilter extends OncePerRequestFilter 
    private JwtUtil jwtUtil;
    private LoginService login;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException 
        final String authorizationHeader = request.getHeader("Authorization");
        String username = null;
        String jwt = null;
        Users usr = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) 
            jwt = authorizationHeader.substring(7);
            username = jwtUtil.extractUsername(jwt);
        

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) 

            if (jwtUtil.isTokenExpired(jwt)) 
                throw new IOException("Token expirado ");
            
            try 
                usr = login.verifyToken(jwt);
                Authentication auth = new UsernamePasswordAuthenticationToken(usr.getEmail(), usr.getSenha());
                Set authorities = new HashSet<>();
                authorities.add(new SimpleGrantedAuthority(usr.getTipo().toString()));
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
                        = new UsernamePasswordAuthenticationToken(
                        auth.getName(), auth.getCredentials(), authorities);
                usernamePasswordAuthenticationToken
                        .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
             catch (Exception e) 
                log.error("Error >> ", e);
                throw new IOException("Erro ao validar o Token");
            

        
        chain.doFilter(request, response);
    

类LoginService.java:

public class LoginService
public  Users  verifyToken(String token) 
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("Authorization", "Bearer "+token);
        HttpEntity<String> entity = new HttpEntity<String>(headers);
        return  restTemplate.exchange(Assets.API_TOKEN_DATA, HttpMethod.PUT, entity, Users.class).getBody(); 
    

谢谢

【问题讨论】:

让我检查一下我是否理解这个问题:您在 .NET 上生成了一个 JWT,并且您希望将其验证为 Java JWT 应用程序,对吗? 我可能错过了一点,但我认为这不是 JWT 的工作方式。我的理解是——你应该得到一个签名的令牌,然后你获取发行者的公钥并在本地验证令牌签名——应该不需要进行更多的 API 调用。鉴于您已经使用 AddJwtBearer - 您可能只需要找到一种方法来让这些额外的颁发者验证工作 嗨@FelipeEsteves,不,我在 emailApi 中收到了一个令牌,我需要将它发送到 LoginApi 进行验证。我在申请标头中收到的令牌,并通过另一种方式在 Controller 中恢复。但是我搜索是否有可能在控制器之前恢复,比如java中的过滤器jwt,如果可能的话,如果可能的话,你能帮我找到方法吗??? 嗨@timur,所以有一个登录api来验证令牌是否有效,负责emailApi,我只需要在申请前进行过滤器验证。 这是项目:github.com/RafaelRfs/apiDotnetCoreEmail 【参考方案1】:

@RafaelRfs 我尝试将此作为评论发布,但它比支持的要长,因此我将其发布为答案。

一旦你读到,如果你不考虑答案,请告诉我,我会删除它。

基本上,您需要一个密钥和一种签名(算法)来生成 JWT 令牌。

在您的情况下,您在 .NET Core 上生成了该令牌,这完全没问题,然后您需要将此令牌发送到 Java API,这也应该完全没问题。

从技术上讲,如果您在两个站点上使用相同的密钥和相同的到期时间(.NET 和 Java),那么在 .NET 端生成的令牌应该在 Java 端完全可用。

我会严格按照我的观点做一些假设:

    在 java 端,您应该使用中间件来接收该令牌,并且该中间件为您执行该处理(从授权标头中获取令牌)并解密它而无需付出很大的努力。

    我有一组微服务,我在我的身份微服务上生成了一个 JWT,这个 JWT 可用于所有其他微服务,没有任何问题。

    即使有一个 .NET 应用程序生成该 JWT,如果您使用相同的密钥、相同的算法和相同的过期时间,您也可以在 Java 应用程序上使用该 JWT;

    您应该尝试使用这样的中间件在 Java 端实现 JWT。您正在编写大量代码来执行 Java 以多种方式完成的操作:https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/

    所有实现 JWT 的最佳方法,您都不会在控制器上看到任何解密令牌的逻辑,您也不必为此编写额外的代码。您只是在某个时候设置了一些中间件。如果您在这种情况下确实需要编写代码,您可以遵循此示例(具有声明。在 Microsoft Identity Framework + OAuth 上也可以在 .NET 中找到相同的模型):https://developer.okta.com/blog/2018/10/31/jwts-with-java

补充:

我对 REST 通常是:

安装 Swagger,这样我的所有 REST API 都会得到正确记录以供使用。 使用生成器读取 Swagger 开放文档以在端点上正确键入所有请求(在您的情况下,我将在两端使用 Swagger,而 .NET 端将具有来自 Java 端的映射。这将消除您在 .net 端编写的所有 WebClient 请求)。 正如您对中间件所做的那样,任何 JWT 中间件自然会返回 401 - 未经授权,以防“授权”标头不存在或带有无效或过期的不记名令牌。

抱歉,解释太长了。希望对您有所帮助。

【讨论】:

以上是关于如何在 Dotnet Core 中拦截请求 Token Jwt 以发送到其他 API?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ASP .NET Core Web API 中映射回退,以便 Blazor WASM 应用程序仅拦截未发送到 API 的请求

DotNet Core:如何跨平台客户端证书 TLS 身份验证?

dotnet core webapi json-api 兼容查询字符串路由

如何在 dotnet core 中制作自定义身份验证过滤器,如 Iauthentication

如何正确停止运行 dotnet core web 应用程序?

如何在 dotnet core 中验证非对称签名的 JWT?