Приложение ASP.NET MVC5/AngularJS/Web API с использованием проверки подлинности Windows и OWIN

Я могу переусердствовать, но у нас есть внутренний ASP.NET MVC5 SPA с AngularJS, использующий проверку подлинности Windows. Это приложение имеет базовую базу данных SQL, в которой есть таблица пользователей, содержащая имена своих учетных записей и их соответствующие роли в приложении. Мы будем звонить в другое приложение веб-API, которое также включает проверку подлинности Windows.

Я попытался провести исследование того, как обрабатывать авторизацию с использованием OWIN, но не смог найти каких-либо конкретных примеров, касающихся OWIN и проверки подлинности Windows. Все, что появляется, использует проверку подлинности с использованием имени пользователя и пароля.

Как я могу использовать OWIN и Windows Auth для моего приложения? Вот пример моего класса OAuthAuthorizationServerProvider.

public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        context.Validated();
        return;
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

        var container = UnityHelper.GetContainerInstance("***");
        var securityHelper = container.Resolve<ISecurityHelper>();

        User currentUser = securityHelper.GetCurrentUser(); // Validates user based on HttpContext.Current.User
        if (currentUser == null)
        {
            context.SetError("invalid_grant", "The user could not be found.");
            return;
        }

        var identity = new ClaimsIdentity(context.Options.AuthenticationType);
        identity.AddClaim(new Claim("sub", currentUser.AccountName));
        identity.AddClaim(new Claim("role", "user"));

        context.Validated(identity);
    }
}

UPDATE: К сожалению, я забыл включить дополнительную информацию о том, что мы хотели бы выполнить. Если возможно, мы хотели бы использовать билеты на аутентификацию на предъявителя, поэтому нам не нужно искать пользователя и их роли каждый раз, когда мы вызываем метод web api.

ОБНОВЛЕНИЕ 2: По просьбе Андрея, ниже приведена версия TL;DR моего класса _securityHelper, в частности метод GetCurrentUser(). Вы заметите, что я пытаюсь позвонить:

HttpContext.Current.GetOwinContext().Request.User.Identity.Name

Это всегда возвращает null для пользователя.

public class SecurityHelper : ISecurityHelper
{
    private readonly ISecurityGroupController _securityGroupController;
    private readonly IUserController _userController;
    private readonly IEmployeeController _employeeController;
    private readonly IFieldPermissionController _fieldPermissionController;
    private readonly IOACController _oacController;

    public SecurityHelper(ISecurityGroupController securityGroupController,
        IUserController userController,
        IEmployeeController employeeController,
        IFieldPermissionController fieldPermissionController,
        IOACController oacController)
    {
        _securityGroupController = securityGroupController;
        _userController = userController;
        _employeeController = employeeController;
        _fieldPermissionController = fieldPermissionController;
        _oacController = oacController;
    }

    // ... other methods

    public User GetCurrentUser()
    {
        User user = _userController.GetByAccountName(HttpContext.Current.GetOwinContext().Request.User.Identity.Name);
        if (user != null)
        {
            List<OAC> memberships = _oacController.GetMemberships(user.SourceId).ToList();
            if (IsTestModeEnabled() && ((user.OACMemberships != null && user.OACMemberships.Count == 0) || user.OACMemberships == null))
            {
                user.OACMemberships = memberships;
            }
            else if (!IsTestModeEnabled())
            {
                user.OACMemberships = memberships;
            }
        }

        return user;
    }
}

Ответы

Ответ 1

Эта серия статей будет хорошим началом: http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/

примечание, будет следующий код, который по существу хранит токен-носитель в локальном хранилище и прикрепляет его к заголовкам. Очевидно, это намного больше, чем это, включая формы и фактическую систему аутентификации сервера, но это должно дать вам достойный старт.

серверный компонент:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        ConfigureOAuth(app);
    //Rest of code is here;
    }

    public void ConfigureOAuth(IAppBuilder app)
    {
        OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new SimpleAuthorizationServerProvider()
        };

        // Token Generation
        app.UseOAuthAuthorizationServer(OAuthServerOptions);
        app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

    }
}

и следующий код на стороне клиента:

'use strict';
app.factory('authService', ['$http', '$q', 'localStorageService', function ($http, $q, localStorageService) {

    var serviceBase = 'http://ngauthenticationapi.azurewebsites.net/';
    var authServiceFactory = {};

    var _authentication = {
        isAuth: false,
        userName : ""
    };

    var _saveRegistration = function (registration) {

        _logOut();

        return $http.post(serviceBase + 'api/account/register', registration).then(function (response) {
            return response;
        });

    };

    var _login = function (loginData) {

        var data = "grant_type=password&username=" + loginData.userName + "&password=" + loginData.password;

        var deferred = $q.defer();

        $http.post(serviceBase + 'token', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).success(function (response) {

            localStorageService.set('authorizationData', { token: response.access_token, userName: loginData.userName });

            _authentication.isAuth = true;
            _authentication.userName = loginData.userName;

            deferred.resolve(response);

        }).error(function (err, status) {
            _logOut();
            deferred.reject(err);
        });

        return deferred.promise;

    };

    var _logOut = function () {

        localStorageService.remove('authorizationData');

        _authentication.isAuth = false;
        _authentication.userName = "";

    };

    var _fillAuthData = function () {

        var authData = localStorageService.get('authorizationData');
        if (authData)
        {
            _authentication.isAuth = true;
            _authentication.userName = authData.userName;
        }

    }

    authServiceFactory.saveRegistration = _saveRegistration;
    authServiceFactory.login = _login;
    authServiceFactory.logOut = _logOut;
    authServiceFactory.fillAuthData = _fillAuthData;
    authServiceFactory.authentication = _authentication;

    return authServiceFactory;
}]);

вместе с

'use strict';
app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {

    var authInterceptorServiceFactory = {};

    var _request = function (config) {

        config.headers = config.headers || {};

        var authData = localStorageService.get('authorizationData');
        if (authData) {
            config.headers.Authorization = 'Bearer ' + authData.token;
        }

        return config;
    }

    var _responseError = function (rejection) {
        if (rejection.status === 401) {
            $location.path('/login');
        }
        return $q.reject(rejection);
    }

    authInterceptorServiceFactory.request = _request;
    authInterceptorServiceFactory.responseError = _responseError;

    return authInterceptorServiceFactory;
}]);

Ответ 2

Ознакомьтесь с этой статьей для шагов по включению проверки подлинности Windows в OWIN: http://www.asp.net/aspnet/overview/owin-and-katana/enabling-windows-authentication-in-katana

Из статьи:

В настоящее время Katana не предоставляет промежуточное ПО OWIN для проверки подлинности Windows, поскольку эта функциональность уже доступна на серверах.

Связанная статья включает включение проверки подлинности Windows для разработки. Для развертывания эти параметры указаны в IIS в разделе "Аутентификация". Пользователям будет предложено ввести свое имя пользователя и пароль браузером, когда они впервые прибудут на вашу страницу приложения.