Populate custom claim from SQL with Windows Authen

2019-07-31 08:12发布

Scenario - .Net Core Intranet Application within Active Directory using SQL Server to manage application specific permissions and extended user identity.

Success to date - User is authenticated and windows claims are available (Name and Groups). Identity.Name can be used to return a domain user model from the database with the extended properties.

Issue and Question - I am trying to then populate one custom claim property "Id" and have that globally available via the ClaimsPrincipal. I have looked into ClaimsTransformation without much success to date. In other articles I have read that you MUST add claims prior to Sign In but can that really be true? That would mean total reliance on AD to fulfil all claims, is that really the case?

Below is my simple code at this point in the HomeController. I am hitting the database and then trying to populate the ClaimsPrincipal but then return the domain user model. I think this could be where my problem lies but I am new to Authorisation in .net and struggling to get my head around claims.

Many thanks for all help received

Current Code:

public IActionResult Index()
        {
            var user = GetExtendedUserDetails();
            User.Claims.ToList();
            return View(user);
        }

        private Models.User GetExtendedUserDetails()
        {
            var user = _context.User.SingleOrDefault(m => m.Username == User.Identity.Name.Remove(0, 6));
            var claims = new List<Claim>();

            claims.Add(new Claim("Id", Convert.ToString(user.Id), ClaimValueTypes.String));
            var userIdentity = new ClaimsIdentity("Intranet");
            userIdentity.AddClaims(claims);
            var userPrincipal = new ClaimsPrincipal(userIdentity);

            return user;
        }

UPDATE:

I have registered ClaimsTransformation

app.UseClaimsTransformation(o => new ClaimsTransformer().TransformAsync(o));

and built ClaimsTransformer as below in line with this github query

https://github.com/aspnet/Security/issues/863

public class ClaimsTransformer : IClaimsTransformer
{
    private readonly TimesheetContext _context;
    public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
    {

        System.Security.Principal.WindowsIdentity windowsIdentity = null;

        foreach (var i in context.Principal.Identities)
        {
            //windows token
            if (i.GetType() == typeof(System.Security.Principal.WindowsIdentity))
            {
                windowsIdentity = (System.Security.Principal.WindowsIdentity)i;
            }
        }

        if (windowsIdentity != null)
        {
            //find user in database
            var username = windowsIdentity.Name.Remove(0, 6);
            var appUser = _context.User.FirstOrDefaultAsync(m => m.Username == username);

            if (appUser != null)
            {

                ((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim("Id", Convert.ToString(appUser.Id)));

                /*//add all claims from security profile
                foreach (var p in appUser.Id)
                {
                    ((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim(p.Permission, "true"));
                }*/

            }

        }
        return await System.Threading.Tasks.Task.FromResult(context.Principal);
    }
}

But am getting NullReferenceException: Object reference not set to an instance of an object error despite having returned the domain model previously.

WITH STARTUP.CS

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Birch.Intranet.Models;
using Microsoft.EntityFrameworkCore;

namespace Birch.Intranet
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAuthorization();

            // Add framework services.
            services.AddMvc();
            // Add database
            var connection = @"Data Source=****;Initial Catalog=Timesheet;Integrated Security=True;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
            services.AddDbContext<TimesheetContext>(options => options.UseSqlServer(connection));

            // Add session
            services.AddSession(options => {
                options.IdleTimeout = TimeSpan.FromMinutes(60);
                options.CookieName = ".Intranet";
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseClaimsTransformation(o => new ClaimsTransformer().TransformAsync(o));

            app.UseSession();
            app.UseDefaultFiles();
            app.UseStaticFiles();

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

1条回答
成全新的幸福
2楼-- · 2019-07-31 08:59

You need to use IClaimsTransformer with dependency injection.

    app.UseClaimsTransformation(async (context) =>
    {
        IClaimsTransformer transformer = context.Context.RequestServices.GetRequiredService<IClaimsTransformer>();
        return await transformer.TransformAsync(context);
    });

    // Register
    services.AddScoped<IClaimsTransformer, ClaimsTransformer>();

And need to inject DbContext in ClaimsTransformer

public class ClaimsTransformer : IClaimsTransformer
{
    private readonly TimesheetContext _context;
    public ClaimsTransformer(TimesheetContext  dbContext)
    {
        _context = dbContext;
    }
    // ....
 }
查看更多
登录 后发表回答