自定义 ServerAuthModule (loginModule),在哪里散列密码?佳士得
custom ServerAuthModule (loginModule), where to hash the password? JASPIC
我正在尝试实现一个登录模块,以便我可以在我的网络应用程序上执行 "remember me" 功能,并使用 bcrypt 散列我的密码。为了构建 class,我使用了 this tutorial。但是,在实施此操作后我没有设法连接。 db 中的密码目前通过 SHA-256 进行哈希处理,我怀疑这就是原因。
public class TestAuthModule implements
javax.security.auth.message.module.ServerAuthModule {
@SuppressWarnings("rawtypes")
protected static final Class[] supportedMessageTypes = new Class[] {
HttpServletRequest.class, HttpServletResponse.class };
private CallbackHandler handler;
public void initialize(MessagePolicy requestPolicy,
MessagePolicy responsePolicy, CallbackHandler handler,
@SuppressWarnings("rawtypes") Map options) throws AuthException {
System.out.println("initialize called.");
this.handler = handler;
}
@SuppressWarnings("rawtypes")
public Class[] getSupportedMessageTypes() {
return supportedMessageTypes;
}
public AuthStatus validateRequest(MessageInfo messageInfo,
Subject clientSubject, Subject serverSubject) throws AuthException {
HttpServletRequest request = (HttpServletRequest) messageInfo
.getRequestMessage();
String user = request.getParameter("user");
String group = request.getParameter("group");
System.out.println("validateRequest called.");
System.out.println("User = " + user);
System.out.println("Group = " + group);
authenticateUser(user, group, clientSubject, serverSubject);
return AuthStatus.SUCCESS;
}
public AuthStatus secureResponse(MessageInfo msgInfo, Subject service)
throws AuthException {
System.out.println("secureResponse called.");
return AuthStatus.SEND_SUCCESS;
}
public void cleanSubject(MessageInfo msgInfo, Subject subject)
throws AuthException {
if (subject != null) {
subject.getPrincipals().clear();
}
}
private void authenticateUser(String user, String group,
Subject clientSubject, Subject serverSubject) {
System.out
.println("Authenticating user " + user + " in group " + group);
CallerPrincipalCallback callerPrincipalCallback = new CallerPrincipalCallback(
clientSubject, user);
GroupPrincipalCallback groupPrincipalCallback = new GroupPrincipalCallback(
clientSubject, new String[] { group });
try {
handler.handle(new Callback[] { callerPrincipalCallback,
groupPrincipalCallback });
} catch (Exception e) {
e.printStackTrace();
}
}
}
我这样登录(在实现自定义登录模块之前确实有效):
private String username;
private Password password;
//....
for (int i = 0; i < x -1 ; i++) {
this.password = PasswordEncoder
.toHex(PasswordEncoder
.hash512(this.password + salt));
}
// x is the number of time I hashed the password before storing it in db.
// x-1 because glassfish authentication does it once for me.
//...
try {
request.login(username, password + salt);
} catch (ServletException e)
另外,在我的页面上,我曾经有一个注册按钮和一个登录按钮,只有当用户为 null 时才会显示,如果不是,我的用户名位于顶部。现在我实现了这个,就像用户连接为 "ANONYMOUS"(所以页面顶部有 "you are connected as ANONYMOUS"。
为了防止这种情况,我做了一个临时修复:
if (username == null || username.equals("ANONYMOUS")) {
this.isUserConnected = false;
} else {
this.isUserConnected = true;
}
我试过了:
isUserInGroup("ANONYMOUS");
但是没有用户,所以我得到了一个 npe。我也不知道该怎么做。
您可以在此处使用两种替代方法。
第一个——我个人喜欢的——是完全忘记专有的 AS 提供的 JAAS LoginModule
s (LMs),并在你的 ServerAuthModule
的(SAM 的)validateRequest
方法。这为您提供了关于应用程序如何保留凭据(hashing/salting,数据库架构)以及 SAM 应如何执行客户端提供的凭据验证的最终自由选择。因此,在这种情况下,SAM 负责以线程安全和高效的方式连接到 DB;这将是这种方法唯一可能棘手的方面。
或者,您的 SAM 可以将凭证验证委托给 AS 提供的 LM(或者您编写的 LM,前提是它适用,即特定于供应商的扩展)。我想这就是你一直在努力完成的事情。然后 SAM 必须符合 JASPIC 的 Servlet Container Profile 及其 LoginModule Bridge Profile(参见规范的第 6 章);简而言之,后者要求:
- SAM 的
initialize
方法查询封装在运行时提供的 options
中的 javax.security.auth.login.LoginContext
密钥的值。它还构造了一个支持NameCallback
和PasswordValidationCallback
的additionalCallbackHandler
。最后,它使用上述实例(分别为 name
和 callbackHandler
参数)实例化一个特定于请求的 LoginContext
(LC)。
- 其
validateRequest
委托给LC的login
;然后将 LC 的 Subject
中的底层 LM 建立的任何 Principal
(s) 传达给 AS(通过 CallerPrincipalCallback
和 GroupPrincipalCallback
)。
- 它的
cleanSubject
委托给 LC 的 logout
。
显然,除非您自己编写 LM,否则没有任何干净的方法可以改变凭据验证过程。
一些结束语:
HttpServletRequest.login
当 JASPIC 被配置为与您的应用程序一起使用时,将始终引发异常(请参阅规范的第 3.9.2 节)。请改用 HttpServletRequest.authenticate
。
- 你的 SAM
validateRequest
应该 return 一个合适的 AuthStatus
;即 SUCCESS
当登录成功或身份验证为 可选 (即请求的资源不受保护),无论身份验证结果如何; SEND_CONTINUE
将用户重定向到登录页面时; SEND_FAILURE
(或抛出 AuthException
)当登录失败并且身份验证是 强制性 (即请求的资源受保护或用户明确请求登录)。
- 未经身份验证的用户的身份应由您的 SAM
validateRequest
通过向 CallerPrincipalCallback
/GroupPrincipalCallback
构造函数提供 null
[=68] 来传达给 AS =]principal 或 groups 参数。一个兼容的 Servlet 容器的 HttpServletRequest.getUserPrincipal
的实现应该 return null
。请注意,EJBContext.getCallerPrincipal
的行为不同,returning 一个 AS 特定类型的实例以表示未经身份验证的用户(也许这就是您所看到的)。无论如何,你的授权相关的应用程序级代码不应该依赖于匿名Principal
的非标准名称。解决 is-guest 问题的另一种方法可能涉及在成功时从 SAM 设置 javax.servlet.http.authType
回调 属性(参见规范的第 3.8.4 节)验证。然后调用 HttpServletRequest.getAuthType
不仅会断言用户是否经过身份验证,还会断言您的 SAM、另一个 SAM 或特定于 AS 的 LM 是否执行了身份验证(想想利用多种身份验证类型的应用程序)。另一种处理方法是将自定义 Principal
实现传递给 CallerPrincipalCallback
;然后在通过 HttpServletRequest.getUserPrincipal
公开的 Principal
上执行 instanceof
(但请注意,仍然存在问题,例如,在 GlassFish 上需要一个 EJB 来实现相同的结果)。
另请参阅:
我正在尝试实现一个登录模块,以便我可以在我的网络应用程序上执行 "remember me" 功能,并使用 bcrypt 散列我的密码。为了构建 class,我使用了 this tutorial。但是,在实施此操作后我没有设法连接。 db 中的密码目前通过 SHA-256 进行哈希处理,我怀疑这就是原因。
public class TestAuthModule implements
javax.security.auth.message.module.ServerAuthModule {
@SuppressWarnings("rawtypes")
protected static final Class[] supportedMessageTypes = new Class[] {
HttpServletRequest.class, HttpServletResponse.class };
private CallbackHandler handler;
public void initialize(MessagePolicy requestPolicy,
MessagePolicy responsePolicy, CallbackHandler handler,
@SuppressWarnings("rawtypes") Map options) throws AuthException {
System.out.println("initialize called.");
this.handler = handler;
}
@SuppressWarnings("rawtypes")
public Class[] getSupportedMessageTypes() {
return supportedMessageTypes;
}
public AuthStatus validateRequest(MessageInfo messageInfo,
Subject clientSubject, Subject serverSubject) throws AuthException {
HttpServletRequest request = (HttpServletRequest) messageInfo
.getRequestMessage();
String user = request.getParameter("user");
String group = request.getParameter("group");
System.out.println("validateRequest called.");
System.out.println("User = " + user);
System.out.println("Group = " + group);
authenticateUser(user, group, clientSubject, serverSubject);
return AuthStatus.SUCCESS;
}
public AuthStatus secureResponse(MessageInfo msgInfo, Subject service)
throws AuthException {
System.out.println("secureResponse called.");
return AuthStatus.SEND_SUCCESS;
}
public void cleanSubject(MessageInfo msgInfo, Subject subject)
throws AuthException {
if (subject != null) {
subject.getPrincipals().clear();
}
}
private void authenticateUser(String user, String group,
Subject clientSubject, Subject serverSubject) {
System.out
.println("Authenticating user " + user + " in group " + group);
CallerPrincipalCallback callerPrincipalCallback = new CallerPrincipalCallback(
clientSubject, user);
GroupPrincipalCallback groupPrincipalCallback = new GroupPrincipalCallback(
clientSubject, new String[] { group });
try {
handler.handle(new Callback[] { callerPrincipalCallback,
groupPrincipalCallback });
} catch (Exception e) {
e.printStackTrace();
}
}
}
我这样登录(在实现自定义登录模块之前确实有效):
private String username;
private Password password;
//....
for (int i = 0; i < x -1 ; i++) {
this.password = PasswordEncoder
.toHex(PasswordEncoder
.hash512(this.password + salt));
}
// x is the number of time I hashed the password before storing it in db.
// x-1 because glassfish authentication does it once for me.
//...
try {
request.login(username, password + salt);
} catch (ServletException e)
另外,在我的页面上,我曾经有一个注册按钮和一个登录按钮,只有当用户为 null 时才会显示,如果不是,我的用户名位于顶部。现在我实现了这个,就像用户连接为 "ANONYMOUS"(所以页面顶部有 "you are connected as ANONYMOUS"。 为了防止这种情况,我做了一个临时修复:
if (username == null || username.equals("ANONYMOUS")) {
this.isUserConnected = false;
} else {
this.isUserConnected = true;
}
我试过了:
isUserInGroup("ANONYMOUS");
但是没有用户,所以我得到了一个 npe。我也不知道该怎么做。
您可以在此处使用两种替代方法。
第一个——我个人喜欢的——是完全忘记专有的 AS 提供的 JAAS LoginModule
s (LMs),并在你的 ServerAuthModule
的(SAM 的)validateRequest
方法。这为您提供了关于应用程序如何保留凭据(hashing/salting,数据库架构)以及 SAM 应如何执行客户端提供的凭据验证的最终自由选择。因此,在这种情况下,SAM 负责以线程安全和高效的方式连接到 DB;这将是这种方法唯一可能棘手的方面。
或者,您的 SAM 可以将凭证验证委托给 AS 提供的 LM(或者您编写的 LM,前提是它适用,即特定于供应商的扩展)。我想这就是你一直在努力完成的事情。然后 SAM 必须符合 JASPIC 的 Servlet Container Profile 及其 LoginModule Bridge Profile(参见规范的第 6 章);简而言之,后者要求:
- SAM 的
initialize
方法查询封装在运行时提供的options
中的javax.security.auth.login.LoginContext
密钥的值。它还构造了一个支持NameCallback
和PasswordValidationCallback
的additionalCallbackHandler
。最后,它使用上述实例(分别为name
和callbackHandler
参数)实例化一个特定于请求的LoginContext
(LC)。 - 其
validateRequest
委托给LC的login
;然后将 LC 的Subject
中的底层 LM 建立的任何Principal
(s) 传达给 AS(通过CallerPrincipalCallback
和GroupPrincipalCallback
)。 - 它的
cleanSubject
委托给 LC 的logout
。
显然,除非您自己编写 LM,否则没有任何干净的方法可以改变凭据验证过程。
一些结束语:
HttpServletRequest.login
当 JASPIC 被配置为与您的应用程序一起使用时,将始终引发异常(请参阅规范的第 3.9.2 节)。请改用HttpServletRequest.authenticate
。- 你的 SAM
validateRequest
应该 return 一个合适的AuthStatus
;即SUCCESS
当登录成功或身份验证为 可选 (即请求的资源不受保护),无论身份验证结果如何;SEND_CONTINUE
将用户重定向到登录页面时;SEND_FAILURE
(或抛出AuthException
)当登录失败并且身份验证是 强制性 (即请求的资源受保护或用户明确请求登录)。 - 未经身份验证的用户的身份应由您的 SAM
validateRequest
通过向CallerPrincipalCallback
/GroupPrincipalCallback
构造函数提供null
[=68] 来传达给 AS =]principal 或 groups 参数。一个兼容的 Servlet 容器的HttpServletRequest.getUserPrincipal
的实现应该 returnnull
。请注意,EJBContext.getCallerPrincipal
的行为不同,returning 一个 AS 特定类型的实例以表示未经身份验证的用户(也许这就是您所看到的)。无论如何,你的授权相关的应用程序级代码不应该依赖于匿名Principal
的非标准名称。解决 is-guest 问题的另一种方法可能涉及在成功时从 SAM 设置javax.servlet.http.authType
回调 属性(参见规范的第 3.8.4 节)验证。然后调用HttpServletRequest.getAuthType
不仅会断言用户是否经过身份验证,还会断言您的 SAM、另一个 SAM 或特定于 AS 的 LM 是否执行了身份验证(想想利用多种身份验证类型的应用程序)。另一种处理方法是将自定义Principal
实现传递给CallerPrincipalCallback
;然后在通过HttpServletRequest.getUserPrincipal
公开的Principal
上执行instanceof
(但请注意,仍然存在问题,例如,在 GlassFish 上需要一个 EJB 来实现相同的结果)。
另请参阅: