WebSocket authentication
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 20 Jul 2022 06:38:50 +0000 (08:38 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 20 Jul 2022 06:38:50 +0000 (08:38 +0200)
12 files changed:
org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java
org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeRequest.java [new file with mode: 0644]
org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeResponse.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java
org.argeo.cms/src/org/argeo/cms/client/SpnegoHttpClient.java
org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java
org.argeo.cms/src/org/argeo/cms/internal/http/AbstractHttpAuthenticator.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java

index 9cb48b212d38b5db6054c6fefac76dc00a9a00e3..dd6467216f3340c67e2a3c20a2d7cc9525aacd7a 100644 (file)
@@ -14,6 +14,8 @@ import javax.servlet.http.HttpServletResponse;
 import org.argeo.api.cms.CmsAuth;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
 import org.argeo.cms.auth.RemoteAuthUtils;
 import org.argeo.cms.servlet.internal.HttpUtils;
 import org.osgi.framework.Bundle;
@@ -29,6 +31,9 @@ public class CmsServletContext extends ServletContextHelper {
        // use CMS bundle for resources
        private Bundle bundle = FrameworkUtil.getBundle(getClass());
 
+       private final String httpAuthRealm = "Argeo";
+       private final boolean forceBasic = false;
+
        public void init(Map<String, String> properties) {
 
        }
@@ -41,17 +46,24 @@ public class CmsServletContext extends ServletContextHelper {
        public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException {
                if (log.isTraceEnabled())
                        HttpUtils.logRequestHeaders(log, request);
+               RemoteAuthRequest remoteAuthRequest = new ServletHttpRequest(request);
+               RemoteAuthResponse remoteAuthResponse = new ServletHttpResponse(response);
                ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
                Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
                LoginContext lc;
                try {
-                       lc = CmsAuth.USER.newLoginContext(
-                                       new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
+                       lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse));
                        lc.login();
                } catch (LoginException e) {
-                       lc = processUnauthorized(request, response);
-                       if (log.isTraceEnabled())
-                               HttpUtils.logResponseHeaders(log, response);
+                       // FIXME better analyse failure so as not to try endlessly
+                       if (authIsRequired(remoteAuthRequest, remoteAuthResponse)) {
+                               int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthResponse, httpAuthRealm, forceBasic);
+                               response.setStatus(statusCode);
+                               return false;
+
+                       } else {
+                               lc = RemoteAuthUtils.anonymousLogin(remoteAuthRequest, remoteAuthResponse);
+                       }
                        if (lc == null)
                                return false;
                } finally {
@@ -59,13 +71,12 @@ public class CmsServletContext extends ServletContextHelper {
                }
 
                Subject subject = lc.getSubject();
-               // log.debug("SERVLET CONTEXT: "+subject);
                Subject.doAs(subject, new PrivilegedAction<Void>() {
 
                        @Override
                        public Void run() {
                                // TODO also set login context in order to log out ?
-                               RemoteAuthUtils.configureRequestSecurity(new ServletHttpRequest(request));
+                               RemoteAuthUtils.configureRequestSecurity(remoteAuthRequest);
                                return null;
                        }
 
@@ -78,24 +89,28 @@ public class CmsServletContext extends ServletContextHelper {
                RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request));
        }
 
-       protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
-               // anonymous
-               ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
-               try {
-                       Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
-                       LoginContext lc = CmsAuth.ANONYMOUS.newLoginContext(
-                                       new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
-                       lc.login();
-                       return lc;
-               } catch (LoginException e1) {
-                       if (log.isDebugEnabled())
-                               log.error("Cannot log in as anonymous", e1);
-                       return null;
-               } finally {
-                       Thread.currentThread().setContextClassLoader(currentContextClassLoader);
-               }
+       protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+               return false;
        }
 
+//     protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
+//             // anonymous
+//             ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
+//             try {
+//                     Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
+//                     LoginContext lc = CmsAuth.ANONYMOUS.newLoginContext(
+//                                     new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response)));
+//                     lc.login();
+//                     return lc;
+//             } catch (LoginException e1) {
+//                     if (log.isDebugEnabled())
+//                             log.error("Cannot log in as anonymous", e1);
+//                     return null;
+//             } finally {
+//                     Thread.currentThread().setContextClassLoader(currentContextClassLoader);
+//             }
+//     }
+
        @Override
        public URL getResource(String name) {
                // TODO make it more robust and versatile
index bf1ddcf88d08e559b61a0cab0dd21292288dec1b..cd28b6e75d2f7d4fb6bb4277cfc2998d3587e5da 100644 (file)
@@ -1,40 +1,42 @@
 package org.argeo.cms.servlet;
 
-import javax.security.auth.login.LoginContext;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.argeo.cms.auth.SpnegoLoginModule;
-import org.argeo.util.http.HttpHeader;
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
 
 /** Servlet context forcing authentication. */
 public class PrivateWwwAuthServletContext extends CmsServletContext {
        // TODO make it configurable
-       private final String httpAuthRealm = "Argeo";
-       private final boolean forceBasic = false;
-
-       @Override
-       protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
-               askForWwwAuth(request, response);
-               return null;
+//     private final String httpAuthRealm = "Argeo";
+//     private final boolean forceBasic = false;
+       
+       protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest,
+                       RemoteAuthResponse remoteAuthResponse) {
+               return true;
        }
 
-       protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) {
-               // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
-               // realm=\"" + httpAuthRealm + "\"");
-               if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO
-                       response.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(), HttpHeader.NEGOTIATE);
-               else
-                       response.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(),
-                                       HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + httpAuthRealm + "\"");
 
-               // response.setDateHeader("Date", System.currentTimeMillis());
-               // response.setDateHeader("Expires", System.currentTimeMillis() + (24 *
-               // 60 * 60 * 1000));
-               // response.setHeader("Accept-Ranges", "bytes");
-               // response.setHeader("Connection", "Keep-Alive");
-               // response.setHeader("Keep-Alive", "timeout=5, max=97");
-               // response.setContentType("text/html; charset=UTF-8");
-               response.setStatus(401);
-       }
+//     @Override
+//     protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) {
+//             askForWwwAuth(request, response);
+//             return null;
+//     }
+//
+//     protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) {
+//             // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
+//             // realm=\"" + httpAuthRealm + "\"");
+//             if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO
+//                     response.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(), HttpHeader.NEGOTIATE);
+//             else
+//                     response.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(),
+//                                     HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + httpAuthRealm + "\"");
+//
+//             // response.setDateHeader("Date", System.currentTimeMillis());
+//             // response.setDateHeader("Expires", System.currentTimeMillis() + (24 *
+//             // 60 * 60 * 1000));
+//             // response.setHeader("Accept-Ranges", "bytes");
+//             // response.setHeader("Connection", "Keep-Alive");
+//             // response.setHeader("Keep-Alive", "timeout=5, max=97");
+//             // response.setContentType("text/html; charset=UTF-8");
+//             response.setStatus(401);
+//     }
 }
index 880eb0ed5b7a8b1b9e381d70e362efb501a28a70..279b610b6edf8cb9d0ef57943cde4ae73762eafb 100644 (file)
@@ -1,11 +1,12 @@
 package org.argeo.cms.websocket.server;
 
-import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.security.auth.Subject;
 import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
 import javax.websocket.Extension;
 import javax.websocket.HandshakeResponse;
 import javax.websocket.server.HandshakeRequest;
@@ -14,10 +15,12 @@ import javax.websocket.server.ServerEndpointConfig.Configurator;
 
 import org.argeo.api.cms.CmsAuth;
 import org.argeo.api.cms.CmsLog;
-import org.argeo.api.cms.CmsState;
 import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
 import org.argeo.cms.auth.RemoteAuthSession;
-import org.argeo.cms.servlet.ServletHttpSession;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.servlet.CmsServletContext;
 
 /**
  * <strong>Disabled until third party issues are solved.</strong>. Customises
@@ -29,16 +32,6 @@ public class CmsWebSocketConfigurator extends Configurator {
 
        private final static CmsLog log = CmsLog.getLog(CmsWebSocketConfigurator.class);
        final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-       
-       private CmsState cmsState;
-       
-       public void start() {
-               
-       }
-       
-       public void stop() {
-               
-       }
 
        @Override
        public boolean checkOrigin(String originHeaderValue) {
@@ -76,34 +69,56 @@ public class CmsWebSocketConfigurator extends Configurator {
 
        @Override
        public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
-               if (true)
-                       return;
+//             if (true)
+//                     return;
 
-               RemoteAuthSession httpSession = new ServletHttpSession(
-                               (javax.servlet.http.HttpSession) request.getHttpSession());
-               if (log.isDebugEnabled() && httpSession != null)
-                       log.debug("Web socket HTTP session id: " + httpSession.getId());
+               WebSocketHandshakeRequest remoteAuthRequest = new WebSocketHandshakeRequest(request);
+               WebSocketHandshakeResponse remoteAuthResponse = new WebSocketHandshakeResponse(response);
+//             RemoteAuthSession httpSession = new ServletHttpSession(
+//                             (javax.servlet.http.HttpSession) request.getHttpSession());
+               RemoteAuthSession remoteAuthSession = remoteAuthRequest.getSession();
+               if (log.isDebugEnabled() && remoteAuthSession != null)
+                       log.debug("Web socket HTTP session id: " + remoteAuthSession.getId());
 
-               if (httpSession == null) {
-                       rejectResponse(response, null);
-               }
+//             if (remoteAuthSession == null) {
+//                     rejectResponse(response, null);
+//             }
+               ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
+               Thread.currentThread().setContextClassLoader(CmsServletContext.class.getClassLoader());
+               LoginContext lc;
                try {
-                       LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, new RemoteAuthCallbackHandler(httpSession));
+                       lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse));
                        lc.login();
-                       if (log.isDebugEnabled())
-                               log.debug("Web socket logged-in as " + lc.getSubject());
-                       Subject.doAs(lc.getSubject(), new PrivilegedAction<Void>() {
+               } catch (LoginException e) {
+                       // FIXME better analyse failure so as not to try endlessly
+                       if (authIsRequired(remoteAuthRequest, remoteAuthResponse)) {
+                               int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthResponse, "Argeo", true);
+                               remoteAuthResponse.setHeader("Status-Code", Integer.toString(statusCode));
+                               return;
+                       } else {
+                               lc = RemoteAuthUtils.anonymousLogin(remoteAuthRequest, remoteAuthResponse);
+                       }
+                       if (lc == null)
+                               rejectResponse(response, e);
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
+               }
 
-                               @Override
-                               public Void run() {
-                                       sec.getUserProperties().put(REMOTE_USER, AccessController.getContext());
-                                       return null;
-                               }
+               Subject subject = lc.getSubject();
+               Subject.doAs(subject, new PrivilegedAction<Void>() {
 
-                       });
-               } catch (Exception e) {
-                       rejectResponse(response, e);
-               }
+                       @Override
+                       public Void run() {
+                               // TODO also set login context in order to log out ?
+                               RemoteAuthUtils.configureRequestSecurity(remoteAuthRequest);
+                               return null;
+                       }
+
+               });
+       }
+
+       protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest, RemoteAuthResponse remoteAuthResponse) {
+               return true;
        }
 
        /**
@@ -113,6 +128,7 @@ public class CmsWebSocketConfigurator extends Configurator {
         * @param e can be null
         */
        protected void rejectResponse(HandshakeResponse response, Exception e) {
+               response.getHeaders().put(HandshakeResponse.SEC_WEBSOCKET_ACCEPT, new ArrayList<String>());
                // violent implementation, as suggested in
                // https://stackoverflow.com/questions/21763829/jsr-356-how-to-abort-a-websocket-connection-during-the-handshake
 //             throw new IllegalStateException("Web socket cannot be authenticated");
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeRequest.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeRequest.java
new file mode 100644 (file)
index 0000000..31bcf92
--- /dev/null
@@ -0,0 +1,82 @@
+package org.argeo.cms.websocket.server;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+import javax.servlet.http.HttpSession;
+import javax.websocket.server.HandshakeRequest;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthSession;
+import org.argeo.cms.servlet.ServletHttpSession;
+
+public class WebSocketHandshakeRequest implements RemoteAuthRequest {
+       private final HandshakeRequest handshakeRequest;
+       private final HttpSession httpSession;
+
+       private Map<String, Object> attributes = new HashMap<>();
+
+       public WebSocketHandshakeRequest(HandshakeRequest handshakeRequest) {
+               Objects.requireNonNull(handshakeRequest);
+               this.handshakeRequest = handshakeRequest;
+               this.httpSession = (HttpSession) handshakeRequest.getHttpSession();
+//             Objects.requireNonNull(this.httpSession);
+       }
+
+       @Override
+       public RemoteAuthSession getSession() {
+               if (httpSession == null)
+                       return null;
+               return new ServletHttpSession(httpSession);
+       }
+
+       @Override
+       public RemoteAuthSession createSession() {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public Locale getLocale() {
+               // TODO check Accept-Language header
+               return Locale.getDefault();
+       }
+
+       @Override
+       public Object getAttribute(String key) {
+               return attributes.get(key);
+       }
+
+       @Override
+       public void setAttribute(String key, Object object) {
+               attributes.put(key, object);
+       }
+
+       @Override
+       public String getHeader(String key) {
+               List<String> values = handshakeRequest.getHeaders().get(key);
+               if (values.size() == 0)
+                       return null;
+               if (values.size() > 1)
+                       throw new IllegalStateException("More that one value for " + key + ": " + values);
+               return values.get(0);
+       }
+
+       @Override
+       public String getRemoteAddr() {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public int getLocalPort() {
+               throw new UnsupportedOperationException();
+       }
+
+       @Override
+       public int getRemotePort() {
+               throw new UnsupportedOperationException();
+       }
+
+}
diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeResponse.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeResponse.java
new file mode 100644 (file)
index 0000000..3a978c8
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.cms.websocket.server;
+
+import java.util.Collections;
+
+import javax.websocket.HandshakeResponse;
+
+import org.argeo.cms.auth.RemoteAuthResponse;
+
+public class WebSocketHandshakeResponse implements RemoteAuthResponse {
+       private final HandshakeResponse handshakeResponse;
+
+       public WebSocketHandshakeResponse(HandshakeResponse handshakeResponse) {
+               this.handshakeResponse = handshakeResponse;
+       }
+
+       @Override
+       public void setHeader(String key, String value) {
+               handshakeResponse.getHeaders().put(key, Collections.singletonList(value));
+
+       }
+
+}
index be5d0e15e966355c55cb6e8ee70a4d980d729ae8..9720812e0f2e7ad5e8b3987fff689216bbce0f06 100644 (file)
@@ -9,6 +9,7 @@ public interface RemoteAuthRequest {
 
        RemoteAuthSession getSession();
 
+       @Deprecated
        RemoteAuthSession createSession();
 
        Locale getLocale();
index cf30d8a715ea72cca0d8916ce9d9925081e478e8..e6c425f692f6455069953968fe99e57144e24014 100644 (file)
@@ -8,9 +8,15 @@ import java.util.function.Supplier;
 
 import javax.security.auth.Subject;
 import javax.security.auth.kerberos.KerberosTicket;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
 
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsLog;
 import org.argeo.api.cms.CmsSession;
+import org.argeo.cms.internal.http.CmsAuthenticator;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
+import org.argeo.util.http.HttpHeader;
 import org.ietf.jgss.GSSContext;
 import org.ietf.jgss.GSSException;
 import org.ietf.jgss.GSSManager;
@@ -19,6 +25,8 @@ import org.ietf.jgss.Oid;
 
 /** Remote authentication utilities. */
 public class RemoteAuthUtils {
+       private final static CmsLog log = CmsLog.getLog(RemoteAuthUtils.class);
+
        static final String REMOTE_USER = "org.osgi.service.http.authentication.remote.user";
        private final static Oid KERBEROS_OID;
 //     private final static Oid KERB_V5_OID, KRB5_PRINCIPAL_NAME_OID;
@@ -117,4 +125,44 @@ public class RemoteAuthUtils {
                        }
                });
        }
+
+       public static LoginContext anonymousLogin(RemoteAuthRequest remoteAuthRequest,
+                       RemoteAuthResponse remoteAuthResponse) {
+               // anonymous
+               ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
+               try {
+                       Thread.currentThread().setContextClassLoader(CmsAuthenticator.class.getClassLoader());
+                       LoginContext lc = CmsAuth.ANONYMOUS
+                                       .newLoginContext(new RemoteAuthCallbackHandler(remoteAuthRequest, remoteAuthResponse));
+                       lc.login();
+                       return lc;
+               } catch (LoginException e1) {
+                       if (log.isDebugEnabled())
+                               log.error("Cannot log in as anonymous", e1);
+                       return null;
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentContextClassLoader);
+               }
+       }
+
+       public static int askForWwwAuth(RemoteAuthResponse remoteAuthResponse, String realm, boolean forceBasic) {
+               // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
+               // realm=\"" + httpAuthRealm + "\"");
+               if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO
+                       remoteAuthResponse.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(), HttpHeader.NEGOTIATE);
+               else
+                       remoteAuthResponse.setHeader(HttpHeader.WWW_AUTHENTICATE.getName(),
+                                       HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + realm + "\"");
+
+               // response.setDateHeader("Date", System.currentTimeMillis());
+               // response.setDateHeader("Expires", System.currentTimeMillis() + (24 *
+               // 60 * 60 * 1000));
+               // response.setHeader("Accept-Ranges", "bytes");
+               // response.setHeader("Connection", "Keep-Alive");
+               // response.setHeader("Keep-Alive", "timeout=5, max=97");
+               // response.setContentType("text/html; charset=UTF-8");
+
+               return 401;
+       }
+
 }
