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 上进行领域身份验证后重定向

Glassfish 在领域身份验证中创建多个 http 会话

Glassfish JMS(平面文件)身份验证

Glassfish V3 LDAP 身份验证失败不显示表单错误页面

休息,glassfish4 shiro 启用工作样本

如何将基本身份验证质询转发到报告管理器 url