简体   繁体   English

使用iPhone UIWebView时的Asp.Net Forms身份验证

[英]Asp.Net Forms Authentication when using iPhone UIWebView

I am writing a Asp.net MVC 2 application that uses Forms Authentication and currently I am having a problem with our iPhone application in regards to the authentication/login over the web. 我正在编写一个使用表单身份验证的Asp.net MVC 2应用程序,目前我的iPhone应用程序在通过Web进行身份验证/登录时遇到问题。 We have developed a simple iPhone app that uses the the UIWebView control. 我们开发了一个使用UIWebView控件的简单iPhone应用程序。 At this stage, all the app does is navigate to our Asp.Net website. 在这个阶段,所有应用程序都会导航到我们的Asp.Net网站。 Simple, right? 简单吧? The problem is, that the user cannot get past the login page. 问题是,用户无法通过登录页面。 The repro steps are: 重复步骤是:

  • Open iPhone app. 打开iPhone应用程序。
  • The app navigates to the home page. 该应用程序导航到主页。
  • the user is not authenticated, so they are redirected to the login screen/page 用户未经过身份验证,因此会重定向到登录屏幕/页面
  • The user enters the correct user name and password. 用户输入正确的用户名和密码。 clicks submit. 点击提交。
  • on the server side, the user is authenticated and a cookie is generated and sent to the client using FormsAuthentication.GetAuthCookie. 在服务器端,用户经过身份验证,并生成cookie并使用FormsAuthentication.GetAuthCookie发送到客户端。
  • The server sends are redirect to send the user to the correct home page. 服务器发送是重定向以将用户发送到正确的主页。

But the user is then redirected BACK to the login screen! 但是用户然后被重定向到BACK登录屏幕!

I've done some extensive debugging on this and what I do know is: 我已经对此进行了一些广泛的调试,我所知道的是:

