Проверка подлинности службы аутентификации не выполняется при использовании постоянных сеансов

При использовании временных сессий работает нормально. Войдите в службу аутентификации и вызов / аутентификация без каких-либо параметров, и это показывает отображаемое имя, идентификатор сессии и т. Д.

Когда я вхожу с RememberMe=true, этот вызов возвращает информацию о сеансе должным образом. Но при последующих вызовах / auth без каких-либо параметров ServiceStack возвращает 401, не прошедший проверку подлинности. Свойство IsAuthenticated объекта сеанса имеет значение true и фактически существует. Мой код проверяет это, и если он ложный, перенаправляет пользователя на страницу входа, чего не происходит, поэтому я знаю, что пользователь действительно аутентифицирован.

Я не делаю ничего другого. Как я могу аутентифицироваться в постоянном сеансе и получать последующие вызовы / auth, чтобы подтвердить, что я вошел в систему?

Если это поможет, я использую CustomCredentialsProvider.

Обновить:

Код AppHost:

    public override void Configure(Funq.Container container)
    {
        //Set JSON web services to return idiomatic JSON camelCase properties
        ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;

        Config.RestrictAllCookiesToDomain = ConfigurationManager.AppSettings["cookieDomain"];

        Plugins.Add(new AuthFeature(() => new CustomUserSession(),
            new IAuthProvider[] { 
                    new CustomCredentialsProvider() 
                        { SessionExpiry = 
                            TimeSpan.FromMinutes(Convert.ToDouble(ConfigurationManager.AppSettings["SessionTimeout"])) 
                        }, 
                }) //end IAuthProvider
                {
                    IncludeAssignRoleServices = false,
                    IncludeRegistrationService = false,
                    HtmlRedirect = ConfigurationManager.AppSettings["mainSiteLink"] + "Login.aspx"
                } //end AuthFeature initializers
                );//end plugins.add AuthFeature

        Plugins.Add(new PostmanFeature() { EnableSessionExport = true });// this is only for when we want the feature and it's NOT in DebugMode
        Plugins.Add(new SwaggerFeature());
        Plugins.Add(new CorsFeature(allowedOrigins: "*",
                                    allowedMethods: "GET, POST, PUT, DELETE, OPTIONS",
                                    allowedHeaders: "Content-Type, Authorization, Accept",
                                    allowCredentials: true));


        container.Register<IRedisClientsManager>
            (c => new PooledRedisClientManager(2, ConfigurationManager.AppSettings["redisIpPort"]));
        container.Register<ICacheClient>(c => c.Resolve<IRedisClientsManager>().GetCacheClient());

        container.Register<ISessionFactory>(c => new SessionFactory(c.Resolve<ICacheClient>()));


        var userRep = new InMemoryAuthRepository();
        container.Register<IUserAuthRepository>(userRep);

        //Set MVC to use the same Funq IOC as ServiceStack
        ControllerBuilder.Current.SetControllerFactory(new FunqControllerFactory(container));

#if DEBUG
        Config.DebugMode = true;

        typeof(Authenticate).AddAttributes
            (
                new RestrictAttribute
                    (RequestAttributes.HttpGet | RequestAttributes.HttpPost)
            );

#else
        typeof(Authenticate).AddAttributes(new RestrictAttribute(RequestAttributes.HttpPost));
#endif

        RegisterTypedRequestFilter<Authenticate>((req, res, dto) =>
            {
                if (dto.UserName != null && dto.UserName != string.Empty
                    && dto.Password != null && dto.Password != string.Empty)
                    if(dto.RememberMe == null)
                        dto.RememberMe = false; 
            });

        RegisterTypedResponseFilter<AuthenticateResponse>((req, res, dto) =>
            {
                var appSettings = new ServiceStack.Configuration.AppSettings();
                dto.UserId = AppHostBase.Instance.TryResolve<ICacheClient>().SessionAs<CustomUserSession>().UserId.ToString();
                dto.Meta = new Dictionary<string, string>();
                dto.Meta.Add("ExpiresMinutes", appSettings.Get("SessionTimeout"));
            });
    }

    public static void Start()
    {
        Licensing.RegisterLicense(licenceKey);
        new ServiceStackAppHost().Init();
    }

Заголовки начального запроса:

HTTPS://****.com/api2/auth имя пользователя = пользователя и пароль = passwordmberme= истина

  • GET /api2/auth? Username=user&password=password& запомнить me=true HTTP/1.1
  • Принять: текст / html, приложение / xhtml + xml, /
  • Accept-Language: en-US
  • Пользователь-агент: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0), как Gecko
  • Accept-Encoding: gzip, выкачать
  • Ведущий: propel.zola360.com
  • DNT: 1
  • Подключение: Keep-Alive
  • Cookie: ss-pid = P2hslABCmSs7pomRqNz5; сс-OPT = завивка; X-UAId =

