Как сделать безопасные вызовы API из службы ресурсов в службу проверки подлинности? - PullRequest
0 голосов
/ 29 ноября 2018

Инфраструктура:

  • 1 Серверы авторизации
  • ~ Серверы ресурсов

Технология:

  • ASP.NET MVC Owin
  • ASP.NET WebAPI Owin
  • ASP.NET Identity
  • Маркер носителя с использованием JWT
  • .Net Framework 4.7.1

Сценарий: У меня есть служба для обработки всех действий, связанных с доступом пользователей, в центральном месте (служба аутентификации) и 4 службы ресурсов (3 веб-сайта).и 1 Web.API).Эта настройка работает правильно, и я могу успешно передать сгенерированный токен на серверы ресурсов.В рамках службы аутентификации у меня есть дополнительный контроллер для обработки других запросов, используемых службами ресурсов, таких как регистрация, сброс пароля, смена пароля, изменение адреса электронной почты и т. Д. Однако в настоящий момент эти действия контроллера установлены на анонимность, ноМне нужно добавить атрибут [Authorize] на них.

AUTHSERVICE:

Startup.cs

/// <summary>
    /// 
    /// </summary>
    /// <param name="app">Application Builder</param>
    /// <param name="provider">Authorization Server Provider</param>
    public void ConfigureOAuth(IAppBuilder app, IOAuthAuthorizationServerProvider provider)
    {
        var oAuthServerOptions = new OAuthAuthorizationServerOptions()
        {
            TokenEndpointPath = new PathString("/oauth2/token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
            Provider = provider,
            RefreshTokenProvider = new RefreshTokenProvider(),
            ApplicationCanDisplayErrors = true,
            AllowInsecureHttp = true,
            AccessTokenFormat = new CustomJwtFormat("http://devurl")
        };

        // OAuth 2.0 Bearer Access Token Generation
        app.UseOAuthAuthorizationServer(oAuthServerOptions);
    }

ApplicationOAuthProvider.cs - внедрен в метод ConfigureOAuth

public class ApplicationOAuthProvider 
    : OAuthAuthorizationServerProvider
    {
        private readonly ApplicationUserManager _userManager;
        private readonly string _publicClientId;

        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="userManager"></param>
        /// <param name="publicClientId"></param>
        public ApplicationOAuthProvider(ApplicationUserManager userManager, string publicClientId = "self")
        {
            _userManager = userManager;
            _publicClientId = publicClientId;
        }

        /// <summary>
        /// Validate a client's Id
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            string clientId = context.ClientId;
            string clientSecret = string.Empty;
            string symmetricKeyAsBase64 = string.Empty;

            if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
            {
                context.TryGetFormCredentials(out clientId, out clientSecret);
            }

            if (context.ClientId == null)
            {
                context.SetError("invalid_clientId", "client_Id is not set");
                return Task.FromResult<object>(null);
            }

            var audience = ClientStore.FindClient(context.ClientId);

            if (audience == null)
            {
                context.SetError("invalid_clientId", $"Invalid client_id '{context.ClientId}'");
                return Task.FromResult<object>(null);
            }

            context.Validated();
            return Task.FromResult<object>(null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            User user = await _userManager.FindByEmailAsync(context.UserName);

            if (user == null)
            {
                context.SetError("invalid_grant", "The email address does not exist!");
                return;
            }

            user = await _userManager.FindAsync(context.UserName, context.Password);

            if (user == null)
            {
                context.SetError("invalid_grant", "The email address or password is incorrect.");
                return;
            }

            if (!await _userManager.IsEmailConfirmedAsync(user.Id))
            {
                context.SetError("invalid_grant", "The email address has not been confirmed.");
                return;
            }

            ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(_userManager, OAuthDefaults.AuthenticationType);
            ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(_userManager, CookieAuthenticationDefaults.AuthenticationType);

            AuthenticationProperties properties = CreateProperties(context, user.UserName);
            AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
            context.Validated(ticket);
            context.Request.Context.Authentication.SignIn(cookiesIdentity);
        }

        /// <summary>
        /// Token Endpoint
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task TokenEndpoint(OAuthTokenEndpointContext context)
        {
            foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
            {
                context.AdditionalResponseParameters.Add(property.Key, property.Value);
            }

            return Task.FromResult<object>(null);
        }

        /// <summary>
        /// Validate the client redirect URI
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
        {
            if (context.ClientId == _publicClientId)
            {
                Uri expectedRootUri = new Uri(context.Request.Uri, "/");

                if (expectedRootUri.AbsoluteUri == context.RedirectUri)
                {
                    context.Validated();
                }
            }

            return Task.FromResult<object>(null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="userName"></param>
        /// <returns></returns>
        private static AuthenticationProperties CreateProperties(OAuthGrantResourceOwnerCredentialsContext context, string userName)
        {
            IDictionary<string, string> data = new Dictionary<string, string>
            {
                { "username", userName },
                { "client", context.ClientId }
            };
            return new AuthenticationProperties(data);
        }

        /// <summary>
        /// Validate a token request
        /// </summary>
        /// <param name="context">The current request context</param>
        /// <returns></returns>
        public override Task ValidateTokenRequest(OAuthValidateTokenRequestContext context)
        {
            return base.ValidateTokenRequest(context);
        }
    }

RefreshTokenProvider.cs

public class RefreshTokenProvider : IAuthenticationTokenProvider
    {
        public async Task CreateAsync(AuthenticationTokenCreateContext context)
        {
            Create(context);
        }

        public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
        {
            Receive(context);
        }

        public void Create(AuthenticationTokenCreateContext context)
        {
            object inputs;
            context.OwinContext.Environment.TryGetValue("Microsoft.Owin.Form#collection", out inputs);

            var grantType = ((FormCollection)inputs)?.GetValues("grant_type");

            var grant = grantType.FirstOrDefault();

            if (grant == null || grant.Equals("refresh_token")) return;

            context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(1);

            context.SetToken(context.SerializeTicket());
        }

        public void Receive(AuthenticationTokenReceiveContext context)
        {
            context.DeserializeTicket(context.Token);

            if (context.Ticket == null)
            {
                context.Response.StatusCode = 400;
                context.Response.ContentType = "application/json";
                context.Response.ReasonPhrase = "invalid token";
                return;
            }

            if (context.Ticket.Properties.ExpiresUtc <= DateTime.UtcNow)
            {
                context.Response.StatusCode = 401;
                context.Response.ContentType = "application/json";
                context.Response.ReasonPhrase = "unauthorized";
                return;
            }

            context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(1);
            context.SetTicket(context.Ticket);
        }
    }

РЕСУРСНАЯ СЛУЖБА:

Startup.cs

public void Configuration(IAppBuilder app)
    {
        app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            LogoutPath = new PathString("/Account/LogOff"),
            CookieName = "access_token",
            Provider = new CookieAuthenticationProvider
            {
                OnResponseSignedIn = RememberMeTokenMiddleware.CheckAndCreateRememberMeToken,

                OnResponseSignOut = ctx =>
                {
                    RememberMeTokenMiddleware.Logout(ctx.OwinContext);
                },
            }
        });

        app.Use<RememberMeTokenMiddleware>();

        ConfigureOAuth(app);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="app"></param>
    public void ConfigureOAuth(IAppBuilder app)
    {
        ISettings settings = new Settings();
        app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            AuthenticationMode = AuthenticationMode.Active,
            AllowedAudiences = new[] { settings.AuthClientId },
            IssuerSecurityKeyProviders = new IIssuerSecurityKeyProvider[]
            {
                new SymmetricKeyIssuerSecurityKeyProvider(settings.AuthServiceUrl, TextEncodings.Base64Url.Decode("clientsecret"))
            },

        });
    }


    public class ProtectedController : Controller
    {
        // GET: Protected
        [Authorize]
        public ActionResult Index()
        {
            var claimsIdentity = User.Identity as ClaimsIdentity;

            var claims = claimsIdentity.Claims.Select(x => new { type = x.Type, value = x.Value });


            return View(claims);
        }
    }

Вопросы:

  1. Если я добавлю атрибут [Authorize] поверх действий, которые ему нужны, смените пароль, какили что разрешает этот вызов службе аутентификации, т. е. нужен ли мне специальный фильтр аутентификации для проверки токена носителя?
  2. Где я могу проверить, разрешено ли клиенту, вызывающему службу, и прекращать вызовы?от других клиентов отдыха?
  3. Я включил refresh_token при первой аутентификации, когда и где это вступает в силу или как я могу его использовать?

Не так многодокументации для полной распределенной архитектуры, подобной этой, и как сервисы должны взаимодействовать, пожалуйста, еслиВы можете предоставить мне демонстрационный проект.Безопасность так запутанно: - (

...