Glassfish 中的服务器身份验证模块转发
Posted
技术标签:
【中文标题】Glassfish 中的服务器身份验证模块转发【英文标题】:Server Authentication Module forwarding in Glassfish 【发布时间】:2015-04-28 21:42:29 【问题描述】:我找到了为 Glassfish 开发自己的服务器身份验证模块 (SAM) 的指南:http://docs.oracle.com/cd/E18930_01/html/821-2418/gizel.html
验证某些凭据(例如在 HTTP Auth 标头中)似乎很简单,但我的问题是:
我能否开发我的 SAM,以便在用户未登录时将其转发到特定页面?
这是指南中的示例:
package tip.sam;
import java.io.IOException;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.MessagePolicy;
import javax.security.auth.message.callback.CallerPrincipalCallback;
import javax.security.auth.message.callback.GroupPrincipalCallback;
import javax.security.auth.message.callback.PasswordValidationCallback;
import javax.security.auth.message.module.ServerAuthModule;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.util.Base64;
public class MySam implements ServerAuthModule
protected static final Class[]
supportedMessageTypes = new Class[]
HttpServletRequest.class,
HttpServletResponse.class
;
private MessagePolicy requestPolicy;
private MessagePolicy responsePolicy;
private CallbackHandler handler;
private Map options;
private String realmName = null;
private String defaultGroup[] = null;
private static final String REALM_PROPERTY_NAME =
"realm.name";
private static final String GROUP_PROPERTY_NAME =
"group.name";
private static final String BASIC = "Basic";
static final String AUTHORIZATION_HEADER =
"authorization";
static final String AUTHENTICATION_HEADER =
"WWW-Authenticate";
public void initialize(MessagePolicy reqPolicy,
MessagePolicy resPolicy,
CallbackHandler cBH, Map opts)
throws AuthException
requestPolicy = reqPolicy;
responsePolicy = resPolicy;
handler = cBH;
options = opts;
if (options != null)
realmName = (String)
options.get(REALM_PROPERTY_NAME);
if (options.containsKey(GROUP_PROPERTY_NAME))
defaultGroup = new String[](String)
options.get(GROUP_PROPERTY_NAME);
public Class[] getSupportedMessageTypes()
return supportedMessageTypes;
public AuthStatus validateRequest(
MessageInfo msgInfo, Subject client,
Subject server) throws AuthException
try
String username =
processAuthorizationToken(msgInfo, client);
if (username ==
null && requestPolicy.isMandatory())
return sendAuthenticateChallenge(msgInfo);
setAuthenticationResult(
username, client, msgInfo);
return AuthStatus.SUCCESS;
catch (Exception e)
AuthException ae = new AuthException();
ae.initCause(e);
throw ae;
private String processAuthorizationToken(
MessageInfo msgInfo, Subject s)
throws AuthException
HttpServletRequest request =
(HttpServletRequest)
msgInfo.getRequestMessage();
String token =
request.getHeader(AUTHORIZATION_HEADER);
if (token != null && token.startsWith(BASIC + " "))
token = token.substring(6).trim();
// Decode and parse the authorization token
String decoded =
new String(Base64.decode(token.getBytes()));
int colon = decoded.indexOf(':');
if (colon <= 0 || colon == decoded.length() - 1)
return (null);
String username = decoded.substring(0, colon);
// use the callback to ask the container to
// validate the password
PasswordValidationCallback pVC =
new PasswordValidationCallback(s, username,
decoded.substring(colon + 1).toCharArray());
try
handler.handle(new Callback[]pVC);
pVC.clearPassword();
catch (Exception e)
AuthException ae = new AuthException();
ae.initCause(e);
throw ae;
if (pVC.getResult())
return username;
return null;
private AuthStatus sendAuthenticateChallenge(
MessageInfo msgInfo)
String realm = realmName;
// if the realm property is set use it,
// otherwise use the name of the server
// as the realm name.
if (realm == null)
HttpServletRequest request =
(HttpServletRequest)
msgInfo.getRequestMessage();
realm = request.getServerName();
HttpServletResponse response =
(HttpServletResponse)
msgInfo.getResponseMessage();
String header = BASIC + " realm=\"" + realm + "\"";
response.setHeader(AUTHENTICATION_HEADER, header);
response.setStatus(
HttpServletResponse.SC_UNAUTHORIZED);
return AuthStatus.SEND_CONTINUE;
// MAYBE SOMETHING HERE?
public AuthStatus secureResponse(
MessageInfo msgInfo, Subject service)
throws AuthException
return AuthStatus.SEND_SUCCESS;
public void cleanSubject(MessageInfo msgInfo,
Subject subject)
throws AuthException
if (subject != null)
subject.getPrincipals().clear();
private static final String AUTH_TYPE_INFO_KEY =
"javax.servlet.http.authType";
// distinguish the caller principal
// and assign default groups
private void setAuthenticationResult(String name,
Subject s, MessageInfo m)
throws IOException,
UnsupportedCallbackException
handler.handle(new Callback[]
new CallerPrincipalCallback(s, name)
);
if (name != null)
// add the default group if the property is set
if (defaultGroup != null)
handler.handle(new Callback[]
new GroupPrincipalCallback(s, defaultGroup)
);
m.getMap().put(AUTH_TYPE_INFO_KEY, ""MySAM");
【问题讨论】:
【参考方案1】:是的,您可以在 validateRequest
方法中执行此操作。
这是一个简单的例子:
public AuthStatus validateRequest(MessageInfo messageInfo,
Subject clientSubject,
Subject serviceSubject) throws AuthException
// clientSubject.getPrincipals() returns the principals
// check this set to know if the user is not logged in
// if the user is not logged in do the following
HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
response.sendRedirect("login.html");
最好在自定义 LoginModule 中执行此操作(如果您已经知道那是什么),但我想这取决于您的要求。
另请参阅:
LoginModule Bridge Profile (JASPIC) in glassfish Implementing container authentication in Java EE with JASPIC JAAS for human beings【讨论】:
感谢您的回复。是否也有可能以这样的方式返回此页面,例如 HTTP Auth 标头中的令牌和/或经过验证的 cookie 中的会话 ID? 我看不出有什么问题,您可以从 HttpServletRequest 中读取所有值,并且可以修改 HttpServletResponse 以设置 cookie 或标头。 好的,所以我可以实现以下场景:检查会话cookie,如果不存在(或无效)并且没有令牌,则重定向到其他页面并返回令牌。如果有令牌,则为其创建会话并将其作为 cookie 放入响应中。 别忘了从validateRequest
返回正确的AuthStatus
,在本例中为SEND_CONTINUE
。
It might be better to do it inside of a custom LoginModule
- 如果您的意思是重定向,那么在此处执行此操作实际上并不好。 LoginModule 应该只进行简单的身份验证(查找用户、验证凭据然后使用户 + 组主体可用),而总体 SAM 应该负责与用户的交互。特制的回调可以让 LoginModule 对请求和响应进行一些有限的访问,但我认为这不应该用于真正的交互,最多是从请求中获取一些基本值。以上是关于Glassfish 中的服务器身份验证模块转发的主要内容,如果未能解决你的问题,请参考以下文章
Glassfish 在领域身份验证中创建多个 http 会话