Skip to content
ChangYo edited this page Oct 26, 2026 · 3 revisions

jAccount 与校园会话

项 值
平台 jaccount.sjtu.edu.cn、*.sjtu.edu.cn
认证 OAuth 2.0 / OIDC;校园 Web 会话 Cookie;站点令牌;站点 API Key
数据方向 身份、授权;会话型接口的读写取决于目标站点
难度 OAuth:高(需单位申请);校园会话/站点令牌:中(需人工登录)
最后验证 2026-10-26
证据等级 A(官方文档 + Dela 实测)

本页官方结论统一于 2026-10-25 核对;站点令牌与站点 API Key 部分于 2026-10-26 补充。下面不再逐条重复日期。

交大的登录方式主要有四类:

  • OAuth 2.0 / OIDC:面向正式应用。需要 clientId 和 clientSecret,由单位或教职工申请。
  • 校园 Web 会话:面向个人工具。用户在受控窗口中登录一次,后续请求带目标站点的 Cookie。Dela 的课表和课堂视频使用这种方式。
  • 站点令牌:用户登录后,站点前端签发一个 token,后续请求放在 Auth 或 Authorization: Bearer 头里。第二课堂、传承·交大使用这种方式;它比 Cookie 更适合桌面客户端,但仍是本人会话。
  • 站点 API Key:水源、选课社区。用户在站点里生成 Key,数据面带 User-Api-Key 或 Authorization: Bearer,不通过 Cookie 直调。详见水源社区、选课社区。

选择方式:

场景 方式 需要什么
正式网站或应用,需要用户授权 OAuth 2.0 / OIDC clientId、clientSecret、scope、回调地址
个人工具,读取课表或录像 校园 Web 会话 一个持久 Session、一个登录窗口、探活接口
水源阅读器或搜索 User API Key(授权窗登录后绑定) 站点书面许可和 read key
查课程评价、给分 站点 API Key 选课社区账号和 Key
查往年资料 站点令牌 jAccount 登录后读取站点 token
查校园活动 站点令牌 jAccount 登录后读取站点 token
读取本人邮件 标准协议 邮箱地址和密码
实验室或社团应用,需要校园数据 OAuth + Open API 单位挂靠、scope 审批

一、jAccount OAuth 2.0 / OIDC

端点

用途 地址
授权 https://jaccount.sjtu.edu.cn/oauth2/authorize
取令牌 https://jaccount.sjtu.edu.cn/oauth2/token
登出 https://jaccount.sjtu.edu.cn/oauth2/logout?client_id=...&post_logout_redirect_uri=...&state=...
OIDC Issuer https://jaccount.sjtu.edu.cn/oauth2/

授权码流程

  1. 把用户导向授权地址:
GET https://jaccount.sjtu.edu.cn/oauth2/authorize
  ?response_type=code
  &scope=openid
  &client_id=<clientId>
  &redirect_uri=<登记过的回调地址>
  &state=<随机串>
  1. 用户在 jAccount 完成登录/授权,回调带上 code 和 state。
  2. 后端用 code 换 Token:
curl -X POST https://jaccount.sjtu.edu.cn/oauth2/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=authorization_code' \
  -d 'code=<code>' \
  -d 'redirect_uri=<同一个回调地址>' \
  -d 'client_id=<clientId>' \
  -d 'client_secret=<clientSecret>'
  1. 返回:
{
  "access_token": "...",
  "refresh_token": "...",
  "id_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 1800
}

关键事实:

  • 授权码有效期 1 分钟。[官方]
  • Access Token 有效期 1800 秒(30 分钟)。[官方]
  • 有 refresh_token,过期用 grant_type=refresh_token 刷新。
  • client_id/client_secret 也可以放 Authorization: Basic 头,官方说优先使用头。[官方]
  • 回调地址必须和申请时登记的一致;OAuth 服务器会检查应用网址。[官方]

ID Token

id_token 是 JWT,签名用 client secret 验证。主要 claims:

claim 含义
iss https://jaccount.sjtu.edu.cn/oauth2/
sub 当前登录用户的 jAccount 账号
aud 你的 client_id
nonce 授权请求里传的 nonce,必须核对
exp / iat 过期/签发时间,Unix 秒
name 姓名
code 默认身份的学/工号
type 身份类型:student、faculty、alumni 等

身份类型完整值域见官方 OIDC 文档附录。[官方]

scope

scope 有两种写法:名字(basic essential)或位值相加(3)。多个 scope 用空格分隔。[官方]

常用 scope:

