Node.js 模拟 CAS 登录:Axios + Cheerio 自动获取智慧江财 3 个关键参数
·
Node.js 模拟 CAS 登录:Axios + Cheerio 自动获取智慧江财 3 个关键参数
在校园信息化系统中,统一身份认证(CAS)是常见的登录方式。对于开发者而言,掌握如何通过代码模拟这一流程,不仅能提升工作效率,还能为后续自动化操作奠定基础。本文将深入探讨如何利用 Node.js 生态中的 Axios 和 Cheerio 库,完整实现智慧江财系统的登录流程,重点解决动态参数获取、会话维持等核心问题。
1. 准备工作与环境搭建
在开始编码之前,我们需要确保开发环境配置正确。以下是必要的工具和库:
- Node.js :建议使用最新的 LTS 版本(如 16.x 或更高)
- npm/yarn :用于管理项目依赖
- TypeScript (可选):提供更好的类型检查和代码提示
创建项目并安装依赖:
mkdir cas-login-demo && cd cas-login-demo
npm init -y
npm install axios cheerio qs @types/node --save
对于使用 TypeScript 的开发者,还需要额外配置:
npm install typescript ts-node @types/axios @types/cheerio --save-dev
2. 登录流程分析与关键参数识别
智慧江财的 CAS 登录流程主要包含以下几个关键步骤:
- 初始请求 :获取登录页面,从中提取动态参数
- 参数加密 :对密码进行 RSA 加密处理
- 提交登录 :携带所有必要参数发起 POST 请求
- 会话维持 :处理服务器返回的 Cookie 和重定向
其中,三个核心动态参数需要特别关注:
| 参数名称 | 获取方式 | 作用 |
|---|---|---|
| lt | 从登录页面 HTML 中提取 | 防止 CSRF 攻击的一次性令牌 |
| JSESSIONID | 从响应头 Set-Cookie 中获取 | 会话标识符 |
| sessionMapKey | 从响应头 Set-Cookie 中获取 | 会话映射键 |
3. 核心代码实现
3.1 初始化请求与参数获取
首先创建一个类来封装登录逻辑:
import axios from 'axios';
import cheerio from 'cheerio';
import qs from 'qs';
class CasLogin {
private readonly loginUrl = 'https://ssl.jxufe.edu.cn/cas/login';
private readonly rsaPublicKey = {
n: '5598e3b75d21a2989274e222fa59ab07d829faa29b544e3a920c4dd287aed9302a657280c23220a35ae985ba157400e0502ce8e44570a1513bf7146f372e9c842115fb1b86def80e2ecf9f8e7a586656d12b27529f487e55052e5c31d0836b2e8c01c011bca911d983b1541f20b7466c325b4e30b4a79652470e88135113c9d9',
e: '10001'
};
constructor(private username: string, private password: string) {}
async startLogin() {
try {
// 第一步:获取登录页面并提取关键参数
const { lt, JSESSIONID, sessionMapKey } = await this.fetchLoginParams();
// 第二步:加密密码
const encryptedPassword = this.rsaEncrypt(this.password);
// 第三步:提交登录
await this.submitLogin(lt, JSESSIONID, sessionMapKey, encryptedPassword);
console.log('登录成功!');
} catch (error) {
console.error('登录失败:', error.message);
}
}
private async fetchLoginParams() {
const response = await axios.get(this.loginUrl, {
headers: { 'User-Agent': 'Mozilla/5.0' }
});
// 从响应头获取 Cookie
const cookies = response.headers['set-cookie'];
const JSESSIONID = cookies[0].match(/JSESSIONID=([^;]+)/)[1];
const sessionMapKey = cookies[1].match(/sessoinMapKey=([^;]+)/)[1];
// 从 HTML 中提取 lt 参数
const $ = cheerio.load(response.data);
const lt = $('input[name="lt"]').val() as string;
return { lt, JSESSIONID, sessionMapKey };
}
}
3.2 RSA 加密实现
密码加密是登录过程中的关键环节。我们需要实现 RSA 加密功能:
// 在 CasLogin 类中添加以下方法
private rsaEncrypt(plainText: string): string {
// 这里简化了实现,实际项目中应使用完整的 RSA 库
const { RSAKey } = require('./rsa');
const rsa = new RSAKey();
rsa.setPublic(this.rsaPublicKey.n, this.rsaPublicKey.e);
return rsa.encrypt(plainText);
}
注意:RSA 加密模块需要单独实现或从源代码中提取。确保公钥参数(n,e)与目标系统一致,这些参数可能会定期更新。
3.3 登录请求提交
获取所有必要参数后,我们可以构造登录请求:
// 在 CasLogin 类中添加以下方法
private async submitLogin(
lt: string,
JSESSIONID: string,
sessionMapKey: string,
encryptedPassword: string
) {
const formData = qs.stringify({
username: this.username,
password: encryptedPassword,
lt,
_eventId: 'submit',
cryptoType: '1',
_rememberMe: 'on',
errors: '0',
imageCodeName: ''
});
const response = await axios.post(this.loginUrl, formData, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': `JSESSIONID=${JSESSIONID}; sessoinMapKey=${sessionMapKey}`,
'User-Agent': 'Mozilla/5.0',
'Referer': this.loginUrl
},
maxRedirects: 0,
validateStatus: status => status >= 200 && status < 400
});
// 处理可能的302重定向
if (response.status === 302) {
const redirectUrl = response.headers.location;
console.log('需要重定向到:', redirectUrl);
// 这里可以继续处理重定向逻辑
}
}
4. 错误处理与健壮性优化
在实际应用中,我们需要考虑各种异常情况:
- 网络请求失败 :添加重试机制
- 参数获取失败 :增加校验逻辑
- 登录状态验证 :检查返回内容确认是否登录成功
改进后的错误处理示例:
// 修改 startLogin 方法
async startLogin(maxRetry = 3) {
let attempt = 0;
while (attempt < maxRetry) {
try {
// ...原有登录逻辑...
return; // 登录成功则直接返回
} catch (error) {
attempt++;
if (attempt >= maxRetry) {
throw new Error(`登录失败,已重试 ${maxRetry} 次: ${error.message}`);
}
// 指数退避重试
await new Promise(resolve => setTimeout(resolve, 1000 * 2 ** attempt));
}
}
}
5. 会话维持与后续操作
成功登录后,通常需要维持会话以进行后续操作。以下是几种常见的会话处理策略:
- Cookie 持久化 :将获取的 Cookie 保存起来供后续请求使用
- 请求拦截器 :使用 Axios 的拦截器自动处理会话
- 会话存储 :将会话信息存储在数据库或缓存中
示例实现:
class SessionManager {
private cookies: Record<string, string> = {};
updateFromResponse(response: AxiosResponse) {
const setCookie = response.headers['set-cookie'];
if (setCookie) {
setCookie.forEach(cookie => {
const [keyValue] = cookie.split(';');
const [key, value] = keyValue.split('=');
this.cookies[key] = value;
});
}
}
getCookieHeader() {
return Object.entries(this.cookies)
.map(([key, value]) => `${key}=${value}`)
.join('; ');
}
}
// 使用示例
const session = new SessionManager();
// 在登录成功后调用
session.updateFromResponse(loginResponse);
// 后续请求中添加 Cookie
axios.get('https://ssl.jxufe.edu.cn/some/protected/resource', {
headers: {
Cookie: session.getCookieHeader()
}
});
6. 完整流程图与工程化建议
为了更清晰地理解整个流程,以下是登录过程的序列图表示:
用户/客户端 -> Node.js 应用: 发起登录请求
Node.js 应用 -> CAS 服务器: GET /cas/login
CAS 服务器 --> Node.js 应用: 返回登录页面(含lt)
Node.js 应用 -> Node.js 应用: 提取lt, JSESSIONID, sessionMapKey
Node.js 应用 -> Node.js 应用: RSA加密密码
Node.js 应用 -> CAS 服务器: POST /cas/login (带所有参数)
CAS 服务器 --> Node.js 应用: 返回302重定向或成功响应
Node.js 应用 -> 用户/客户端: 返回登录结果
工程化建议:
- 配置管理 :将公钥参数、URL 等提取到配置文件中
- 日志记录 :添加详细的日志记录帮助调试
- 单元测试 :为关键功能编写测试用例
- 定时更新 :定期检查 RSA 公钥是否变更
// 配置示例 (config.ts)
export default {
cas: {
loginUrl: 'https://ssl.jxufe.edu.cn/cas/login',
rsaPublicKey: {
n: '5598e3b75d21a2989274e222fa59ab07d829faa29b544e3a920c4dd287aed9302a657280c23220a35ae985ba157400e0502ce8e44570a1513bf7146f372e9c842115fb1b86def80e2ecf9f8e7a586656d12b27529f487e55052e5c31d0836b2e8c01c011bca911d983b1541f20b7466c325b4e30b4a79652470e88135113c9d9',
e: '10001'
}
},
retry: {
maxAttempts: 3,
delayMs: 1000
}
};
在实际项目中,我曾遇到因 RSA 公钥变更导致登录失败的情况。后来通过添加自动检测机制,在登录失败时尝试重新获取公钥参数,显著提高了系统的稳定性。另一个常见问题是会话过期,解决方案是实现自动重新登录逻辑,当检测到会话无效时透明地重新认证。
更多推荐



所有评论(0)