Заголовки начального ответа:

  • HTTP / 1.1 200 ОК
  • Cache-Control: приватный
  • Тип контента: текст / HTML
  • Контент-кодировка: gzip
  • Варьируется: Accept-Encoding
  • Сервер: Microsoft-IIS / 7.5
  • X-Powered-By: ServiceStack / 4.033 Win32NT /.NET
  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
  • Access-Control-Allow-Headers: Content-Type, Authorization, Accept
  • Access-Control-Allow-Credentials: правда
  • X-AspNet-версия: 4.0.30319
  • Set-Cookie: ss-id = pojZkNAdMcEcACDREcRM; домен =.zola360.com; Путь = /; HttpOnly
  • Set-Cookie: ss-opt = perm; домен =.zola360.com; истекает = понедельник, 13 ноября 2034 16:11:09 GMT; - путь = /; HttpOnly
  • Set-Cookie: X-UAId =; домен =.zola360.com; истекает = понедельник, 13 ноября 2034 16:11:09 GMT; Путь = /; HttpOnly
  • Set-Cookie: 47 = 0; домен =.zola360.com; Путь = /
  • Set-Cookie: UserId = 47; домен =.zola360.com; Путь = /
  • X-Powered-By: ASP.NET
  • Дата: чт, 13 ноября 2014 16:11:09 GMT
  • Длина контента: 4129

Начальный ответ тела:

{ "Идентификатор пользователя":"47","SESSIONID":"PKrITmRawxAtnaABCDgN","имя_пользователя": "пользователь","responseStatus":{},"мета":{"ExpiresMinutes":"360"}}

Последующий вызов / запрос авторизации:

  • GET / api2 / auth HTTP / 1.1
  • Принять: текст / html, приложение / xhtml + xml, /
  • Accept-Language: en-US
  • Пользователь-агент: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0), как Gecko
  • Accept-Encoding: gzip, выкачать
  • Ведущий: propel.zola360.com
  • DNT: 1
  • Подключение: Keep-Alive
  • Cookie: ss-pid = cvgslABCmSs6pomYdLu0; сс-OPT = завивка; X-UAId=; сс-ID =lYWZkFAdMcZcABCDcRM; 47=0; UserId=47

Последующий вызов к / auth response

  • HTTP / 1.1 401 не аутентифицирован
  • Cache-Control: приватный
  • Тип контента: текст / HTML
  • Варь: Принять
  • Сервер: Microsoft-IIS / 7.5
  • X-Powered-By: ServiceStack / 4.033 Win32NT /.NET
  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
  • Access-Control-Allow-Headers: Content-Type, Authorization, Accept
  • Access-Control-Allow-Credentials: правда
  • X-AspNet-версия: 4.0.30319
  • X-Powered-By: ASP.NET
  • Дата: четверг, 13 ноября 2014 г. 16:11:23 GMT
  • Длина контента: 9731

Последующий звонок в / auth body:

{"responseStatus": {"errorCode": "Not Authenticated", "message": "Not Authenticated", "stackTrace": "[Authenticate: 13.11.2014 15:27:49 PM]:\n[ЗАПРОС: {}]\nServiceStack.HttpError: Не аутентифицировано \r\n в ServiceStack.Auth.AuthenticateService.Post(запрос аутентификации)\r\n в lambda_method(Closure, Object, Object)\r\n в ServiceStack.Host.ServiceRunner`1.Execute(запрос IRequest, экземпляр объекта, запрос TRequestDto)","errors":[]}}

Обновление Я создал небольшой скрипт на Python3 для аутентификации и вызова другого веб-сервиса. После аутентификации с использованием RememberMe = true файлы cookie возвращаются, как и ожидалось: ss-id / pid установлены нормально и ss-opt = perm. Я решил напечатать cookie заголовка и просто вставить его в заголовок другого запроса, чтобы вызвать другой сервис, помеченный [Authenticate]. Это не сработало. Поэтому я попробовал что-то глупое и вставил значение cookie ss-pid в значение ss-id. Это сработало.

Вот ошибочная строка cookie (сессия отредактирована:)):

cookie = "ss-id = ss-ID-session-cookie; домен =.zola360.com; путь = /; HttpOnly, ss-pid = ss-PID-сеанс-cookie; домен =.zola360.com; истекает = вт, 14 ноября 2034 01:34:25 GMT; путь =/; HttpOnly, ss-opt=perm; домен =.zola360.com; срок действия = вт, 14 ноября 2034 01:34:25 GMT; путь =/; HttpOnly, X-UAId=; домен =.zola360.com; истекает = вторник, 14 ноября 2034 г. 01:34:25 GMT; путь =/; HttpOnly, 47=0; домен =.zola360.com; путь =/, UserId=47; домен =.zola360.com; путь =/"

И просто вставка значения ss-pid в ss-id работает:

cookie = "ss-id = ss-PID-сеанс-cookie; домен =.zola360.com; путь = /; HttpOnly, ss-pid = ss-PID-сеанс-cookie; домен =.zola360.com; истекает = вторник, 14 ноября 2034 01:34:25 GMT; путь =/; HttpOnly, ss-opt=perm; домен =.zola360.com; срок действия = вт, 14 ноября 2034 01:34:25 GMT; путь =/; HttpOnly, X-UAId=; домен =.zola360.com; истекает = вторник, 14 ноября 2034 г. 01:34:25 GMT; путь =/; HttpOnly, 47=0; домен =.zola360.com; путь =/, UserId=47; домен =.zola360.com; путь =/"

И скрипт Python3, который я использовал:

import httplib2 as http
import json

try:
    from urlparse import urlparse
except ImportError:
    from urllib.parse import urlparse

headers = {
    'Accept': 'application/json',
    'Content-Type': 'application/json; charset=UTF-8'
}

uri = 'https://mysite.com'
path = '/api2/auth/credentials'

target = urlparse(uri+path)
method = 'POST'
body = '{"username": "username", "password": "password", "RememberMe": "true"}'.encode()

h = http.Http()

response, content = h.request(target.geturl(), method, body, headers)

#save the cookie and use it for subsequent requests
cookie = response['set-cookie']

print(cookie)

path2 = '/api2/time/start'
target2 = urlparse(uri+path2)

headers['cookie'] = cookie

response, content = h.request(target2.geturl(), 'GET', body, headers)

# assume that content is a json reply
# parse content with the json module
data = json.loads(content.decode())

print(data)

Кажется, что-то все еще смотрит на значение ss-id, даже если ss-opt = perm.

1 ответ

Решение

При аутентификации с GET /api2/auth?username=user&password=... оно отправлено с вашим постоянным cookie ss-pidт.е.

Cookie: ss-pid=P2hslABCmSs7pomRqNz5; ss-opt=perm; X-UAId=

rememberme=true опция говорит ServiceStack поддерживать сеанс пользователей против постоянного ss-pid печенье. Эта опция поддерживается пользователями ss-opt=perm cookie, который HTTP-ответ сообщает клиенту добавить:

Set-Cookie: ss-opt=perm; domain=.zola360.com; expires=Mon, 13-Nov-2034 16:11:09 GMT; - path=/; HttpOnly

Хотя это и не важно в этом случае, так как временная сессия ss-id ServiceStack указывает клиенту добавить новый запрос с помощью:

Set-Cookie: ss-id=pojZkNAdMcEcACDREcRM; domain=.zola360.com; path=/; HttpOnly

Вопрос с последующим запросом GET /api2/auth где клиент не пересылает ss-pid cookie, он изначально аутентифицирован с помощью (т.е. P2hslABCmSs7pomRqNz5 против cvgslABCmSs6pomYdLu0):

Cookie: ss-pid=cvgslABCmSs6pomYdLu0; ss-opt=perm; X-UAId=; ss-id=lYWZkFAdMcZcABCDcRM; 47=0; UserId=47

Какой ServiceStack не знает (т.е. не поддерживает сессию против), поэтому он возвращается с 401 Not Authenticated как и ожидалось.

HTTP-клиент должен быть настроен для повторной отправки файлов cookie

Не ясно, какой HTTP-клиент вы используете, но он должен быть настроен на повторную отправку файлов cookie, что обычно является поведением по умолчанию. Аякс отправит как постоянный ss-pid печенье и временные ss-id только для этого сеанса браузера, например, временный ss-id cookie будет удален, когда браузер будет закрыт, а новый запрос получит новый ss-id печенье.

С помощью клиентов службы C# он только повторно отправляет постоянные файлы cookie, поэтому клиент должен пройти проверку подлинности с RememberMe = trueНапример:

var client = JsonServiceClient(BaseUrl);
var authResponse = client.Send(new Authenticate
{
    provider = "credentials",
    UserName = "user",
    Password = "p@55word",
    RememberMe = true,
});

authResponse.PrintDump();

После аутентификации такой же аутентифицированный client Экземпляр может быть использован для доступа к защищенной записи несколько раз, как показано в этом Auth Test:

for (int i = 0; i < 500; i++)
{
    var response = client.Send<SecureResponse>(new Secured { Name = "test" });
    Console.WriteLine("loop : {0}", i);
}
Другие вопросы по тегам