使用永久会话时,身份验证服务说不经过身份验证

时间:2014-11-12 22:39:27

标签: servicestack servicestack-auth

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

当我使用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&amp; password = password&amp; rememberme = true HTTP / 1.1
  • 接受:text / html,application / xhtml + xml, /
  • 接受语言:en-US
  • 用户代理:Mozilla / 5.0(Windows NT 6.1; WOW64; Trident / 7.0; rv:11.0),如Gecko
  • 接受编码:gzip,deflate
  • 主持人:propel.zola360.com
  • DNT:1
  • 连接:Keep-Alive
  • Cookie:ss-pid = P2hslABCmSs7pomRqNz5; SS-OPT =烫发; X-UAId =

初始回复标题:

  • HTTP / 1.1 200确定
  • Cache-Control:private
  • 内容类型:text / html
  • 内容编码:gzip
  • 变化:接受编码
  • 服务器:Microsoft-IIS / 7.5
  • X-Powered-By:ServiceStack / 4.033 Win32NT / .NET
  • Access-Control-Allow-Origin:*
  • 访问控制允许方法:GET,POST,PUT,DELETE,OPTIONS
  • Access-Control-Allow-Headers:Content-Type,Authorization,Accept
  • Access-Control-Allow-Credentials:true
  • X-AspNet-Version:4.0.30319
  • Set-Cookie:ss-id = pojZkNAdMcEcACDREcRM;域= .zola360.com;路径= /;仅Http
  • Set-Cookie:ss-opt = perm;域= .zola360.com; expires = Mon,13-Nov-2034 16:11:09 GMT; - path = /;仅Http
  • Set-Cookie:X-UAId =;域= .zola360.com; expires = Mon,13-Nov-2034 16:11:09 GMT;路径= /;仅Http
  • Set-Cookie:47 = 0;域= .zola360.com;路径= /
  • Set-Cookie:UserId = 47;域= .zola360.com;路径= /
  • X-Powered-By:ASP.NET
  • 日期:2014年11月13日星期四16:11:09 GMT
  • 内容长度:4129

初步回复正文:

{&#34;用户id&#34;:&#34; 47&#34;&#34;的sessionId&#34;:&#34; PKrITmRawxAtnaABCDgN&#34;&#34;用户名&#34;:& #34;使用者&#34;&#34; responseStatus&#34;:{},&#34;元&#34; {&#34; ExpiresMinutes&#34;:&#34; 360&#34;}} < / p>

后续调用/ auth请求:

  • GET / api2 / auth HTTP / 1.1
  • 接受:text / html,application / xhtml + xml, /
  • 接受语言:en-US
  • 用户代理:Mozilla / 5.0(Windows NT 6.1; WOW64; Trident / 7.0; rv:11.0),如Gecko
  • 接受编码:gzip,deflate
  • 主持人:propel.zola360.com
  • DNT:1
  • 连接:Keep-Alive
  • Cookie:ss-pid = cvgslABCmSs6pomYdLu0; SS-OPT =烫发; X-UAId =; SS-ID = lYWZkFAdMcZcABCDcRM; 47 = 0;用户ID = 47

后续调用/ auth响应

  • HTTP / 1.1 401未经过身份验证
  • Cache-Control:private
  • 内容类型:text / html
  • 不同:接受
  • 服务器:Microsoft-IIS / 7.5
  • X-Powered-By:ServiceStack / 4.033 Win32NT / .NET
  • Access-Control-Allow-Origin:*
  • 访问控制允许方法:GET,POST,PUT,DELETE,OPTIONS
  • Access-Control-Allow-Headers:Content-Type,Authorization,Accept
  • Access-Control-Allow-Credentials:true
  • X-AspNet-Version:4.0.30319
  • X-Powered-By:ASP.NET
  • 日期:2014年11月13日星期四16:11:23 GMT
  • 内容长度:9731

后续调用/ auth正文:

{&#34; responseStatus&#34;:{&#34; errorCode&#34;:&#34;未经过身份验证&#34;,&#34;消息&#34;:&#34;未经过身份验证&#34 ;,&#34; stackTrace&#34;:&#34; [验证时间:2014年11月13日下午3:27:49]:\ n [请求:{}] \ n服务支持.HttpError:未经过身份验证\ r \ n at ServiceStack.Auth.AuthenticateService.Post(验证请求)\ r \ n在lambda_method(Closure,Object,Object)\ r \ n在ServiceStack.Host.ServiceRunner`1.Execute(IRequest请求,对象实例,TRequest requestDto)& #34;&#34;错误&#34;:[]}}

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

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

cookie =&#34; ss-id = ss-ID-session-cookie ;域= .zola360.com;路径= /; HttpOnly, ss-pid = ss-PID-session-cookie ;域= .zola360.com; expires = Tue,14-Nov-2034 01:34:25 GMT;路径= /; HttpOnly,ss-opt = perm;域= .zola360.com; expires = Tue,14-Nov-2034 01:34:25 GMT;路径= /; HttpOnly,X-UAId =;域= .zola360.com; expires = Tue,14-Nov-2034 01:34:25 GMT;路径= /; HttpOnly,47 = 0;域= .zola360.com; path = /,UserId = 47;域= .zola360.com;路径= /&#34;

简单地将ss-pid值粘贴到ss-id中就可以了:

cookie =&#34; ss-id = ss-PID-session-cookie ;域= .zola360.com;路径= /; HttpOnly, ss-pid = ss-PID-session-cookie ;域= .zola360.com; expires = Tue,14-Nov-2034 01:34:25 GMT;路径= /; HttpOnly,ss-opt = perm;域= .zola360.com; expires = Tue,14-Nov-2034 01:34:25 GMT;路径= /; HttpOnly,X-UAId =;域= .zola360.com; expires = Tue,14-Nov-2034 01:34:25 GMT;路径= /; HttpOnly,47 = 0;域= .zola360.com; path = /,UserId = 47;域= .zola360.com;路径= /&#34;

我使用的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的值。

1 个答案:

答案 0 :(得分:0)

使用GET /api2/auth?username=user&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

虽然在这种情况下不重要,但由于Request中缺少临时会话ss-id,ServiceStack会告诉客户端添加一个新的:{/ p>

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

问题在于GET /api2/auth的后续请求,其中客户端未重新发送最初通过身份验证的ss-pid Cookie(即P2hslABCmSs7pomRqNz5 vs cvgslABCmSs6pomYdLu0):

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);
}