JAAS 把「向谁要口令、要什么」拆成 LoginModule 与 CallbackHandler。这篇用真实运行的模块,逐个看回调对象怎么保存口令、多个模块怎么各自提问、出错时是什么样子。
[flow]
好口令:成功;Subject 的主体 [P(amy@A)];logout 之后主体 []
· A.initialize options={id=A} sharedState.keys=[]
· A.login 向 handler 要了用户名与口令
· A.login 验证通过
· A.commit
· A.logout
handler 被调用 1 次
错口令:失败 FailedLoginException: A: 用户名或口令不对
· A.initialize options={id=A} sharedState.keys=[]
· A.login 向 handler 要了用户名与口令
· A.login 验证失败
· A.abort
handler 被调用 1 次 [pc]
getPrompt = “口令: ”;isEchoOn = false;未设置时 getPassword = null
setPassword 之后:getPassword() 两次返回的是同一个数组? false;与传入的数组是同一个? false;内容相同? true
把传入的数组清成 x 之后:pc.getPassword() = hunter2(回调内部保存的是副本)
改动 getPassword() 返回的数组之后,再取:hunter2(返回的也是副本)
clearPassword() 之后 getPassword() 返回 长度 7 的数组,字符码集合 [32];之前取出的副本 g2 仍然是 hunter2(调用者自己取出的副本要自己清)
setPassword(null) → 成功;getPassword = null
new PasswordCallback(null, true) → IllegalArgumentException: null;new PasswordCallback("", true) → IllegalArgumentException: null
echoOn = true 的回调:isEchoOn = true(只是给界面的提示,类本身不回显)
可序列化吗? PasswordCallback implements Serializable = true;序列化一个设置了口令的回调(口令字段是否被序列化):序列化 170 字节;字节流里能找到口令的 UTF-16BE 形式? true;反序列化回来 getPassword = pw123456;inputPassword 字段是 transient? false [order]
TwoRequired:成功;Subject 的主体 [P(amy@A), P(amy@B)];logout 之后主体 []
· A.initialize options={id=A} sharedState.keys=[]
· A.login 向 handler 要了用户名与口令
· A.login 验证通过
· B.initialize options={id=B} sharedState.keys=[]
· B.login 向 handler 要了用户名与口令
· B.login 验证通过
· A.commit
· B.commit
· A.logout
· B.logout
handler 被调用 2 次
TwoRequiredOneBad:失败 FailedLoginException: B: 用户名或口令不对
· A.initialize options={id=A} sharedState.keys=[]
· A.login 向 handler 要了用户名与口令
· A.login 验证通过
· B.initialize options={password=other, id=B} sharedState.keys=[]
· B.login 向 handler 要了用户名与口令
· B.login 验证失败
· A.abort
· B.abort
handler 被调用 2 次
SufficientFirst:成功;Subject 的主体 [P(amy@A)];logout 之后主体 []
· A.initialize options={id=A} sharedState.keys=[]
· A.login 向 handler 要了用户名与口令
· A.login 验证通过
· A.commit
· A.logout
· B.initialize options={password=other, id=B} sharedState.keys=[]
· B.logout
handler 被调用 1 次
OptionalBad:成功;Subject 的主体 [P(amy@A), P(amy@B)];logout 之后主体 []
· A.initialize options={password=other, id=A} sharedState.keys=[]
· A.login 向 handler 要了用户名与口令
· A.login 验证失败
· B.initialize options={id=B} sharedState.keys=[]
· B.login 向 handler 要了用户名与口令
· B.login 验证通过
· A.commit
· B.commit
· A.logout
· B.logout
handler 被调用 2 次
RequisiteBad:失败 FailedLoginException: A: 用户名或口令不对
· A.initialize options={password=other, id=A} sharedState.keys=[]
· A.login 向 handler 要了用户名与口令
· A.login 验证失败
· A.abort
· B.initialize options={id=B} sharedState.keys=[]
· B.abort
handler 被调用 1 次
SharedState:成功;Subject 的主体 [P(amy@A), P(amy@B)];logout 之后主体 []
· A.initialize options={storePass=true, id=A} sharedState.keys=[]
· A.login 向 handler 要了用户名与口令
· A.login 验证通过
· B.initialize options={id=B, useFirstPass=true} sharedState.keys=[javax.security.auth.login.password, javax.security.auth.login.name]
· B.login 使用共享状态里的口令(没有再向 handler 要)
· B.login 验证通过
· A.commit
· B.commit
· A.logout
· B.logout
handler 被调用 1 次
SharedStateNoStore:成功;Subject 的主体 [P(amy@A), P(amy@B)];logout 之后主体 []
· A.initialize options={id=A} sharedState.keys=[]
· A.login 向 handler 要了用户名与口令
· A.login 验证通过
· B.initialize options={id=B, useFirstPass=true} sharedState.keys=[]
· B.login 向 handler 要了用户名与口令
· B.login 验证通过
· A.commit
· B.commit
· A.logout
· B.logout
handler 被调用 2 次 [errors]
handler 不支持 PasswordCallback:失败 LoginException: A: 回调失败 UnsupportedCallbackException: 只支持 NameCallback
handler 抛 IOException:失败 LoginException: A: 回调失败 IOException: 终端不可用
handler 抛运行时异常:失败 LoginException: java.lang.IllegalStateException: handler 坏了(消息里还附带 15 行堆栈文本)
handler 为 null:LoginException: invalid null CallbackHandler provided
不存在的配置项:LoginException: No LoginModules configured for Nope
模块类不存在:失败 LoginException: No LoginModule found for pk.NoSuchModule
模块类不是 public:失败 LoginException: java.lang.IllegalAccessException: class javax.security.auth.login.LoginContext (in module java.base) cannot access a member of class pk.Hidden with package access(消息里还附带 15 行堆栈文本)
login() 两次:第二次成功;主体 [P(amy@A), P(amy@A)]
logout() 之前没有 login:LoginException: null subject - logout called before login
getSubject() 在 login 之前 / 失败之后:login 前 null;失败后 null
没有 handler 且模块需要回调:LoginException: java.lang.NullPointerException: Cannot invoke "javax.security.auth.callback.CallbackHandler.handle(javax.security.auth.callback.Callback[])" because "this.handler" is null(消息里附带堆栈文本) [keystore]
KeyStoreLoginModule 登录成功;Subject 主体 [X500Principal(CN=localhost)];公开凭证 [X509CertPath];私有凭证 [X500PrivateCredential]
logout 之后私有凭证 0 个,公开凭证 0 个
回调的顺序与内容:
· TextOutputCallback(0, "Please enter keystore information")
· NameCallback("Keystore alias: ", default=srv)
· PasswordCallback("Keystore password: ", echo=false)
· PasswordCallback("Private key password (optional): ", echo=false)
· ConfirmationCallback(null)
口令错误:LoginException: Error initializing keystore ← IOException: keystore password was incorrect