Hot questions for Using GlassFish in jaas

Question:

I'm using spnego.jar from dfelix with Glassfish 4.1 as filter in web.xml. I asked the question on project forum but did not receive response.

I want to switch from Java 1.7.0_45 to 1.8.0_45 (tried also other 1.8 versions). After switching on a test environment I'm getting the error shown below. Tried also a separate case with simple Java also received case exception.

I'm using same krb5.conf (below), login.conf and keytab file as in 1.7.

Is it incompatible with Java 1.8?

[glassfish 4.1] [SEVERE] [] [javax.enterprise.web] [tid: _ThreadID=16 _ThreadName=RunLevelControllerThread-1431425761516] [timeMillis: 1431425776202] [levelValue: 1000] [[ WebModule[/ax]Exception starting filter SpnegoHttpFilter java.lang.InstantiationException at org.apache.catalina.core.ApplicationFilterConfig.(ApplicationFilterConfig.java:135) at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:5329) at org.apache.catalina.core.StandardContext.start(StandardContext.java:5943) at com.sun.enterprise.web.WebModule.start(WebModule.java:691) at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:1041) at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:1024) at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:747) at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:2286) at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:1932) at com.sun.enterprise.web.WebApplication.start(WebApplication.java:139) at org.glassfish.internal.data.EngineRef.start(EngineRef.java:122) at org.glassfish.internal.data.ModuleInfo.start(ModuleInfo.java:291) at org.glassfish.internal.data.ApplicationInfo.start(ApplicationInfo.java:352) at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:500) at com.sun.enterprise.v3.server.ApplicationLoaderService.processApplication(ApplicationLoaderService.java:406) at com.sun.enterprise.v3.server.ApplicationLoaderService.postConstruct(ApplicationLoaderService.java:243) at org.jvnet.hk2.internal.ClazzCreator.postConstructMe(ClazzCreator.java:329) at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:377) at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:461) at org.glassfish.hk2.runlevel.internal.AsyncRunLevelContext.findOrCreate(AsyncRunLevelContext.java:227) at org.glassfish.hk2.runlevel.RunLevelContext.findOrCreate(RunLevelContext.java:84) at org.jvnet.hk2.internal.Utilities.createService(Utilities.java:2258) at org.jvnet.hk2.internal.ServiceHandleImpl.getService(ServiceHandleImpl.java:105) at org.jvnet.hk2.internal.ServiceHandleImpl.getService(ServiceHandleImpl.java:87) at org.glassfish.hk2.runlevel.internal.CurrentTaskFuture$QueueRunner.oneJob(CurrentTaskFuture.java:1162) at org.glassfish.hk2.runlevel.internal.CurrentTaskFuture$QueueRunner.run(CurrentTaskFuture.java:1147) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) Caused by: javax.servlet.ServletException: javax.security.auth.login.LoginException: java.lang.IllegalArgumentException: java.lang.ClassCastException: java.util.Vector cannot be cast to java.util.Hashtable at sun.security.krb5.Config.get0(Config.java:287) at sun.security.krb5.Config.getString0(Config.java:268) at sun.security.krb5.Config.getAll(Config.java:240) at sun.security.krb5.Config.getKDCList(Config.java:1030) at sun.security.krb5.KdcComm.send(KdcComm.java:218) at sun.security.krb5.KdcComm.send(KdcComm.java:200) at sun.security.krb5.KrbAsReqBuilder.send(KrbAsReqBuilder.java:316) at sun.security.krb5.KrbAsReqBuilder.action(KrbAsReqBuilder.java:361) at com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:776) at com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:617) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at javax.security.auth.login.LoginContext.invoke(LoginContext.java:755) at javax.security.auth.login.LoginContext.access$000(LoginContext.java:195) at javax.security.auth.login.LoginContext$4.run(LoginContext.java:682) at javax.security.auth.login.LoginContext$4.run(LoginContext.java:680) at java.security.AccessController.doPrivileged(Native Method) at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680) at javax.security.auth.login.LoginContext.login(LoginContext.java:587) at net.sourceforge.spnego.SpnegoAuthenticator.(SpnegoAuthenticator.java:161) at net.sourceforge.spnego.SpnegoHttpFilter.init(SpnegoHttpFilter.java:196) at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:275) at org.apache.catalina.core.ApplicationFilterConfig.(ApplicationFilterConfig.java:131) at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:5329) at org.apache.catalina.core.StandardContext.start(StandardContext.java:5943) at com.sun.enterprise.web.WebModule.start(WebModule.java:691) at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:1041) at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:1024) at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:747) at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:2286) at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:1932) at com.sun.enterprise.web.WebApplication.start(WebApplication.java:139) at org.glassfish.internal.data.EngineRef.start(EngineRef.java:122) at org.glassfish.internal.data.ModuleInfo.start(ModuleInfo.java:291) at org.glassfish.internal.data.ApplicationInfo.start(ApplicationInfo.java:352) at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:500) at com.sun.enterprise.v3.server.ApplicationLoaderService.processApplication(ApplicationLoaderService.java:406) at com.sun.enterprise.v3.server.ApplicationLoaderService.postConstruct(ApplicationLoaderService.java:243) at org.jvnet.hk2.internal.ClazzCreator.postConstructMe(ClazzCreator.java:329) at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:377) at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:461) at org.glassfish.hk2.runlevel.internal.AsyncRunLevelContext.findOrCreate(AsyncRunLevelContext.java:227) at org.glassfish.hk2.runlevel.RunLevelContext.findOrCreate(RunLevelContext.java:84) at org.jvnet.hk2.internal.Utilities.createService(Utilities.java:2258) at org.jvnet.hk2.internal.ServiceHandleImpl.getService(ServiceHandleImpl.java:105) at org.jvnet.hk2.internal.ServiceHandleImpl.getService(ServiceHandleImpl.java:87) at org.glassfish.hk2.runlevel.internal.CurrentTaskFuture$QueueRunner.oneJob(CurrentTaskFuture.java:1162) at org.glassfish.hk2.runlevel.internal.CurrentTaskFuture$QueueRunner.run(CurrentTaskFuture.java:1147) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)