scope 位值 能读什么 支持的授权方式
basic 1 用户 id、jAccount 账号名、姓名 A / C
essential 2 加默认身份:学工号、身份类型、院系 A / C
profile 4 加手机、邮箱、性别、班级等 A / C
privacy 256 加证件号、生日、照片等 A
lessons 1<<34 课程信息 A
classes 1<<35 课表(含课程) A
exams 1<<36 考试信息 A
scores 1<<37 考试成绩 A
read_mails 1<<26 读邮件 A
send_mail 1<<27 发邮件、改阅读状态 A / P
card_info 1<<39 一卡通信息 A / C
card_transactions 1<<40 一卡通交易 A
tasks 1<<5 待办、在办、已办 A / C
notifications 1<<7 系统通知 A
exchange_data 1<<12 数据交换 C
calendar 1<<52 日程同步 A / C
bus 1<<51 校车时刻与实时信息 C
storage 1<<29 应用非结构化存储 C / P

Note

完整 scope 表以官方 OAuth 文档为准。上表只是常用子集;申请前逐个核对「支持的授权方式」,不要假设某个 scope 一定支持授权码模式。字段级可见性以 Profile API 为准,同一个 scope 在不同接口返回的字段可能不同。

登出只用当前应用:官方示例给的是 GET http://jaccount.sjtu.edu.cn/oauth2/logout,实际用 https;client_id、post_logout_redirect_uri 必填,state 可选回传。[官方]

申请现实

官方流程(2026-10-25 核对):

  1. 登录 my.sjtu.edu.cn 或交我办。
  2. 服务大厅 → 信息服务 → 其他 → jAccount 接口申请。
  3. 填应用名称、用途、部署地址、回调地址、需要的 scope。
  4. 提交给部门领导审核,通过后网络信息中心处理,约 3 个工作日。
  5. 取得 clientId 和 48 字符的 clientSecret。
  6. 增加 scope 发邮件给 gwang@sjtu.edu.cn,附 appId 和新增 scope 代码。[官方]

公开页面的服务对象写的是部门、教职工。学生个人通常拿不到;需要老师、实验室、社团或院系挂靠。不要用别人的 clientId/secret,也不要把它写进前端或公开仓库。

Note

Dela 自身没有申请 jAccount clientId;它用的是个人 Token(Canvas、邮箱)和校园 Web 会话(课表、课堂视频)。不要把「Dela 能用」理解成「Dela 有 jAccount clientId」。

Dela/docs/DEVELOPMENT.md 里「client_id 已停止申请,不存在取得途径」的说法与 2026-10-25 官方页面不一致;以官方页面为准。学生个人确实很难拿到,但「部门/教职工仍可申请」是公开事实。

错误与二次验证

  • 鉴权失败常见 HTTP 403,body 形如 {"errno":10002,"error":"认证失败","total":0}。[官方]
  • 令牌过期错误码 10007;scope 不足 10010。[官方]
  • jAccount 支持 TOTP 二次验证:交我办生成的 6 位、30 秒动态码。高风险写操作可以用它做 step-up。[官方]
  • 登出只登出当前应用,不会把其他已 SSO 的应用一起登出。[官方]

二、校园 Web 会话

为什么需要

i.sjtu.edu.cn、v.sjtu.edu.cn、水源和 Pan 的登录入口都是 jAccount SSO,但这些站点不是你的 OAuth 应用。你不需要 clientId,也不能用 OAuth Token 调用它们。做法是:

让真实用户在独立、持久的浏览器 Session 中完成一次 jAccount 登录,再带目标站点的 Cookie 调用其 JSON 接口。

这与「用户自己打开浏览器登录,然后复制 Cookie 给你」相同,只是把登录窗口放进了应用,凭据不经过剪贴板。

Cookie 模型

站点 关键 Cookie 说明
jAccount / 校园域 JAAuthCookie 等 社区实践里最常见的是 JAAuthCookie;名字和 Domain 以浏览器 Cookie 列表为准,不要只抓一个名字
教学信息服务网 站点会话 Cookie 登录后种下;请求课表时复用
课堂视频 站点会话 Cookie v.sjtu.edu.cn 域;录播数据只需要它
水源 _forum_session、_t 仅用于授权窗/媒体;数据面推荐 User API Key,见水源社区
Pan UserToken 等 私有 Web API 使用;无公开契约

Cookie 的 Domain 决定它能被哪些子域带上。校园 SSO 的 Cookie 可能覆盖多个子域(以实际 Cookie 为准),但每个站点仍会种自己的会话 Cookie。[推断]

Warning

水源是特例:数据面使用 User API Key,不通过 Cookie 直调。水源协议要求非官方客户端、脚本、API 或 RSS 访问事先取得书面许可。没有许可不要发布第三方客户端,否则可能封号。详见水源社区。

