You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI使用URLSession登录ASP.NET表单遇ViewState匹配难题求助

解决ASP.NET登录时ViewState不匹配导致重定向的问题

核心问题分析与修复步骤

1. 复用URLSession保持会话Cookie一致性

你当前代码中,GET请求和POST请求分别创建了独立的URLSession,这会导致两次请求的会话Cookie不共享。ASP.NET的ViewState与会话Cookie绑定,必须确保同一会话内的请求共享Cookie。

修复:
创建全局的URLSession实例,在GET和POST请求中复用它,保证会话一致性。

2. 动态提取__VIEWSTATEGENERATOR而非硬编码

代码中硬写__VIEWSTATEGENERATOR的值为521EEB84,但该值与ViewState一一对应,每次GET请求可能变化,必须从登录页HTML中动态提取。同时部分ASP.NET页面还需要__EVENTVALIDATION字段,也需一并提取。

修复:

let viewState = try doc.select("input[name=__VIEWSTATE]").val()
let viewStateGenerator = try doc.select("input[name=__VIEWSTATEGENERATOR]").val()
let eventValidation = try doc.select("input[name=__EVENTVALIDATION]").val()

3. 修正POST请求的Cookie设置位置

代码中错误地将Cookie设置给了GET请求的request对象,而非POST请求的postRequest,导致POST请求未携带会话Cookie,ASP.NET判定为新会话,ViewState自然不匹配。

修复:
将Cookie设置到postRequest的请求头中:

if let cookies = HTTPCookieStorage.shared.cookies(for: postUrl) {
    let headers = HTTPCookie.requestHeaderFields(with: cookies)
    postRequest.allHTTPHeaderFields = headers
}

4. 优化表单数据编码方式

使用URLComponents.query生成POST请求体可能存在编码问题,ASP.NET要求表单数据严格遵循application/x-www-form-urlencoded格式,手动构建表单字符串更可靠。

修复:

let postData = [
    "__LASTFOCUS": "",
    "__VIEWSTATE": viewState ?? "",
    "__VIEWSTATEGENERATOR": viewStateGenerator ?? "",
    "__EVENTVALIDATION": eventValidation ?? "",
    "__EVENTTARGET": "",
    "__EVENTARGUMENT": "",
    "_login:ctrlUserName": "exampleUsername",
    "_login:ctrlPassword": "examplePassword",
    "_login:btnLogin": "Login"
]

let postString = postData.compactMap { key, value in
    guard let encodedValue = value.addingPercentEncoding(withAllowedCharacters: .urlFormAllowed) else { return nil }
    return "\(key)=\(encodedValue)"
}.joined(separator: "&")

postRequest.httpBody = postString.data(using: .utf8)

5. 检查重定向与登录结果

登录成功后ASP.NET通常会返回302重定向,可通过响应状态码和Location头判断是否跳转到目标页面,而非登录页。

修正后的完整代码示例

// 创建全局URLSession实例,保持会话一致性
let sharedSession = URLSession(configuration: {
    let config = URLSessionConfiguration.default
    config.timeoutIntervalForRequest = 30
    config.httpShouldSetCookies = true
    config.httpCookieAcceptPolicy = .always
    return config
}())