login.conf:

    spnego-server {
    com.sun.security.auth.module.Krb5LoginModule required
       useKeyTab=true
       keyTab="ax.keytab"
       principal=development
    storeKey=true;
};

krb5.conf

[libdefaults]
    default_realm = LOC.COM
    default_tkt_enctypes = aes256-cts aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
    default_tgs_enctypes = aes256-cts aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
    permitted_enctypes = aes256-cts aes128-cts rc4-hmac des3-cbc-sha1 des-cbc-md5 des-cbc-crc
    forwardable = true
    udp_preference_limit = 1 

[realms] 
    LOC.COM = { KDC = ax.loc.com default_domain = LOC.COM } 

[domain_realm] 
    .loc.com = LOC.COM
    loc.com = LOC.COM
    ax.loc.com = AX.LOC.COM

Answer:

You need to reformat the [realms] section of krb5.conf like this:

[realms] 
    LOC.COM = {
        KDC = ax.loc.com
        default_domain = LOC.COM
    }

There are some changes to krb5.conf parsing in JDK8, and JDK-7184246 : Simplify Config.get() of krb5 looks interesting. The diff shows significant changes to the line-by-line processing of krb5.conf. This seems to have made your file which was parseable under JDK7, unparsable under JDK8. I think it now wants to see closing braces on their own lines.

I don't think there's a strict specification for krb5.conf but most examples show braces on their own lines and only one assignment per-line.

Question:

I found this guide for developing your own Server Authentication Module (SAM) for Glassfish: http://docs.oracle.com/cd/E18930_01/html/821-2418/gizel.html

It seems pretty straightforward to verify some credentials (in HTTP Auth headers for instance), but my question is this:

Can I develop my SAM in such a way that I can forward the user to a specific page if he's not logged in?

Here's the example from the guide:

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");
       }
   }
  }

Answer:

Yes, you can do that in the validateRequest method.

Here is a simple example:

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");
}

It might be better to do it inside of a custom LoginModule (if you already know what that is), but I guess this depends on your requirements.

See also:

Question:

I made a Form Realm like this:

Db have entries for users(and their groups)

web.xml

    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>TipRealm</realm-name>
        <form-login-config>
            <form-login-page>/login.xhtml</form-login-page>
            <form-error-page>/login.xhtml</form-error-page>
        </form-login-config>
    </login-config>
    <security-role>
        <role-name>User</role-name>
    </security-role>
    <welcome-file-list>
        <welcome-file>redirect.xhtml</welcome-file>
    </welcome-file-list>

So when I try to login with a valid user email and password, this line is printed on the console (and nothing else):

Warning: WEB9102: Web Login Failed: com.sun.enterprise.security.auth.login.common.LoginException: Login failed: No LoginModules configured for jdbcTipRealm

any idea where the problem is?


Answer:

I fix it by change tables and columns names to lowercase, and setting Digest Algorithm: to none