登录窗模式

Dela 的做法(src/main/campus.ts,可作参考):

  1. 建一个独立的持久 Session(例如 Electron 的 persist:dela-campus),不要和普通浏览窗口混用。
  2. 打开目标站点的 jAccount 登录入口。
  3. 用户手动完成账号密码/扫码/短信 + 验证码。不要脚本化验证码,也不要保存用户密码。
  4. 监听导航:到达目标域且不再是登录页时,调用一个探活接口确认登录真的生效。
  5. 探活成功后,把 Session Cookie 加密保存(系统钥匙串或等效加密存储),后续请求复用。
  6. 用户断开连接时,清 Cookie、清加密快照,并尽量调用站点撤销接口。

探活接口因站点而异:

站点 探活 成功标志
教学信息服务网 拉一次当前学期课表 返回 kbList JSON
课堂视频 GET /cloud-rbac/authority/me code === '0' 且 result.user 存在
水源 GET /session/current.json 返回 current_user.username
第二课堂 GET /api/v1/profile body code === 200 且 data.id 存在
传承·交大 GET /api/v1/material-type HTTP 200 且 success === true
选课社区 GET /api/auth/me HTTP 200 且返回 id
Pan 调一个只读空间接口 无公开文档,未验证

站点令牌

有的站点不把 API 会话放在 HttpOnly Cookie 里。登录成功后,前端把 token 写进 localStorage,再由页面脚本放进请求头。实测到的两种:

站点 存储键 请求头 未登录表现
第二课堂 IMPSA-Token Authorization: Bearer <token> HTTP 200 + body code: 401
传承·交大 token Auth: <token> HTTP 401 + success: false

获取方式:在受控窗口打开站点,让用户完成 jAccount 登录,然后在同一窗口里读 localStorage;不要要求用户手工复制。[实测 2026-10-26]

这种 token 对页面脚本可读,和 HttpOnly Cookie 不同。客户端拿到后按密码级凭据加密保存,不要写进 URL、日志或分享配置。退出登录或站点改版后,旧 token 可能立即失效,也可能留到过期;不要假设撤销语义。

失效表现

校园会话失效不总是 401。按站点分别处理:

表现 含义 处理
302 到 /jaccountlogin 或 jaccount.sjtu.edu.cn 登录失效 重新打开登录窗
返回 HTML 登录页而不是 JSON 登录失效/被重定向 不要按 JSON 解析;提示重新登录
401 / 403 可能是登录失效,也可能是无权限 结合探活接口判断;不要一律清 Token
空列表 / 空响应 课堂视频未登录时常见 先探活;不要把空当成「没有数据」
水源 403 not_logged_in 未登录 重新登录或检查 Cookie
第二课堂 HTTP 200 + code: 401 站点令牌失效 重新登录;不要按 HTTP 状态判断
传承 HTTP 401 + success: false 站点令牌缺失或过期 重新登录
选课社区 401 {"error":"unauthorized"} Key 无效或没带上 重新生成 Key 或补请求头

CSRF

  • 水源 Cookie 路径的写操作按 Discourse 规范需要 X-CSRF-Token,从 GET /session/csrf 取(该端点匿名可用)。本次只验证了取 token,没有实测 Cookie 写操作。[上游]
  • 水源 User API Key 路径的写操作不需要 CSRF;只带 User-Api-Key 和 User-Api-Client-Id 即可。[实测 2026-09-15]
  • 选课社区 API Key 路径的写操作不需要 CSRF;浏览器会话路径仍可能要求。[官方]
  • 其他站点的写操作先假设需要 CSRF,直到你验证不需要。

存储与撤销

  • Cookie 按密码级凭据处理:系统钥匙串、safeStorage、Vault 等;不要明文落盘。
  • 会话 Cookie 可能是 session cookie(浏览器关闭即失效)。如果需要跨重启,必须额外加密保存,并接受「服务端可能已经失效」。
  • 不要把 Cookie 发给模型、日志、错误报告、第三方分析。
  • 用户断开时:清本地 Cookie + 清快照 + 调站点撤销(如果支持)。
  • 多账号:不同账号用不同 Session/分区,不要在同一 Session 里切换。