The cookie is being sent to the client, and the client is storing the cookie. cookie被发送到客户端,客户端正在存储cookie。 Verified this in the iPhone debugger and also by using Javsascript to display cookie data on the page. 在iPhone调试器中验证了这一点,并使用Javsascript在页面上显示cookie数据。 The cookie is being sent back to the server. cookie正被发送回服务器。 Verified this in the Visual Studio debugger. 在Visual Studio调试器中验证了这一点。 It is the correct cookie (it's the same one that was set). 它是正确的cookie(它与设置的相同)。 The property User.Identity.IsAuthenticated returns false for some reason, even though the auth cookie is contained in the Request object. User.Identity.IsAuthenticated属性由于某种原因返回false,即使auth cookie包含在Request对象中。 I have verified that the iPhone app is setup to accept cookies, and they are on the client. 我已经确认iPhone应用程序已设置为接受cookie,并且它们在客户端上。

Here is the funny thing: It works fine if you open the Safari browser on the iPhone and go to our site directly. 这是有趣的事情:如果您在iPhone上打开Safari浏览器并直接访问我们的网站,它可以正常工作。

It has the same behaviour on the iPad too in that it doesn't get past the login screen. 它在iPad上也具有相同的行为,因为它没有通过登录屏幕。 This repros on the emulators, and on devices. 这种模拟器和设备上的重复。

This same web site has been tested with IE 7-8, Safari (for Windows), Blackberry, IEMobile 6.5, Phone 7 and it works find. 这个网站已经通过IE 7-8,Safari(适用于Windows),Blackberry,IEMobile 6.5,Phone 7进行了测试,可以找到它。 The only circumstance that it doesn't work on is the UIWebView in the iPhone app. 唯一不适用的情况是iPhone应用程序中的UIWebView。

I had exactly the same problem, but with another device (NokiaN8), and also traced the problem back to the User-Agent. 我有完全相同的问题,但使用另一台设备(NokiaN8),并将问题追溯到User-Agent。

IIS uses regular expressions to match against the User-Agent string. IIS使用正则表达式来匹配User-Agent字符串。 The root of the problem was that it didn't have any matching regular expressions for the specific device, and ended up in one of the lowest levels of match, where the Default properties were used. 该问题的根源在于它没有特定设备的任何匹配正则表达式,最终处于使用默认属性的最低匹配级别之一。 The default properties said that the browser didn't support cookies. 默认属性表示浏览器不支持cookie。

Solution: 解:

  1. Add a folder in your web project named App_Browsers (right-click the project, choose: Add > Add ASP.NET Folder > App_Browsers ). 在Web项目中添加名为App_Browsers的文件夹(右键单击该项目,选择: Add > Add ASP.NET Folder > App_Browsers )。
  2. Add a file in that folder (right-click, choose: Add > New Item ). 在该文件夹中添加文件(右键单击,选择: Add > New Item )。 The file can have any name, but must have the .browser ending. 该文件可以具有任何名称,但必须具有.browser结尾。
  3. Add a good matching expression and the correct capabilities (or add changes to the Default ). 添加一个良好的匹配表达式和正确的功能(或添加对Default更改)。

Two examples: 两个例子:

<browsers>
  <browser id="NokiaN8" parentID="Mozilla">
    <identification>
      <userAgent match="NokiaN8" />
    </identification>
    <capabilities>
      <capability name="browser" value="NokiaN8" />
      <capability name="cookies" value="true" /> 
    </capabilities> 
  </browser> 
</browsers>

Or change the default: 或者更改默认值:

<browsers>
  <browser refID="Default"> 
    <capabilities> 
      <capability name="cookies" value="true" /> 
    </capabilities>
  </browser>
</browsers>

More info: Browser Definition File Schema 更多信息: 浏览器定义文件架构

The solution we found was to create a file (generic.browser) and include this xml to tell the web server that "Mozilla" and the Default browser settings should all support cookies. 我们找到的解决方案是创建一个文件(generic.browser)并包含这个xml来告诉Web服务器“Mozilla”和默认浏览器设置都应该支持cookie。

<browser refID="Mozilla" >
    <capabilities>
        <capability name="cookies"  value="true" />
    </capabilities>
</browser>

这在ASP.NET 4.5中已得到修复,并且假定所有浏览器都支持cookie,因此不需要额外的.browser文件。

From the research I did, the reason why you can't set the User-Agent is that the UIWebView is setting the User-Agent value just before it sends out the request, that is, after you've made your request from your code. 根据我的研究,您无法设置User-Agent的原因是UIWebView在发出请求之前就设置了User-Agent值,也就是说,在您从代码发出请求之后。

The trick to get around this problem is to use something called "method swizzling", an advanced and potentially dangerous Objective-C concept that swaps out a standard method with one you provide. 解决这个问题的诀窍是使用一种称为“方法调配”的东西,这是一种先进的,具有潜在危险的Objective-C概念,可以用您提供的方法交换标准方法。 The end result is that when your request is sent out and the framework code adds the User-Agent it will be fooled into using the method you provided. 最终结果是,当您的请求被发出并且框架代码添加了User-Agent时,它将被使用您提供的方法欺骗。

The following explains what I did to implement this but I am no Objective-C expert and would suggest to you to do some research to familiarize yourself with the technique. 以下解释了我为实现这一点所做的工作,但我不是Objective-C专家,建议您做一些研究以熟悉该技术。 In particular, there was a link out there explaining better than me what is going on here, but at the moment I can't find it. 特别是,那里有一个链接比我更好地解释了这里发生了什么,但目前我找不到它。

1) Add a category on NSObject to allow swizzling. 1)在NSObject上添加一个类别以允许调配。

@interface NSObject (Swizzle)

+ (BOOL) swizzleMethod:(SEL)origSelector withMethod:(SEL)newSelector;

@end

@implementation NSObject (Swizzle)


+ (BOOL) swizzleMethod:(SEL) origSelector withMethod:(SEL)newSelector
{
    Method origMethod= class_getInstanceMethod(self, origSelector);
    Method newMethod= class_getInstanceMethod(self, newSelector);

    if (origMethod && newMethod)
    {
        if (class_addMethod(self, origSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
        {
            class_replaceMethod(self, newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
        }
        else {
            method_exchangeImplementations(origMethod, newMethod);
        }
        return YES;
    }
    return NO;
}
@end

2) Subclass NSMutableURLRequest to allow the swizzle: 2)子类NSMutableURLRequest允许swizzle:

@interface NSMutableURLRequest (MyMutableURLRequest)

+ (void) setupUserAgentOverwrite;

@end
@implementation NSMutableURLRequest (MyMutableURLRequest)

- (void) newSetValue:(NSString*)value forHTTPHeaderField:(NSString*)field
{
    if ([field isEqualToString:@"User-Agent"])
    {
        value = USER_AGENT;  // ie, the value I want to use.
    }
    [self newSetValue:value forHTTPHeaderField:field];
}
+ (void) setupUserAgentOverwrite
{
    [self swizzleMethod:@selector(setValue:forHTTPHeaderField:) 
             withMethod:@selector(newSetValue:forHTTPHeaderField:)];

}

@end

3) Call the static method to swap out the method. 3)调用静态方法来交换方法。 I made this call in didFinishLaunchingWithOptions: 我在didFinishLaunchingWithOptions中进行了这个调用:

// Need to call this method so that User-Agent get updated correctly:
[NSMutableURLRequest setupUserAgentOverwrite];

4) And then used it like this. 4)然后像这样使用它。 (The connection delegate saves the data in a mutable array and then manually sets the UIWebView using its loadData method when it finishes loading). (连接委托将数据保存在可变数组中,然后在完成加载时使用其loadData方法手动设置UIWebView)。

- (void)loadWithURLString:(NSString*)urlString
{
    NSURL *url = [NSURL URLWithString:urlString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
_connection = [NSURLConnection connectionWithRequest:request delegate:self];
[_connection start];
}

我有同样的问题,研究并整合了一个完整的解决方案(来自上面的答案和其他线程): http//www.bloggersworld.com/index.php/asp-net-forms-authentication-iphone-cookies/

The reason of this happening apparently has to do with the fact that if the user-agent is not known then the browser is assumed to not accept cookies (as others have answered), and instead IIS puts the ASPXAUTH value in the URL. 发生这种情况的原因显然与以下事实有关:如果用户代理未知,则假定浏览器不接受cookie(正如其他人已经回答的那样),而IIS将ASPXAUTH值放入URL中。

However the MVC routing system apparently missed that possibility, which is clearly a bug, and therefore it is getting messed up. 然而,MVC路由系统显然错过了这种可能性,这显然是一个错误,因此它变得混乱。

While adding the .browser with a custom user-agent solves the problem, it does not guarantee that other user-agents will also be solved, and in fact I have found the K9 Browser for the android also has this problem, and as such it is only a solution if one has a logging system such as elmeh to track down such errors. 虽然添加.browser与自定义用户代理解决了问题,但它并不能保证其他用户代理也会得到解决,事实上我发现Android的K9浏览器也有这个问题,因此它如果有一个像elmeh这样的记录系统来追踪这些错误,那么这只是一个解决方案。

On the other hand adding a default brings up the question if it is true that all browsers accept cookies, which is apparently the reason why IIS does not assume so. 另一方面,如果所有浏览器都接受cookie,那么添加默认值会提出问题,这显然是IIS不这样做的原因。

However besides adding explictly the user-agents one can add in the global.asax RegiterRoutes() method an explicit handler to ignore it, as follows: 然而,除了明确地添加用户代理之外,还可以在global.asax RegiterRoutes()方法中添加一个显式处理程序来忽略它,如下所示:

         routes.MapRoute(
            "CookieLess", // Route name
            "(F({Cookie}))/{controller}/{action}/{id}", // URL with parameters
            new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
        );

However in this case one will have to copy all route entries to match with the cookie-less situation, unless one is about to write a custom route handler. 但是在这种情况下,除非要编写自定义路由处理程序,否则必须复制所有路径条目以匹配无cookie情况。

Or we can use the above cookie-less route to send the user to an error page explaining that his browser is not supported on the moment, and send an alert to the web-master with the user-agent to handle it. 或者我们可以使用上面的无cookie路由将用户发送到错误页面,说明他的浏览器目前不受支持,并向用户代理发送警报给web-master来处理它。

  1. Have you specified a DestinationPageUrl in markup? 您是否在标记中指定了DestinationPageUrl?

  2. Have you specified the defaultURL in web.config? 您是否在web.config中指定了defaultURL?

Example web.config 示例web.config

<authentication mode="Forms">
     <forms loginUrl="~/Login.aspx" defaultUrl="~/CustomerArea/Default.aspx"/>
</authentication>

Example DestinationPageUrl 示例DestinationPageUrl

 <asp:Login ID="Login" runat="server" DestinationPageUrl="~/Secret/Default.aspx" />

Lastly have you looked in the cookie jar and seen if your session cookie actually exists? 最后你看过饼干罐,看看你的会话cookie是否真的存在?

Where are an UIWebView's cookies stored? UIWebView的cookie存储在哪里?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM