【问题标题】:Auth Service Saying Not Authenticated when using Permanent Sessions使用永久会话时,身份验证服务说未经过身份验证
【发布时间】:2014-11-12 22:39:26
【问题描述】:

使用临时会话时,它可以正常工作。登录 auth 服务并不带任何参数调用 /auth,它会显示显示名称、会话 ID 等。

当我使用 RememberMe=true 登录时,该调用会正确返回会话信息。但是在随后不带任何参数调用 /auth 时,ServiceStack 返回 401 not authenticated。会话对象的 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?username=user&password=passwordmberme=true

  • GET /api2/auth?username=user&password=password&rememberme=true HTTP/1.1
  • 接受:文本/html,应用程序/xhtml+xml,/
  • 接受语言:en-US
  • 用户代理:Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) 像 Gecko
  • 接受编码:gzip、放气
  • 主机:propel.zola360.com
  • DNT:1
  • 连接:保持活动状态
  • Cookie:ss-pid=P2hslABCmSs7pomRqNz5; ss-opt=烫发; X-UAId=

初始响应标头:

  • HTTP/1.1 200 正常
  • 缓存控制:私有
  • 内容类型:文本/html
  • 内容编码:gzip
  • 变化:接受编码
  • 服务器:Microsoft-IIS/7.5
  • X-Powered-By:ServiceStack/4.033 Win32NT/.NET
  • 访问控制允许来源:*
  • 访问控制允许方法:GET、POST、PUT、DELETE、OPTIONS
  • Access-Control-Allow-Headers:内容类型、授权、接受
  • 访问控制允许凭据:true
  • X-AspNet-版本:4.0.30319
  • 设置 Cookie:ss-id=pojZkNAdMcEcACDREcRM;域=.zola360.com;路径=/; HttpOnly
  • 设置 Cookie:ss-opt=perm;域=.zola360.com;过期=格林威治标准时间 2034 年 11 月 13 日星期一 16:11:09; - 路径=/; HttpOnly
  • 设置 Cookie:X-UAId=;域=.zola360.com;过期=格林威治标准时间 2034 年 11 月 13 日星期一 16:11:09;路径=/; HttpOnly
  • 设置 Cookie:47=0;域=.zola360.com;路径=/
  • 设置 Cookie:用户 ID=47;域=.zola360.com;路径=/
  • X-Powered-By:ASP.NET
  • 日期:2014 年 11 月 13 日星期四 16:11:09 GMT
  • 内容长度:4129

初始响应正文:

{"userId":"47","sessionId":"PKrITmRawxAtnaABCDgN","userName":"user","re​​sponseStatus":{},"meta":{"ExpiresMinutes":"360"}}

随后调用 /auth 请求:

  • GET /api2/auth HTTP/1.1
  • 接受:文本/html,应用程序/xhtml+xml,/
  • 接受语言:en-US
  • 用户代理:Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) 像 Gecko
  • 接受编码:gzip、放气
  • 主机:propel.zola360.com
  • DNT:1
  • 连接:保持活动状态
  • Cookie:ss-pid=cvgslABCmSs6pomYdLu0; ss-opt=烫发; X-UAId=; ss-id=lYWZkFAdMcZcABCDcRM; 47=0;用户 ID=47

随后调用 /auth 响应

  • HTTP/1.1 401 未通过身份验证
  • 缓存控制:私有
  • 内容类型:文本/html
  • 变化:接受
  • 服务器:Microsoft-IIS/7.5
  • X-Powered-By:ServiceStack/4.033 Win32NT/.NET
  • 访问控制允许来源:*
  • 访问控制允许方法:GET、POST、PUT、DELETE、OPTIONS
  • Access-Control-Allow-Headers:内容类型、授权、接受
  • 访问控制允许凭据:true
  • X-AspNet-版本:4.0.30319
  • X-Powered-By:ASP.NET
  • 日期:2014 年 11 月 13 日星期四 16:11:23 GMT
  • 内容长度:9731

对 /auth 正文的后续调用:

{"responseStatus":{"errorCode":"Not Authenticated","message":"Not Authenticated","stackTrace":"[Authenticate: 11/13/2014 3:27:49 PM]:\n [请求:{}]\nServiceStack.HttpError: 在 ServiceStack.Auth.AuthenticateService.Post(验证请求)处未经过身份验证\r\n\r\n 在 lambda_method(闭包、对象、对象)\r\n 在 ServiceStack.Host .ServiceRunner`1.Execute(IRequest request, Object instance, TRequest requestDto)","errors":[]}}

更新 我制作了一个小的 Python3 脚本来验证自己并调用其他一些 Web 服务。在使用 RememberMe=true 进行身份验证后,cookie 按预期返回:ss-id/pid 设置正确且 ss-opt=perm。我想我会打印标头 cookie,然后将其粘贴到另一个请求的标头中,以调用标有 [Authenticate] 的不同服务。它没有用。所以我尝试了一些愚蠢的方法并将 ss-pid cookie 值粘贴到 ss-id 中。它奏效了。