三、常见问题

  1. 把 OAuth 当成万能钥匙:OAuth Token 不能调 i.sjtu/v.sjtu 的 Web 接口。它们是两套体系。
  2. 把 401 当成 Token 失效:Canvas 的单课 401 可能是无权限;校园站点也可能只是当前资源不可见。先探活,再决定是否重新登录。
  3. 把空列表当成没数据:课堂视频未登录时返回空列表或空响应,不返回 401。必须先调用 authority/me。
  4. 脚本化验证码:jAccount 登录包含验证码,也支持扫码和短信。应让用户人工登录;不要保存密码,也不要做验证码识别。
  5. 把 Cookie 放进 URL 或日志:Cookie 是凭据;只能放在请求头/Cookie Jar 里。
  6. 假设 SSO 登出会全局登出:jAccount 登出只影响当前应用。
  7. 把 clientSecret 放进前端:OAuth 授权码换 Token 必须在后端做;桌面/移动应用也应把 secret 放在主进程或服务端,不暴露给渲染层。
  8. 用 password grant:官方文档仍列了资源所有者密码模式,但长期方案应该用授权码 + refresh token,不要把用户密码交给应用。
  9. HTTP 200 就认为登录成功:第二课堂未登录也是 200,要看 body 的 code。
  10. 把 Auth 写成 Authorization: Bearer:传承只认 Auth,换头会 401。
  11. 把 API Key 当只读:选课社区的 Key 能发点评、投票、改选课记录。

四、最小示例

1. 生成授权地址

import { randomUUID } from 'node:crypto';

const authorizeUrl = new URL('https://jaccount.sjtu.edu.cn/oauth2/authorize');
authorizeUrl.search = new URLSearchParams({
  response_type: 'code',
  scope: 'openid',
  client_id: process.env.JACCOUNT_CLIENT_ID!,
  redirect_uri: process.env.JACCOUNT_REDIRECT_URI!,
  state: randomUUID(),
  nonce: randomUUID(),
}).toString();
console.log(authorizeUrl.href);

2. 用授权码换 Token

const response = await fetch('https://jaccount.sjtu.edu.cn/oauth2/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code,
    redirect_uri: process.env.JACCOUNT_REDIRECT_URI!,
    client_id: process.env.JACCOUNT_CLIENT_ID!,
    client_secret: process.env.JACCOUNT_CLIENT_SECRET!,
  }),
});
if (!response.ok) throw new Error(`token exchange failed: ${response.status}`);
const token = (await response.json()) as {
  access_token: string;
  refresh_token: string;
  id_token: string;
  expires_in: number;
};

3. 读本人 Profile

curl 'https://api.sjtu.edu.cn/v1/me/profile' \
  -H 'Authorization: Bearer <access_token>'

4. 校园会话的请求形状

// 伪代码:复用同一个 Session,不要每次新建
const session = await getPersistentCampusSession();
const response = await session.fetch('https://i.sjtu.edu.cn/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'X-Requested-With': 'XMLHttpRequest',
    Referer: 'https://i.sjtu.edu.cn/kbcx/xskbcx_cxXsgrkbIndex.html?gnmkdm=N2151',
  },
  body: new URLSearchParams({ xnm: '2026', xqm: '3', kzlx: 'ck', xsdm: '' }).toString(),
});
if (response.redirected) throw new Error('登录已失效,请重新登录');

五、证据与来源

  • jAccount OAuth/OIDC 端点、授权码 1 分钟、Access Token 1800 秒、ID Token claims、scope 表:[官方 2026-10-25,developer.sjtu.edu.cn/auth/oauth.html、oidc.html]
  • jAccount 接口申请对象、流程、app 网址检查:[官方 2026-10-25,net.sjtu.edu.cn/wlfw/jAccountjk.htm]
  • scope 增加邮件:[官方 2026-10-25,developer.sjtu.edu.cn/api/overview.html]
  • 水源 SSO 跳转、_forum_session/_t、User API Key、CSRF 差异:[实测 2026-09-13/15,内部实测记录未入库;实现见 Dela src/main/shuiyuan/*]
  • 课堂视频只需要 Cookie:[实测 2026-10-06,内部实测记录未入库;实现见 Dela src/main/vsjtu/*]
  • 校园会话持久化、登录窗、探活:[实测 2026-09,Dela src/main/campus.ts]
  • 第二课堂、传承的站点令牌、未登录响应与探活:[实测 2026-10-26,本人账号,只读];记录未入库,见第二课堂、传承·交大。
  • 选课社区 API Key、401 与 CSRF 差异:[官方/实测 2026-10-26,本人 Key,只读],见选课社区。

变更记录

  • 2026-10-25:首版。合并 jAccount OAuth/OIDC、校园 Web 会话、申请现实三部分。
  • 2026-10-26:补站点令牌(第二课堂、传承)与站点 API Key(选课社区);增加对应探活、失效表现和 CSRF 说明。

Clone this wiki locally