Repository navigation
Auth
| 项 | 值 |
|---|---|
| 平台 |
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 审批 |
| 用途 | 地址 |
|---|---|
| 授权 | 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/ |
- 把用户导向授权地址:
GET https://jaccount.sjtu.edu.cn/oauth2/authorize
?response_type=code
&scope=openid
&client_id=<clientId>
&redirect_uri=<登记过的回调地址>
&state=<随机串>
- 用户在 jAccount 完成登录/授权,回调带上
code和state。 - 后端用
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>'- 返回:
{
"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 是 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 有两种写法:名字(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 核对):
- 登录
my.sjtu.edu.cn或交我办。 - 服务大厅 → 信息服务 → 其他 → jAccount 接口申请。
- 填应用名称、用途、部署地址、回调地址、需要的 scope。
- 提交给部门领导审核,通过后网络信息中心处理,约 3 个工作日。
- 取得
clientId和 48 字符的clientSecret。 - 增加 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 的应用一起登出。
[官方]
i.sjtu.edu.cn、v.sjtu.edu.cn、水源和 Pan 的登录入口都是 jAccount SSO,但这些站点不是你的 OAuth 应用。你不需要 clientId,也不能用 OAuth Token 调用它们。做法是:
让真实用户在独立、持久的浏览器 Session 中完成一次 jAccount 登录,再带目标站点的 Cookie 调用其 JSON 接口。
这与「用户自己打开浏览器登录,然后复制 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,可作参考):
- 建一个独立的持久 Session(例如 Electron 的
persist:dela-campus),不要和普通浏览窗口混用。 - 打开目标站点的 jAccount 登录入口。
- 用户手动完成账号密码/扫码/短信 + 验证码。不要脚本化验证码,也不要保存用户密码。
- 监听导航:到达目标域且不再是登录页时,调用一个探活接口确认登录真的生效。
- 探活成功后,把 Session Cookie 加密保存(系统钥匙串或等效加密存储),后续请求复用。
- 用户断开连接时,清 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 或补请求头 |
- 水源 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 里切换。
- 把 OAuth 当成万能钥匙:OAuth Token 不能调 i.sjtu/v.sjtu 的 Web 接口。它们是两套体系。
- 把 401 当成 Token 失效:Canvas 的单课 401 可能是无权限;校园站点也可能只是当前资源不可见。先探活,再决定是否重新登录。
-
把空列表当成没数据:课堂视频未登录时返回空列表或空响应,不返回 401。必须先调用
authority/me。 - 脚本化验证码:jAccount 登录包含验证码,也支持扫码和短信。应让用户人工登录;不要保存密码,也不要做验证码识别。
- 把 Cookie 放进 URL 或日志:Cookie 是凭据;只能放在请求头/Cookie Jar 里。
- 假设 SSO 登出会全局登出:jAccount 登出只影响当前应用。
- 把 clientSecret 放进前端:OAuth 授权码换 Token 必须在后端做;桌面/移动应用也应把 secret 放在主进程或服务端,不暴露给渲染层。
- 用 password grant:官方文档仍列了资源所有者密码模式,但长期方案应该用授权码 + refresh token,不要把用户密码交给应用。
-
HTTP 200 就认为登录成功:第二课堂未登录也是 200,要看 body 的
code。 -
把
Auth写成Authorization: Bearer:传承只认Auth,换头会 401。 - 把 API Key 当只读:选课社区的 Key 能发点评、投票、改选课记录。
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);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;
};curl 'https://api.sjtu.edu.cn/v1/me/profile' \
-H 'Authorization: Bearer <access_token>'// 伪代码:复用同一个 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 说明。
本 Wiki 记录的是开发实践,不是学校官方承诺。每个结论都标注证据与日期;校园平台会调整字段、权限和登录方式,动手前请按页面里的「最后验证」复核。发现过期内容请提 Issue 或直接改。