From: Mathieu Baudier Date: Wed, 20 Jul 2022 06:38:50 +0000 (+0200) Subject: WebSocket authentication X-Git-Tag: v2.3.10~108 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=c2eb0b8ebd1c9df4923f5fb2298a4ae04237f65d;p=lgpl%2Fargeo-commons.git WebSocket authentication --- diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java index 9cb48b212..dd6467216 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/CmsServletContext.java @@ -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 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() { @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 diff --git a/org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java index bf1ddcf88..cd28b6e75 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java @@ -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); +// } } diff --git a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java index 880eb0ed5..279b610b6 100644 --- a/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/CmsWebSocketConfigurator.java @@ -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; /** * Disabled until third party issues are solved.. 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() { + } 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() { - }); - } 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()); // 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 index 000000000..31bcf9298 --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeRequest.java @@ -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 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 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 index 000000000..3a978c8ae --- /dev/null +++ b/org.argeo.cms.ee/src/org/argeo/cms/websocket/server/WebSocketHandshakeResponse.java @@ -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)); + + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java index be5d0e15e..9720812e0 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthRequest.java @@ -9,6 +9,7 @@ public interface RemoteAuthRequest { RemoteAuthSession getSession(); + @Deprecated RemoteAuthSession createSession(); Locale getLocale(); diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java index cf30d8a71..e6c425f69 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteAuthUtils.java @@ -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; + } + } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java index d801b5e57..6bf3fc985 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/RemoteSessionLoginModule.java @@ -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); diff --git a/org.argeo.cms/src/org/argeo/cms/client/SpnegoHttpClient.java b/org.argeo.cms/src/org/argeo/cms/client/SpnegoHttpClient.java index e530f5550..21a32940b 100644 --- a/org.argeo.cms/src/org/argeo/cms/client/SpnegoHttpClient.java +++ b/org.argeo.cms/src/org/argeo/cms/client/SpnegoHttpClient.java @@ -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= " + SpnegoHttpClient.class.getName() + " "); 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 bodyHandler = BodyHandlers.ofString(); HttpResponse response = httpClient.send(request, bodyHandler); diff --git a/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java b/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java index aab806a46..0787b0478 100644 --- a/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java +++ b/org.argeo.cms/src/org/argeo/cms/client/WebSocketEventClient.java @@ -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 ws = client.newWebSocketBuilder().buildAsync(uri, listener); + CompletableFuture 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 index 000000000..418b0689d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/AbstractHttpAuthenticator.java @@ -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); + + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java index e15d074fe..61d2d3066 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/http/CmsAuthenticator.java @@ -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() { // // @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); - } - }