func loginToASPNet() {
    guard let url = URL.loginURL else {
        print("Invalid URL")
        return
    }
    
    var request = URLRequest(url: url)
    request.setValue("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", forHTTPHeaderField: "Accept")
    request.setValue("gzip, deflate, br", forHTTPHeaderField: "Accept-Encoding")
    request.setValue("en-GB,en-US;q=0.9,en;q=0.8", forHTTPHeaderField: "Accept-Language")
    request.setValue("keep-alive", forHTTPHeaderField: "Connection")
    request.setValue("Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1", forHTTPHeaderField: "User-Agent")
    
    // 应用已有Cookie
    if let cookies = HTTPCookieStorage.shared.cookies(for: url) {
        let headers = HTTPCookie.requestHeaderFields(with: cookies)
        request.allHTTPHeaderFields = headers
        print(headers)
    }
    
    sharedSession.dataTask(with: request) { data, response, error in
        if let error = error {
            print(error.localizedDescription)
            return
        }
        
        guard let httpResponse = response as? HTTPURLResponse else {
            print("Invalid Response")
            return
        }
        
        guard let data = data, let htmlString = String(data: data, encoding: .utf8) else {
            print("No data received.")
            return
        }
        
        do {
            let doc = try SwiftSoup.parse(htmlString)
            // 提取所有ASP.NET必要字段
            let viewState = try doc.select("input[name=__VIEWSTATE]").val()
            let viewStateGenerator = try doc.select("input[name=__VIEWSTATEGENERATOR]").val()
            let eventValidation = try doc.select("input[name=__EVENTVALIDATION]").val()
            
            guard let postUrl = URL.loginURL else {
                print("Invalid URL")
                return
            }
            
            var postRequest = URLRequest(url: postUrl)
            postRequest.httpMethod = "POST"
            postRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
            postRequest.setValue("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", forHTTPHeaderField: "Accept")
            postRequest.setValue("gzip, deflate, br", forHTTPHeaderField: "Accept-Encoding")
            postRequest.setValue("en-GB,en-US;q=0.9,en;q=0.8", forHTTPHeaderField: "Accept-Language")
            postRequest.setValue("keep-alive", forHTTPHeaderField: "Connection")
            postRequest.setValue("Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1", forHTTPHeaderField: "User-Agent")
            
            // 传递会话Cookie到POST请求
            if let cookies = HTTPCookieStorage.shared.cookies(for: postUrl) {
                let headers = HTTPCookie.requestHeaderFields(with: cookies)
                postRequest.allHTTPHeaderFields = headers
                print(headers)
            }
            
            // 构建正确的表单数据
            let postData = [
                "__LASTFOCUS": "",
                "__VIEWSTATE": viewState ?? "",
                "__VIEWSTATEGENERATOR": viewStateGenerator ?? "",
                "__EVENTVALIDATION": eventValidation ?? "",
                "__EVENTTARGET": "",
                "__EVENTARGUMENT": "",
                "_login:ctrlUserName": "exampleUsername",
                "_login:ctrlPassword": "examplePassword",
                "_login:btnLogin": "Login"
            ]
            
            let postString = postData.compactMap { key, value in
                guard let encodedValue = value.addingPercentEncoding(withAllowedCharacters: .urlFormAllowed) else { return nil }
                return "\(key)=\(encodedValue)"
            }.joined(separator: "&")
            
            postRequest.httpBody = postString.data(using: .utf8)
            
            sharedSession.dataTask(with: postRequest) { data, response, error in
                if let error = error {
                    print("Error: \(error.localizedDescription)")
                    return
                }
                
                guard let httpResponse = response as? HTTPURLResponse else {
                    print("Invalid POST Response")
                    return
                }
                
                print("DEBUG LOGIN STATUS CODE \(httpResponse.statusCode)")
                // 检查重定向地址
                if let location = httpResponse.allHeaderFields["Location"] as? String {
                    print("Redirect to: \(location)")
                }
                
                guard let data = data, let htmlString = String(data: data, encoding: .utf8) else {
                    print("No POST data received.")
                    return
                }
                
                // 判断是否登录成功
                if htmlString.contains("_login:ctrlUserName") {
                    print("Login failed - redirected back to login page")
                } else {
                    print("Login successful")
                    // 处理后续业务逻辑
                }
                
            }.resume()
            
        } catch {
            print("Parse HTML error: \(error)")
        }
    }.resume()
}

额外排查建议

  • 对比Chrome开发者工具中的请求头与代码请求头,确保User-Agent等关键字段一致(部分ASP.NET网站会校验该字段)
  • 开启URLSession日志,查看实际发送的请求参数和响应详情,确认ViewState、Cookie是否正确传递
  • 检查页面是否存在__VIEWSTATEENCRYPTED等其他隐藏字段,若有需一并提取并加入POST参数

内容的提问来源于stack exchange,提问作者Sander

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 18:35:54