Multiple authenticaion schemes in asp .net core 2.

2019-07-18 03:56发布

I need to support two authentication types in ASP.NET Core 2.0 MVC application:

  1. AddIdentityServerAuthentication
  2. AddOpenIdConnect

It was very easy in ASP.NET Core 1.0 version. But in version 2.0 version syntax changed. This is my code:

services.AddAuthentication(o =>
    {
        o.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        o.DefaultAuthenticateScheme = OpenIdConnectDefaults.AuthenticationScheme;
    }).AddIdentityServerAuthentication(options =>
    {
        options.Authority = PlatformConfiguration.IdentityServerUri;
        options.RequireHttpsMetadata = false;
        options.SaveToken = true;
        options.ApiSecret = "somesecret";
        options.ApiName = "some_api";
    })
    .AddCookie()
    .AddOpenIdConnect(o =>
    {
        o.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        o.Authority = PlatformConfiguration.IdentityServerUri;
        o.RequireHttpsMetadata = false;
        o.ClientId = "some_viewer";
        o.UseTokenLifetime = true;
        o.ResponseType = "id_token token";
        o.Scope.Add("openid");
        o.Scope.Add("roles");
        o.Scope.Add("profile");
        o.SaveTokens = true;
        o.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = JwtClaimTypes.Name,
            RoleClaimType = JwtClaimTypes.Role
        };
    });

services.AddAuthorization();

But in this way, the Bearer authentication doesn't work. Because of default schemes: DefaultChallengeScheme, DefaultAuthenticateScheme.
How to define several authentication schemes?

2条回答
劳资没心,怎么记你
2楼-- · 2019-07-18 04:38

Here is an example that I used to authenticate web pages using JWT token in ASP.NET 2.0

You can find the syntax I used even if it does not include all your needs I hope it will help you.

Asp Net Core

First step is write the method that configure Jwt authentication:

// Configure authentication with JWT (Json Web Token).
public void ConfigureJwtAuthService(IServiceCollection services)
{
  // Enable the use of an [Authorize(AuthenticationSchemes = 
  // JwtBearerDefaults.AuthenticationScheme)]
  // attribute on methods and classes to protect.
  services.AddAuthentication().AddJwtBearer(cfg =>
  {
    cfg.RequireHttpsMetadata = false;
    cfg.SaveToken = true;
    cfg.TokenValidationParameters = new TokenValidationParameters()
    {
      IssuerSigningKey = JwtController.SecurityKey,
      ValidAudience = JwtController.Audience,
      ValidIssuer = JwtController.Issuer,
      // When receiving a token, check that we've signed it.
      ValidateIssuerSigningKey = true,
      // When receiving a token, check that it is still valid.
      ValidateLifetime = true,
      // This defines the maximum allowable clock skew when validating 
      // the lifetime. As we're creating the tokens locally and validating
      // them on the same machines which should have synchronised time,
      // this can be set to zero.
      ClockSkew = TimeSpan.FromMinutes(0)
    };
  });
}

Now inside the ConfigureServices() method of the Startup.cs, we can call ConfigureJwtAuthService() method to configure the Jwt authentication.

This is the complete Startup.cs:

using System;
using Autofac;
using ExpertCodeBlogWebApp.Controllers;
using ExpertCodeBlogWebApp.Domain;
using ExpertCodeBlogWebApp.Domain.Interfaces;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

using Microsoft.IdentityModel.Tokens;

namespace ExpertCodeBlogWebApp
{
  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 IServiceProvider ConfigureServices(IServiceCollection services)
  {
    services.AddMvc();

    // Configure jwt autenticazione 
    ConfigureJwtAuthService(services);

    // Repositories
    services.AddScoped<IUserRepository, UserRepository>();

    // Create the Autofac container builder for dependency injection
    var builder = new ContainerBuilder();

    // Add any Autofac modules or registrations. 
    builder.RegisterModule(new AutofacModule());

    // Return ServiceProvider
    var serviceProvider = services.BuildServiceProvider();
    return serviceProvider;
  }

  // Configure authentication with JWT (Json Web Token).
  public void ConfigureJwtAuthService(IServiceCollection services)
  {
    // Enable the use of an [Authorize(AuthenticationSchemes = 
    // JwtBearerDefaults.AuthenticationScheme)]
    // attribute on methods and classes to protect.
    services.AddAuthentication().AddJwtBearer(cfg =>
    {
      cfg.RequireHttpsMetadata = false;
      cfg.SaveToken = true;

      cfg.TokenValidationParameters = new TokenValidationParameters()
      {
        IssuerSigningKey = JwtController.SecurityKey,
        ValidAudience = JwtController.Audience,
        ValidIssuer = JwtController.Issuer,
        // When receiving a token, check that we've signed it.
        ValidateIssuerSigningKey = true,
        // When receiving a token, check that it is still valid.
        ValidateLifetime = true,
        // This defines the maximum allowable clock skew when validating 
        // the lifetime.
        // As we're creating the tokens locally and validating them on the 
        // same machines which should have synchronised time, this can be 
        // set to zero.
        ClockSkew = TimeSpan.FromMinutes(0)
      };
    });
  }

  // This method gets called by the runtime. Use this method to configure 
  // the HTTP request pipeline.
  public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  {
    if (env.IsDevelopment())
    {
      app.UseDeveloperExceptionPage();
      app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
      {
        HotModuleReplacement = true
      });
    }
    else
    {
      app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
      routes.MapRoute(
      name: "default",
      template: "{controller=Home}/{action=Index}/{id?}");

      routes.MapSpaFallbackRoute(
        name: "spa-fallback",
        defaults: new { controller = "Home", action = "Index" });
      });
    }
  }

  // For dependency injection.
  public class AutofacModule : Module
  {
    // Dependency Injection with Autofact
    protected override void Load(ContainerBuilder builder)
    {
      builder.RegisterType<UserRepository>().As<IUserRepository>()
        .SingleInstance();
    }
  }
}

The JwtController.cs

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using ExpertCodeBlogWebApp.Domain;
using ExpertCodeBlogWebApp.Domain.Interfaces;
using ExpertCodeBlogWebApp.Domain.Models;
using ExpertCodeBlogWebApp.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;

namespace ExpertCodeBlogWebApp.Controllers
{

[Route("api/[controller]")]
public class JwtController : Controller
{
  #region Private Members
  // JWT-related members
  private TimeSpan TokenExpiration;
  private SigningCredentials SigningCredentials;
  // EF and Identity members, available through DI
  private MyDbContext DbContext;
  private IUserRepository _userRepository;
  private readonly ILogger _logger;
  #endregion Private Members

  #region Static Members
  private static readonly string PrivateKey = "my_PrivateKey";
  public static readonly SymmetricSecurityKey SecurityKey = 
    new SymmetricSecurityKey(Encoding.ASCII.GetBytes(PrivateKey));
  public static readonly string Issuer = "my_Issuer";
  public static readonly string Audience = "my_Audience";
  #endregion Static Members

  #region Constructor
  // I have used Autofac in the Startup.cs for dependency injection)
  public JwtController(
    MyDbContext dbContext,
    IUserRepository userRepository,
    ILogger<JwtController> logger)
  {
    _logger = logger;
    _userRepository = userRepository;
    // Instantiate JWT-related members
    TokenExpiration = TimeSpan.FromMinutes(10);
    SigningCredentials = new SigningCredentials(SecurityKey, 
      SecurityAlgorithms.HmacSha256);
    // Instantiate through Dependency Injection with Autofact
    DbContext = dbContext;
  }
  #endregion Constructor

  #region Public Methods 
  // Manages the request for a new authentication or the refresh of an 
  // already established one
  [HttpPost("token")]
  public async Task<IActionResult> 
    Authentication([FromBody]JwtRequestViewModel jwt)
  {
    if (ModelState.IsValid)
    {
      string grantType = jwt.GrantType; 
      if (grantType == "password")
      {
        string userName = jwt.UserName;
        string password = jwt.Password;

        // Password check required
        var user = await 
          _userRepository.GetUserInfoWithCheckPwd(userName, password);

        // Check if user is expired (check the ExpireDate property)
        if (UserExpired(user))
          return BadRequest($"Account of {user.Name} expired!");

        if (UserEnabled(user))
          return await GenerateToken(user);
        else
          return BadRequest("User name or password invalid.");
      }
    }
    else if (grantType == "refresh_token")
    {
      string userName = jwt.UserName;

      // Refresh token (no password check required)
      var user = await _userRepository.GetUserInfoByName(userName);

      // Check if user is expired (check the ExpireDate property)
      if (UserExpired(user))
        return BadRequest($"Account of {user.Name} expired!");

      string token = jwt.Token;
      if (token == user.Token)
      {
        // Generate token and send it via a json-formatted string
        return await GenerateToken(user);
      }
      else
      {
        return BadRequest("User token invalid.");
      }
    }
    else
      return BadRequest("Authentication type invalid.");
  }
  else
    return BadRequest("Request invalid.");
  }
  #endregion Public Methods

