From b6828be09b7919f0d6447af0bebc9bca127d74f6 Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Mon, 4 Aug 2014 00:24:57 +0200 Subject: [PATCH] [Major] cleaned up stupid use of forms for POST and added proper sec --- .../rest/DefaultStrolchSessionHandler.java | 78 ++++++++----------- .../strolch/rest/StrolchRestfulClasses.java | 7 +- .../strolch/rest/StrolchRestfulConstants.java | 24 ++++++ .../strolch/rest/StrolchSessionHandler.java | 2 +- .../rest/endpoint/AuthenticationService.java | 46 +++++------ .../filters/AuthenicationRequestFilter.java | 49 ++++++++++++ .../filters/AuthenicationResponseFilter.java | 34 ++++++++ .../{form/LoginForm.java => model/Login.java} | 17 ++-- .../LogoutForm.java => model/Logout.java} | 17 ++-- 9 files changed, 185 insertions(+), 89 deletions(-) create mode 100644 src/main/java/li/strolch/rest/StrolchRestfulConstants.java create mode 100644 src/main/java/li/strolch/rest/filters/AuthenicationRequestFilter.java create mode 100644 src/main/java/li/strolch/rest/filters/AuthenicationResponseFilter.java rename src/main/java/li/strolch/rest/{form/LoginForm.java => model/Login.java} (77%) rename src/main/java/li/strolch/rest/{form/LogoutForm.java => model/Logout.java} (77%) diff --git a/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java b/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java index 1d40f24e8..7b3c87df6 100644 --- a/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java +++ b/src/main/java/li/strolch/rest/DefaultStrolchSessionHandler.java @@ -15,6 +15,7 @@ */ package li.strolch.rest; +import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; @@ -31,10 +32,11 @@ import ch.eitchnet.utils.dbc.DBC; */ public class DefaultStrolchSessionHandler extends StrolchComponent implements StrolchSessionHandler { - private static final String PROP_REMEMBER_USER = "rememberUser"; + private static final String SESSION_ORIGIN = "session.origin"; + private static final String PROP_VALIDATE_ORIGIN = "validateOrigin"; private StrolchPrivilegeHandler privilegeHandler; private Map certificateMap; - private boolean rememberUser; + private boolean validateOrigin; /** * @param container @@ -46,7 +48,7 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St @Override public void initialize(ComponentConfiguration configuration) { - this.rememberUser = configuration.getBoolean(PROP_REMEMBER_USER, false); + this.validateOrigin = configuration.getBoolean(PROP_VALIDATE_ORIGIN, false); super.initialize(configuration); } @@ -81,63 +83,47 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St DBC.PRE.assertNotEmpty("Username must be set!", username); DBC.PRE.assertNotNull("Passwort must be set", password); - String userId = getUserId(origin, username); - Certificate certificate; - if (this.rememberUser) { - certificate = this.certificateMap.get(userId); - if (certificate != null) { - this.privilegeHandler.isCertificateValid(certificate); - logger.info("Re-using session for user " + userId + " and sessionId " + certificate.getSessionId()); - return certificate; - } - } - - certificate = this.privilegeHandler.authenticate(username, password); - if (this.rememberUser) - this.certificateMap.put(userId, certificate); - else - this.certificateMap.put(certificate.getAuthToken(), certificate); + Certificate certificate = this.privilegeHandler.authenticate(username, password); + certificate.getSessionDataMap().put(SESSION_ORIGIN, origin); + this.certificateMap.put(certificate.getAuthToken(), certificate); return certificate; } @Override - public Certificate validate(String origin, String username, String sessionId) { - DBC.PRE.assertNotEmpty("Origin must be set!", username); - DBC.PRE.assertNotEmpty("Username must be set!", username); - - Certificate certificate; - if (this.rememberUser) - certificate = this.certificateMap.get(getUserId(origin, username)); - else - certificate = this.certificateMap.get(sessionId); + public Certificate validate(String origin, String authToken) { + DBC.PRE.assertNotEmpty("Origin must be set!", origin); + DBC.PRE.assertNotEmpty("SessionId must be set!", authToken); + Certificate certificate = this.certificateMap.get(authToken); if (certificate == null) - throw new StrolchException("No certificate exists for sessionId " + sessionId); - - if (!certificate.getUsername().equals(username) || !certificate.getAuthToken().equals(sessionId)) { - throw new StrolchException("Illegal request for username " + username + " and sessionId " + sessionId); - } + throw new StrolchException(MessageFormat.format("No certificate exists for sessionId {0}", authToken)); this.privilegeHandler.isCertificateValid(certificate); + + if (this.validateOrigin && !origin.equals(certificate.getSessionDataMap().get(SESSION_ORIGIN))) { + String msg = MessageFormat.format("Illegal request for origin {0} and sessionId {1}", origin, authToken); + throw new StrolchException(msg); + } + return certificate; } @Override public void invalidateSession(String origin, Certificate certificate) { - if (this.rememberUser) - this.certificateMap.remove(getUserId(origin, certificate.getUsername())); - else - this.certificateMap.remove(certificate.getSessionId()); + DBC.PRE.assertNotEmpty("Origin must be set!", origin); + DBC.PRE.assertNotNull("Certificate must bet given!", certificate); + + if (this.validateOrigin && !origin.equals(certificate.getSessionDataMap().get(SESSION_ORIGIN))) { + String msg = MessageFormat.format("Illegal request for origin {0} and sessionId {1}", origin, + certificate.getAuthToken()); + throw new StrolchException(msg); + } + + Certificate removedCert = this.certificateMap.remove(certificate.getAuthToken()); + if (removedCert == null) + logger.error("No session was registered with token " + certificate.getAuthToken()); + this.privilegeHandler.invalidateSession(certificate); } - - /** - * @param origin - * @param username - * @return - */ - private String getUserId(String origin, String username) { - return origin + "_" + username; - } } diff --git a/src/main/java/li/strolch/rest/StrolchRestfulClasses.java b/src/main/java/li/strolch/rest/StrolchRestfulClasses.java index 26cf42cb7..ec43201f4 100644 --- a/src/main/java/li/strolch/rest/StrolchRestfulClasses.java +++ b/src/main/java/li/strolch/rest/StrolchRestfulClasses.java @@ -24,6 +24,8 @@ import li.strolch.rest.endpoint.EnumQuery; import li.strolch.rest.endpoint.Inspector; import li.strolch.rest.endpoint.VersionQuery; import li.strolch.rest.filters.AccessControlResponseFilter; +import li.strolch.rest.filters.AuthenicationRequestFilter; +import li.strolch.rest.filters.AuthenicationResponseFilter; /** * @author Robert von Burg @@ -43,14 +45,13 @@ public class StrolchRestfulClasses { Set> providerClasses = new HashSet<>(); providerClasses.add(StrolchRestfulExceptionMapper.class); providerClasses.add(AccessControlResponseFilter.class); + providerClasses.add(AuthenicationRequestFilter.class); + providerClasses.add(AuthenicationResponseFilter.class); StrolchRestfulClasses.restfulClasses = Collections.unmodifiableSet(restfulClasses); StrolchRestfulClasses.providerClasses = Collections.unmodifiableSet(providerClasses); } - /** - * @return the classes - */ public static Set> getRestfulClasses() { return restfulClasses; } diff --git a/src/main/java/li/strolch/rest/StrolchRestfulConstants.java b/src/main/java/li/strolch/rest/StrolchRestfulConstants.java new file mode 100644 index 000000000..c05b3ac6c --- /dev/null +++ b/src/main/java/li/strolch/rest/StrolchRestfulConstants.java @@ -0,0 +1,24 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.rest; + +/** + * @author Robert von Burg + */ +public class StrolchRestfulConstants { + + public static final String STROLCH_CERTIFICATE = "strolch.certificate"; //$NON-NLS-1$ +} diff --git a/src/main/java/li/strolch/rest/StrolchSessionHandler.java b/src/main/java/li/strolch/rest/StrolchSessionHandler.java index 802341c07..ab228549d 100644 --- a/src/main/java/li/strolch/rest/StrolchSessionHandler.java +++ b/src/main/java/li/strolch/rest/StrolchSessionHandler.java @@ -24,7 +24,7 @@ public interface StrolchSessionHandler { public Certificate authenticate(String origin, String username, byte[] password); - public Certificate validate(String origin, String username, String sessionId); + public Certificate validate(String origin, String authToken); public void invalidateSession(String origin, Certificate certificate); } diff --git a/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java b/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java index f26b9366e..bfd8c00e5 100644 --- a/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java +++ b/src/main/java/li/strolch/rest/endpoint/AuthenticationService.java @@ -16,8 +16,8 @@ package li.strolch.rest.endpoint; import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.BeanParam; import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; @@ -30,9 +30,9 @@ import javax.ws.rs.core.Response.Status; import li.strolch.exception.StrolchException; import li.strolch.rest.RestfulStrolchComponent; import li.strolch.rest.StrolchSessionHandler; -import li.strolch.rest.form.LoginForm; -import li.strolch.rest.form.LogoutForm; +import li.strolch.rest.model.Login; import li.strolch.rest.model.LoginResult; +import li.strolch.rest.model.Logout; import li.strolch.rest.model.LogoutResult; import org.slf4j.Logger; @@ -51,24 +51,24 @@ public class AuthenticationService { private static final Logger logger = LoggerFactory.getLogger(AuthenticationService.class); @Context - HttpServletRequest servletRequest; + HttpServletRequest request; @POST - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - @Path("login") - public Response login(@BeanParam LoginForm loginForm) { + public Response login(Login login) { LoginResult loginResult = new LoginResult(); GenericEntity entity = new GenericEntity(loginResult, LoginResult.class) { }; + try { StringBuilder sb = new StringBuilder(); - if (StringHelper.isEmpty(loginForm.getUsername())) { + if (StringHelper.isEmpty(login.getUsername())) { sb.append("Username was not given. "); } - if (StringHelper.isEmpty(loginForm.getPassword())) { + if (StringHelper.isEmpty(login.getPassword())) { sb.append("Password was not given."); } @@ -79,9 +79,9 @@ public class AuthenticationService { StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getComponent( StrolchSessionHandler.class); - String origin = getOrigin(); - Certificate certificate = sessionHandler.authenticate(origin, loginForm.getUsername(), loginForm - .getPassword().getBytes()); + String origin = request.getRemoteAddr(); + Certificate certificate = sessionHandler.authenticate(origin, login.getUsername(), login.getPassword() + .getBytes()); loginResult.setSessionId(certificate.getAuthToken()); loginResult.setUsername(certificate.getUsername()); @@ -102,17 +102,10 @@ public class AuthenticationService { } } - private String getOrigin() { - if (servletRequest == null) - return "test"; - return servletRequest.getRequestedSessionId(); - } - - @POST - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + @DELETE + @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - @Path("logout") - public Response logout(@BeanParam LogoutForm logoutForm) { + public Response logout(Logout logout) { LogoutResult logoutResult = new LogoutResult(); @@ -121,10 +114,10 @@ public class AuthenticationService { try { StringBuilder sb = new StringBuilder(); - if (StringHelper.isEmpty(logoutForm.getUsername())) { + if (StringHelper.isEmpty(logout.getUsername())) { sb.append("Username was not given."); } - if (StringHelper.isEmpty(logoutForm.getSessionId())) { + if (StringHelper.isEmpty(logout.getSessionId())) { sb.append("SessionId was not given."); } if (sb.length() != 0) { @@ -134,9 +127,8 @@ public class AuthenticationService { StrolchSessionHandler sessionHandlerHandler = RestfulStrolchComponent.getInstance().getComponent( StrolchSessionHandler.class); - String origin = getOrigin(); - Certificate certificate = sessionHandlerHandler.validate(origin, logoutForm.getUsername(), - logoutForm.getSessionId()); + String origin = request.getRemoteAddr(); + Certificate certificate = sessionHandlerHandler.validate(origin, logout.getSessionId()); sessionHandlerHandler.invalidateSession(origin, certificate); return Response.ok().entity(entity).build(); diff --git a/src/main/java/li/strolch/rest/filters/AuthenicationRequestFilter.java b/src/main/java/li/strolch/rest/filters/AuthenicationRequestFilter.java new file mode 100644 index 000000000..5d72b92fc --- /dev/null +++ b/src/main/java/li/strolch/rest/filters/AuthenicationRequestFilter.java @@ -0,0 +1,49 @@ +/** + * + */ +package li.strolch.rest.filters; + +import static li.strolch.rest.StrolchRestfulConstants.STROLCH_CERTIFICATE; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; + +import li.strolch.rest.RestfulStrolchComponent; +import li.strolch.rest.StrolchSessionHandler; +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author Reto Breitenmoser + * @author Robert von Burg + */ +@Provider +public class AuthenicationRequestFilter implements ContainerRequestFilter { + + @Context + HttpServletRequest request; + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + + String sessionId = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); + if (sessionId != null) { + try { + String origin = request.getRemoteAddr(); + StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getComponent( + StrolchSessionHandler.class); + Certificate certificate = sessionHandler.validate(origin, sessionId); + requestContext.setProperty(STROLCH_CERTIFICATE, certificate); + } catch (Exception e) { + requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED) + .entity("User cannot access the resource.").build()); //$NON-NLS-1$ + } + } + } +} diff --git a/src/main/java/li/strolch/rest/filters/AuthenicationResponseFilter.java b/src/main/java/li/strolch/rest/filters/AuthenicationResponseFilter.java new file mode 100644 index 000000000..7aeecc1b9 --- /dev/null +++ b/src/main/java/li/strolch/rest/filters/AuthenicationResponseFilter.java @@ -0,0 +1,34 @@ +/** + * + */ +package li.strolch.rest.filters; + +import static li.strolch.rest.StrolchRestfulConstants.STROLCH_CERTIFICATE; + +import java.io.IOException; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.ext.Provider; + +import ch.eitchnet.privilege.model.Certificate; + +/** + * @author Reto Breitenmoser + * @author Robert von Burg + */ +@Provider +public class AuthenicationResponseFilter implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + + Certificate cert = (Certificate) requestContext.getProperty(STROLCH_CERTIFICATE); + if (cert != null) { + responseContext.getHeaders().add(HttpHeaders.AUTHORIZATION, cert.getAuthToken()); + } + } +} diff --git a/src/main/java/li/strolch/rest/form/LoginForm.java b/src/main/java/li/strolch/rest/model/Login.java similarity index 77% rename from src/main/java/li/strolch/rest/form/LoginForm.java rename to src/main/java/li/strolch/rest/model/Login.java index 72e59ca01..7a573e658 100644 --- a/src/main/java/li/strolch/rest/form/LoginForm.java +++ b/src/main/java/li/strolch/rest/model/Login.java @@ -13,21 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package li.strolch.rest.form; +package li.strolch.rest.model; -import javax.ws.rs.FormParam; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; /** * @author Robert von Burg */ -public class LoginForm { +@XmlAccessorType(XmlAccessType.NONE) +@XmlRootElement(name = "Login") +public class Login { - @FormParam("username") + @XmlAttribute private String username; - @FormParam("password") + @XmlAttribute private String password; - public LoginForm() { + public Login() { // no-arg constructor for JAXB } diff --git a/src/main/java/li/strolch/rest/form/LogoutForm.java b/src/main/java/li/strolch/rest/model/Logout.java similarity index 77% rename from src/main/java/li/strolch/rest/form/LogoutForm.java rename to src/main/java/li/strolch/rest/model/Logout.java index d0fd75560..8fdcc8866 100644 --- a/src/main/java/li/strolch/rest/form/LogoutForm.java +++ b/src/main/java/li/strolch/rest/model/Logout.java @@ -13,21 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package li.strolch.rest.form; +package li.strolch.rest.model; -import javax.ws.rs.FormParam; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; /** * @author Robert von Burg */ -public class LogoutForm { +@XmlAccessorType(XmlAccessType.NONE) +@XmlRootElement(name = "Logout") +public class Logout { - @FormParam("username") + @XmlAttribute private String username; - @FormParam("sessionId") + @XmlAttribute private String sessionId; - public LogoutForm() { + public Logout() { // no-arg constructor for JAXB }