index d801b5e57978aed234b519c0a657e9d2861fd240..6bf3fc985a8d1040edfbab0692a4a4e8cd991dae 100644 (file)
@@ -77,21 +77,23 @@ public class RemoteSessionLoginModule implements LoginModule {
                        authorization = (Authorization) request.getAttribute(RemoteAuthRequest.AUTHORIZATION);
                        if (authorization == null) {// search by session ID
                                RemoteAuthSession httpSession = request.getSession();
-                               if (httpSession == null) {
-                                       // TODO make sure this is always safe
-                                       if (log.isTraceEnabled())
-                                               log.trace("Create http session");
-                                       httpSession = request.createSession();
-                               }
-                               String httpSessionId = httpSession.getId();
+//                             if (httpSession == null) {
+//                                     // TODO make sure this is always safe
+//                                     if (log.isTraceEnabled())
+//                                             log.trace("Create http session");
+//                                     httpSession = request.createSession();
+//                             }
+                               if (httpSession != null) {
+                                       String httpSessionId = httpSession.getId();
 //                             if (log.isTraceEnabled())
 //                                     log.trace("HTTP login: " + request.getPathInfo() + " #" + httpSessionId);
-                               CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId);
-                               if (cmsSession != null && !cmsSession.isAnonymous()) {
-                                       authorization = cmsSession.getAuthorization();
-                                       locale = cmsSession.getLocale();
-                                       if (log.isTraceEnabled())
-                                               log.trace("Retrieved authorization from " + cmsSession);
+                                       CmsSessionImpl cmsSession = CmsContextImpl.getCmsContext().getCmsSessionByLocalId(httpSessionId);
+                                       if (cmsSession != null && !cmsSession.isAnonymous()) {
+                                               authorization = cmsSession.getAuthorization();
+                                               locale = cmsSession.getLocale();
+                                               if (log.isTraceEnabled())
+                                                       log.trace("Retrieved authorization from " + cmsSession);
+                                       }
                                }
                        }
                        sharedState.put(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST, request);
index e530f5550f17b0af40d5bcbae6b26575c934ad32..21a32940bfed6294e85c4f6a10b1846f4fcb1665 100644 (file)
@@ -3,6 +3,7 @@ package org.argeo.cms.client;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.http.HttpClient;
+import java.net.http.HttpHeaders;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.BodyHandler;
@@ -18,11 +19,12 @@ import javax.security.auth.Subject;
 import javax.security.auth.login.LoginContext;
 
 import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.util.http.HttpHeader;
 
 public class SpnegoHttpClient {
        public static void main(String[] args) throws MalformedURLException {
 //             String principal = System.getProperty("javax.security.auth.login.name");
-               if (args.length == 0 ) {
+               if (args.length == 0) {
                        System.err.println("usage: java -Djavax.security.auth.login.name=<principal@REALM> "
                                        + SpnegoHttpClient.class.getName() + " <url>");
                        System.exit(1);
@@ -31,7 +33,7 @@ public class SpnegoHttpClient {
                String url = args[0];
                URL u = new URL(url);
                String server = u.getHost();
-               
+
                URL jaasUrl = SpnegoHttpClient.class.getResource("jaas.cfg");
                System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
                try {
@@ -53,7 +55,7 @@ public class SpnegoHttpClient {
                        String token = RemoteAuthUtils.getGssToken(lc.getSubject(), "HTTP", server);
 
                        HttpRequest request = HttpRequest.newBuilder().uri(u.toURI()) //
-                                       .header("Authorization", "Negotiate " + token) //
+                                       .header(HttpHeader.AUTHORIZATION.getName(), HttpHeader.NEGOTIATE + " " + token) //
                                        .build();
                        BodyHandler<String> bodyHandler = BodyHandlers.ofString();
                        HttpResponse<String> response = httpClient.send(request, bodyHandler);
index aab806a46d1059b77f3ebc59a39bdcd020ca5529..0787b04780c4d00890869514f263d9dbbaf52c32 100644 (file)
@@ -1,12 +1,18 @@
 package org.argeo.cms.client;
 
 import java.net.URI;
+import java.net.URL;
 import java.net.http.HttpClient;
 import java.net.http.WebSocket;
 import java.nio.ByteBuffer;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 
+import javax.security.auth.login.LoginContext;
+
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.util.http.HttpHeader;
+
 /** Tests connectivity to the web socket server. */
 public class WebSocketEventClient {
 
@@ -33,8 +39,17 @@ public class WebSocketEventClient {
 
                };
 
+               // SPNEGO
+               URL jaasUrl = SpnegoHttpClient.class.getResource("jaas.cfg");
+               System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
+               LoginContext lc = new LoginContext("SINGLE_USER");
+               lc.login();
+               String token = RemoteAuthUtils.getGssToken(lc.getSubject(), "HTTP", uri.getHost());
+
                HttpClient client = HttpClient.newHttpClient();
-               CompletableFuture<WebSocket> ws = client.newWebSocketBuilder().buildAsync(uri, listener);
+               CompletableFuture<WebSocket> ws = client.newWebSocketBuilder()
+                               .header(HttpHeader.AUTHORIZATION.getName(), HttpHeader.NEGOTIATE + " " + token)
+                               .buildAsync(uri, listener);
                WebSocket webSocket = ws.get();
                webSocket.request(Long.MAX_VALUE);
 
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/AbstractHttpAuthenticator.java b/org.argeo.cms/src/org/argeo/cms/internal/http/AbstractHttpAuthenticator.java
new file mode 100644 (file)
index 0000000..418b068
--- /dev/null
@@ -0,0 +1,18 @@
+package org.argeo.cms.internal.http;
+
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsLog;
+import org.argeo.cms.auth.RemoteAuthCallbackHandler;
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+import org.argeo.cms.auth.SpnegoLoginModule;
+import org.argeo.util.http.HttpHeader;
+
+public class AbstractHttpAuthenticator {
+       private final static CmsLog log = CmsLog.getLog(AbstractHttpAuthenticator.class);
+
+
+}
index e15d074fe4a9435e626cf9f367dd3df2eed7ac0e..61d2d306604e950ea83e66f207d100216584bec2 100644 (file)
@@ -8,8 +8,10 @@ import org.argeo.api.cms.CmsAuth;
 import org.argeo.api.cms.CmsLog;
 import org.argeo.cms.auth.CurrentUser;
 import org.argeo.cms.auth.RemoteAuthCallbackHandler;
-import org.argeo.cms.auth.SpnegoLoginModule;
-import org.argeo.util.http.HttpHeader;
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.util.CurrentSubject;
 
 import com.sun.net.httpserver.Authenticator;
 import com.sun.net.httpserver.HttpExchange;
@@ -29,22 +31,21 @@ public class CmsAuthenticator extends Authenticator {
        public Result authenticate(HttpExchange exch) {
 //             if (log.isTraceEnabled())
 //                     HttpUtils.logRequestHeaders(log, request);
-               RemoteAuthHttpExchange remoteAuthHttpExchange = new RemoteAuthHttpExchange(exch);
+               RemoteAuthHttpExchange remoteAuthExchange = new RemoteAuthHttpExchange(exch);
                ClassLoader currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
                Thread.currentThread().setContextClassLoader(CmsAuthenticator.class.getClassLoader());
                LoginContext lc;
                try {
-                       lc = CmsAuth.USER
-                                       .newLoginContext(new RemoteAuthCallbackHandler(remoteAuthHttpExchange, remoteAuthHttpExchange));
+                       lc = CmsAuth.USER.newLoginContext(new RemoteAuthCallbackHandler(remoteAuthExchange, remoteAuthExchange));
                        lc.login();
                } catch (LoginException e) {
                        // FIXME better analyse failure so as not to try endlessly
-                       if (authIsRequired(exch)) {
-                               return askForWwwAuth(exch);
+                       if (authIsRequired(remoteAuthExchange,remoteAuthExchange)) {
+                               int statusCode = RemoteAuthUtils.askForWwwAuth(remoteAuthExchange, httpAuthRealm, forceBasic);
+                               return new Authenticator.Retry(statusCode);
+
                        } else {
-                               lc = processUnauthorized(exch);
-//                     if (log.isTraceEnabled())
-//                             HttpUtils.logResponseHeaders(log, response);
+                               lc = RemoteAuthUtils.anonymousLogin(remoteAuthExchange, remoteAuthExchange);
                        }
                        if (lc == null)
                                return new Authenticator.Failure(403);
@@ -54,6 +55,10 @@ public class CmsAuthenticator extends Authenticator {
 
                Subject subject = lc.getSubject();
 
+               CurrentSubject.callAs(subject, () -> {
+                       RemoteAuthUtils.configureRequestSecurity(remoteAuthExchange);
+                       return null;
+               });
 //             Subject.doAs(subject, new PrivilegedAction<Void>() {
 //
 //                     @Override
@@ -69,48 +74,9 @@ public class CmsAuthenticator extends Authenticator {
                return new Authenticator.Success(httpPrincipal);
        }
 
-       protected boolean authIsRequired(HttpExchange httpExchange) {
+       protected boolean authIsRequired(RemoteAuthRequest remoteAuthRequest,
+                       RemoteAuthResponse remoteAuthResponse) {
                return true;
        }
 
-       protected LoginContext processUnauthorized(HttpExchange httpExchange) {
-
-               RemoteAuthHttpExchange remoteAuthExchange = new RemoteAuthHttpExchange(httpExchange);
-               // anonymous
-               ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
-               try {
-                       Thread.currentThread().setContextClassLoader(CmsAuthenticator.class.getClassLoader());
-                       LoginContext lc = CmsAuth.ANONYMOUS
-                                       .newLoginContext(new RemoteAuthCallbackHandler(remoteAuthExchange, remoteAuthExchange));
-                       lc.login();
-                       return lc;
-               } catch (LoginException e1) {
-                       if (log.isDebugEnabled())
-                               log.error("Cannot log in as anonymous", e1);
-                       return null;
-               } finally {
-                       Thread.currentThread().setContextClassLoader(currentContextClassLoader);
-               }
-       }
-
-       protected Authenticator.Retry askForWwwAuth(HttpExchange httpExchange) {
-               // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic
-               // realm=\"" + httpAuthRealm + "\"");
-               if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO
-                       httpExchange.getResponseHeaders().set(HttpHeader.WWW_AUTHENTICATE.getName(), HttpHeader.NEGOTIATE);
-               else
-                       httpExchange.getResponseHeaders().set(HttpHeader.WWW_AUTHENTICATE.getName(),
-                                       HttpHeader.BASIC + " " + HttpHeader.REALM + "=\"" + httpAuthRealm + "\"");
-
-               // response.setDateHeader("Date", System.currentTimeMillis());
-               // response.setDateHeader("Expires", System.currentTimeMillis() + (24 *
-               // 60 * 60 * 1000));
-               // response.setHeader("Accept-Ranges", "bytes");
-               // response.setHeader("Connection", "Keep-Alive");
-               // response.setHeader("Keep-Alive", "timeout=5, max=97");
-               // response.setContentType("text/html; charset=UTF-8");
-
-               return new Authenticator.Retry(401);
-       }
-
 }