  #region Private Methods
  private bool UserExpired(Users utente)
  {
    if (utente != null)
      return utente.ExpireDate.CompareTo(DateTime.Now) < 0;
    return true;
  }

  private bool UserEnabled(Users utente)
  {
    if (utente != null)
      return utente.Enabled == true;
    return false;
  }

  private JsonSerializerSettings DefaultJsonSettings
  {
    get
    {
      return new JsonSerializerSettings()
      {
        Formatting = Formatting.Indented
      };
    }
  }

  private async Task<IActionResult> GenerateToken(Users user)
  {
    try
    {
      if (user != null)
      {
        var handler = new JwtSecurityTokenHandler();
        DateTime newTokenExpiration = DateTime.Now.Add(TokenExpiration);

        ClaimsIdentity identity = new ClaimsIdentity(
          new GenericIdentity(user.Name, "TokenAuth"),
          new[] { new Claim("ID", user.Id.ToString())}
        );

        var securityToken = handler.CreateToken(new SecurityTokenDescriptor
        {
          Issuer = JwtController.Issuer,
          Audience = JwtController.Audience,
          SigningCredentials = SigningCredentials,
          Subject = identity,
          Expires = newTokenExpiration
        });
        string encodedToken = handler.WriteToken(securityToken);

        // Update token data on database
        await _userRepository.UpdateTokenData(user.Name, encodedToken, 
          newTokenExpiration);
        // Build the json response 
        // (I use Automapper to maps an object into another object)
        var jwtResponse = Mapper.Map<JwtResponseViewModel>(user);
        jwtResponse.AccessToken = encodedToken;
        jwtResponse.Expiration = (int)TokenExpiration.TotalSeconds;
        return Ok(jwtResponse);
      }
      return NotFound();
      }
      catch(Exception e)
      {
        return BadRequest(e.Message);
      }
    }
    #endregion
  }
}

On my project I use Angular. For call JwtController method by Angular:

login(userName: string, password: string)
{
  return this.getLoginEndpoint(userName, password)
    .map((response: Response) => this.processLoginResponse(response));
}

getLoginEndpoint(userName: string, password: string): Observable<Response> 
{
  // Body
  // JwtRequest is a model class that I use to send info to the controller
  let jwt = new JwtRequest(); 
  jwt.GrantType = "password";
  jwt.UserName = userName;
  jwt.Password = password;
  jwt.ClientId = "my_Issuer";
  // Post requiest (I use getAuthHeader that attach to the header the
  // authentication token, but it can also be omitted because it is ignored
  // by the JwtController
  return this.http.post(this.loginUrl, JSON.stringify(jwt), 
    this.getAuthHeader(true))
}

protected getAuthHeader(includeJsonContentType?: boolean): RequestOptions
{
  // Hera I use this.authService.accessToken  that is a my service where
  // I have store the token received from the server
  let headers = new Headers({
    'Authorization': 'Bearer ' + this.authService.accessToken });

  if (includeJsonContentType)
    headers.append("Content-Type", "application/json");

  headers.append("Accept", `application/vnd.iman.v01+json, 
    application/json, text/plain, */*`);
  headers.append("App-Version", "01");

  return new RequestOptions({ headers: headers });
}

private processLoginResponse(response: Response)
{
  // process the response..
}

On the controllers classes (or methods) that you want to be accessible only by authenticated users (not on your JwtController because its method must be accessible by all users) you can set:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

To call from Angular the controller method that require authentication, you need to attach the token into the header with the getAuthHeader() method.

I hope this post can help you.

查看更多
Root(大扎)
3楼-- · 2019-07-18 04:41

I've added attribute

[Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme + "," + OpenIdConnectDefaults.AuthenticationScheme)]

And now I have two authentication schemes.

More flexible solution to use this code in Startup:

if (UseAuthorization)
{
    var policy = new AuthorizationPolicyBuilder(IdentityServerAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme)
        .RequireAuthenticatedUser()
        .Build();

    options.Filters.Add(new AuthorizeFilter(policy));
}
查看更多
登录 后发表回答