这是失败的 cookie 字符串(会话已编辑:)):

cookie = "ss-id=ss-ID-session-cookie; domain=.zola360.com; path=/; HttpOnly, ss-pid=ss-PID-session -cookie; domain=.zola360.com; expires=Tue, 14-Nov-2034 01:34:25 GMT; path=/; HttpOnly, ss-opt=perm; domain=.zola360.com; expires =2034 年 11 月 14 日星期二 01:34:25 GMT;路径=/;HttpOnly,X-UAId=;域=.zola360.com;过期=2034 年 11 月 14 日星期二 01:34:25 GMT;路径=/; HttpOnly, 47=0; domain=.zola360.com; path=/, UserId=47; domain=.zola360.com; path=/"

只需将 ss-pid 值粘贴到 ss-id 中即可:

cookie = "ss-id=ss-PID-session-cookie; domain=.zola360.com; path=/; HttpOnly, ss-pid=ss-PID-session -cookie; domain=.zola360.com; expires=Tue, 14-Nov-2034 01:34:25 GMT; path=/; HttpOnly, ss-opt=perm; domain=.zola360.com; expires =2034 年 11 月 14 日星期二 01:34:25 GMT;路径=/;HttpOnly,X-UAId=;域=.zola360.com;过期=2034 年 11 月 14 日星期二 01:34:25 GMT;路径=/; HttpOnly, 47=0; domain=.zola360.com; path=/, UserId=47; domain=.zola360.com; path=/"

还有我使用的 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-opt=perm,好像还是有东西看 ss-id 的值。

【问题讨论】:

  • 我们需要更多的上下文来尝试找出您的问题所在。您能否发布您的 AuthFeature 注册代码和原始 HTTP 请求/响应正文以及有问题的后续请求? (您可以使用 WebInspector 或 Fiddler 获取原始 HTTP 标头)。
  • 我已经添加了一个答案,问题是 HTTP 客户端没有重新发送它通过身份验证的 ss-pid cookie,但不清楚为什么会这样,也许它不喜欢域 cookie ?.

标签: servicestack servicestack-auth


【解决方案1】:

当使用 GET /api2/auth?username=user&amp;password=... 进行身份验证时,它会与您的永久 cookie ss-pid 一起发送,即:

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

rememberme=true 选项告诉 ServiceStack 针对永久的ss-pid cookie 维护用户会话。此选项在用户 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(即P2hslABCmSs7pomRqNz5cvgslABCmSs6pomYdLu0):

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

哪个 ServiceStack 不知道(即不维护任何会话),这就是它按预期返回 401 Not Authenticated 的原因。

HTTP 客户端应配置为重新发送 Cookies

不清楚您使用的是什么 HTTP 客户端,但应将其配置为重新发送 cookie,这通常是默认行为。 Ajax 将只为该浏览器会话发送永久的ss-pid cookie 和临时的ss-id,例如当浏览器关闭时,临时的ss-id cookie 将被丢弃,发出新请求将收到一个新的ss-id cookie。

使用C# Service Clients,它只会重新发送永久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 实例可用于多次访问受保护的记录as seen in this Auth Test

for (int i = 0; i < 500; i++)
{
    var response = client.Send<SecureResponse>(new Secured { Name = "test" });
    Console.WriteLine("loop : {0}", i);
}

【讨论】:

  • 你说得对,cookie 没有被重新发送。但最终我遇到了另一个问题。我注意到我的永久会话就像临时会话一样过期,这不是我想要的。因此,作为一种解决方法,我在我的 CustomCredentialsProvider.OnAuthenticated 中为 req.dto.rememberme == true 放置了一些代码,如果为 true,则点击 redis 并重置密钥而不会过期。这会导致问题,因为 ServiceStack 可能会感到困惑。所以我只是将代码移出到 TypedResponse 过滤器中,它就像一个魅力。
  • @mythz 我将 ServiceStack.Client C# 4.0.50 与 .NET 4.6 一起使用,似乎不再需要设置 RememberMe = true。即使RememberMe = false 也会重新发送临时 ss-id cookie,就像在浏览器中一样。我不确定这是否是由于 .NET 或 SS 的变化。
  • @georgehemmings 必须在 .NET 中更改,HttpWebRequest 以前不会保留临时 cookie。
猜你喜欢
  • 1970-01-01
  • 2019-09-25
  • 1970-01-01
  • 2011-11-11
  • 1970-01-01
  • 2016-04-24
  • 1970-01-01
  • 2021-05-26
  • 1970-01-01
相关资源
最近更新 更多