Any suggestions on how to session proof a website? - java

This might be a very odd question, but some hints or guidelines will be very helpful. We want to "session proof" our web site, basically make sure that two different sessions generate the same link structure (e.g. Both user-a and user-b will get the same links in the same web page).
The reason behind this test, is that our web site is generated by a content management solution that will generate different links(URLs) for each different session. That CMS (which is internally built) was fixed to return the same links across sessions.
The other situation with session proofing, is that our caching mechanism (SQUID) is giving TCP_MISSes all the time to our website, which makes us think that the complete site is marked dynamic and the caching server is having to reget all objects all the time.

Are you trying to verify that two different users do in fact see the same structure? One way would be to use something like wget to crawl the whole web site from two different IP addresses, and then compare the resulting trees.

This is what we do at amplafi.com
(h/t See http://randomcoder.com/articles/jsessionid-considered-harmful )
in the web.xml:
<filter>
<filter-name>DisableSessionIdsInUrlFilter</filter-name>
<filter-class>
com.amplafi.web.servlet.DisableSessionIdsInUrlFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>DisableSessionIdsInUrlFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
And this java code:
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
/**
* remove any session id from the Url.
*
*
* Ideally we would like to only remove this container-provided functionality
* only for public portions of the web site (that can be crawled by google)
* or for links that are to be bookmarked.
*
* #author Patrick Moore
*/
public class DisableSessionIdsInUrlFilter implements Filter {
#Override
public void destroy() {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest)) {
chain.doFilter(request, response);
return;
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
/*
* Next, let's invalidate any sessions that are backed by a URL-encoded
* session id. This prevents an attacker from generating a valid link.
* Just because we won't be generating session-encoded links doesn't
* mean someone else won't try
*/
if (httpRequest.isRequestedSessionIdFromURL()) {
HttpSession session = httpRequest.getSession();
if (session != null) {
session.invalidate();
}
}
HttpServletResponseWrapper wrappedResponse = new ResponseWrapper(httpResponse);
chain.doFilter(request, wrappedResponse);
}
#Override
#SuppressWarnings("unused")
public void init(FilterConfig arg0) throws ServletException {
}
/**
* wraps response and prevense jsessionid from being encoded on the output.
*/
private static class ResponseWrapper extends HttpServletResponseWrapper {
ResponseWrapper(HttpServletResponse httpResponse) {
super(httpResponse);
}
#Override
public String encodeRedirectUrl(String uri) {
return uri;
}
#Override
public String encodeRedirectURL(String uri) {
return uri;
}
#Override
public String encodeUrl(String uri) {
return uri;
}
#Override
public String encodeURL(String uri) {
return uri;
}
}
}

If every page in your site has a different link then caching would definitely be broken for the site.
Are you asking how to verify that the links are the same per session?
Or are you asking how to ensure that links are the same per session?
For the former simply browsing from two different browsers and user logins should be sufficient.
As for ensuring the links are the same per session well... That would depend on your implementation which you indicated was inhouse.

Why are your URLs different? Are you storing session IDs in them?
If you are, you should probably move that to a cookie!

You could try using a Load test suite like Pureload to simulate multiple users hitting your site. Pureload can have multiple concurrent simulated users each make the same request and confirm that the results are as expected. Depending on how dynamic your results are this may help you test your bug.

You could write a test in Java using selenium. Add the assertions for the data you expect. Then you could loop over a list of multiple logins to see the test passes for each.
Selenium is pretty easy to get started in and tests can be written in many languages.
See here for details:
http://seleniumhq.org/projects/remote-control/
An example test would be something like this (pseudocode):
public void setUp() throws Exception {
setUp("http://mywebsite.com", "*chrome"); // to run the test in opera replace chrome with opera
}
public void testLoginWithMultipleUsers() {
for(loop over users) {
runLogin(user)
}
}
public void runLogin(User user) throws Exception {
selenium.open("/login.htm");
assertTrue(selenium.isTextPresent("Link1"));
assertTrue(selenium.isTextPresent("2003-2008"));
selenium.open("/account");
assertTrue(selenium.isTextPresent("2003-2008"));
selenium.waitForPageToLoad("30000");
etc...
Selenium provides lots of methods to check if links and other page elements are present and the test can even be recorded in the browser - give it a try!

Related

SlingServletFilter: Get status code before doFilter

I'm trying to make unexisting pages under my domain go to a 404 page. I need to distinguish 404 pages from the other pages. However, I do not know how to do this. And the thing below is not working.
#Component(service = Filter.class,
property = {
"service.ranking=" + Integer.MIN_VALUE})
#SlingServletFilter(scope = {SlingServletFilterScope.REQUEST},
pattern = "/content/foo/.*",
resourceTypes = "cq:Page",
extensions = {"html"},
methods = {"GET"})
public class NotFoundFilter implements Filter {
private static final String DEFAULT_METHOD = "GET";
#Reference
private UrlOperationsManager urlOperationsManager;
#Reference
private RequestResponseFactory requestResponseFactory;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (!(request instanceof SlingHttpServletRequest) ||
!(response instanceof SlingHttpServletResponse)) {
chain.doFilter(request, response);
return;
}
SlingHttpServletResponse slingResponse = (SlingHttpServletResponse) response;
//this condition here is not working since slingResponse has no getStatusCode method.
if(slingResponse.getStatusCode() == 404) {
//do something
}
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
}
You could work around this by implementing your own HttpServletResponseWrapper to save the value and access it later. The Sling implementation is marginally different (at least as far as this particular mechanic is concerned) from the generic Servlet API, which is covered in depth in How can I get the HTTP status code out of a ServletResponse in a ServletFilter?
However, if your intention is to serve a particular error document for a given status code, I'd approach it differently. Assuming you use a Dispatcher, you could have the web server take care of it.
The official AEM project archetype comes with a few simple examples that you could enable if you use Apache. The details will depend on your site structure but the gist is that it's possible to provide a similar configuration using the ErrorDocument directive to point to a cached error page relative to the document root, usually making it use content-editable error pages.
Some errors, especially HTTP 5** family could be a bit more tricky that way in that they usually happen when there's something wrong with AEM itself so it's prudent to make sure a fully static version is always available.

How do i ban a user from a spring web application effectively

I have built a single page webapplication using Angular on the frontend and Spring on the backend. Its a community website and i want to be able to ban misbehaving users the spring way. I have searched stackoverflow to see if there is a thread about this but i could find none.
My attempt to build this functionality is by creating a custom webfilter that filters all incoming requests and checks whether the ip address of the requester is in the blocked ip list. If not then the request gets forwarded, but if it is on the list then an error response is sent back instead.
Here is the code of the filter and an implementing interface:
package RequestPreprocessors;
import DAOs.BanDao;
import Interfaces.IpAddressExtractor;
import Services.ModeratorService;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;
import java.util.logging.Logger;
#WebFilter("/*")
public class IpAddressFilter implements Filter, IpAddressExtractor {
private static final Logger log = Logger.getLogger("IpAddressFilter.class");
private Set<String> blockedIpAddresses;
#Autowired
private ModeratorService moderatorService;
#PostConstruct
private void loadBlockedIpAddresses(){
blockedIpAddresses = moderatorService.getBlockedIpAddresses();
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
String clientIpAddress = getClientIP((HttpServletRequest) servletRequest);
log.info("ip " + clientIpAddress + " is requesting " + httpServletRequest.getRequestURI());
if(blockedIpAddresses.contains(clientIpAddress)) {
log.info(clientIpAddress + " is currently on the banlist, aborting request...");
httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
}
filterChain.doFilter(servletRequest, servletResponse);
}
#Override
public void destroy() {
}
}
Here is the code of the implementing interface
public interface IpAddressExtractor {
public default String getClientIP(HttpServletRequest request) {
String xfHeader = request.getHeader("X-Forwarded-For");
if (xfHeader == null){
return request.getRemoteAddr();
}
return xfHeader.split(",")[0]; // voor als ie achter een proxy zit
}
}
This should work but i don't think sending a simple http status code is very elegant, i would like to be able to send back a message explaining to the user that he/she is in fact banned.
So my question is; How do i ban a user from a spring web application effectively. And with effectively i mean being able to send an error message back to the single page app that can then be displayed to the user. I would also like to know if this is the best way to deny banned users access from the REST api. I would like to know if there are different more effective ways to accomplish this.
Thank you
EDIT: This is the tutorial i used to create the majority of the code https://www.baeldung.com/java-web-app-without-web-xml
Effective banning is a co-operative effort between your application doing the detection and the operating system implementing the block. If you are deploying on to Linux then an effective strategy is this:
Log an easily parseable message to an audit log file that says the user is to be banned and include the IP address. The HTTP response to the offender can include a suitable 'goodbye' message.
Install and configure fail2ban to parse your log files and implement the ban. It works by making local modifications to the firewall rules to prevent the offender even making a network connection to your server.

The httpsession which i get from modifyHandshake is not the correct session,why? [duplicate]

Is it possible to get the HttpServletRequest inside a #ServerEndpoint? Primarily I am trying to get it so I can access the HttpSession object.
Update (November 2016): The information provided in this answer is for the JSR356 spec, individual implementations of the spec may vary outside of this information. Other suggestions found in comments and other answers are all implementation specific behaviors outside of the JSR356 spec.
If the suggestions in here are causing you problems, upgrade your various installations of Jetty, Tomcat, Wildfly, or Glassfish/Tyrus. All current versions of those implementations have all been reported to work in the way outlined below.
Now back to the original answer from August 2013...
The answer from Martin Andersson has a concurrency flaw. The Configurator can be called by multiple threads at the same time, it is likely that you will not have access to the correct HttpSession object between the calls from modifyHandshake() and getEndpointInstance().
Or said another way...
Request A
Modify Handshake A
Request B
Modify Handshake B
Get Endpoint Instance A <-- this would have Request B's HttpSession
Get Endpoint Instance B
Here's a modification to Martin's code that uses ServerEndpointConfig.getUserProperties() map to make the HttpSession available to your socket instance during the #OnOpen method call
GetHttpSessionConfigurator.java
package examples;
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator
{
#Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response)
{
HttpSession httpSession = (HttpSession)request.getHttpSession();
config.getUserProperties().put(HttpSession.class.getName(),httpSession);
}
}
GetHttpSessionSocket.java
package examples;
import java.io.IOException;
import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
#ServerEndpoint(value = "/example",
configurator = GetHttpSessionConfigurator.class)
public class GetHttpSessionSocket
{
private Session wsSession;
private HttpSession httpSession;
#OnOpen
public void open(Session session, EndpointConfig config) {
this.wsSession = session;
this.httpSession = (HttpSession) config.getUserProperties()
.get(HttpSession.class.getName());
}
#OnMessage
public void echo(String msg) throws IOException {
wsSession.getBasicRemote().sendText(msg);
}
}
Bonus feature: no instanceof or casting required.
Some EndpointConfig Knowledge
EndpointConfig objects do exist per "Endpoint Instance".
However, an "Endpoint Instance" has 2 meanings with the spec.
Default behavior of the JSR, where each incoming upgrade request results in a new object instance of the endpoint class
A javax.websocket.Session that ties together the object endpoint instance, with its configuration, to a specific logical connection.
It is possible to have a singleton Endpoint instance being used for multiple javax.websocket.Session instances (that is one of the features that ServerEndpointConfig.Configurator supports)
The ServerContainer implementation will track a set of ServerEndpointConfig's that represent all of the deployed endpoints that the server can respond to a websocket upgrade request.
These ServerEndpointConfig object instances can come from a few different sources.
Manually provided by the javax.websocket.server.ServerContainer.addEndpoint(ServerEndpointConfig)
Usually done within a javax.servlet.ServletContextInitializer.contextInitialized(ServletContextEvent sce) call
From the javax.websocket.server.ServerApplicationConfig.getEndpointConfigs(Set) call.
Automatically created from scanning of the web application for #ServerEndpoint annotated classes.
These ServerEndpointConfig object instances exist as defaults for when a javax.websocket.Session does eventually get created.
ServerEndpointConfig.Configurator Instance
Before any upgrade requests are received or processed, all of the ServerEndpointConfig.Configurator objects now exist and are ready to perform their main and sole purpose, to allow for customization of the upgrade process of a websocket connection to an eventual javax.websocket.Session
Access to Session specific EndpointConfig
Note, you cannot access the ServerEndpointConfig object instances from within a endpoint instance. You can only access EndpointConfig instances.
This means if you provided ServerContainer.addEndpoint(new MyCustomServerEndpointConfig()) during deploy and later tried to access it via the annotations, it will not work.
All of the following would be invalid.
#OnOpen
public void onOpen(Session session, EndpointConfig config)
{
MyCustomServerEndpointConfig myconfig = (MyCustomServerEndpointConfig) config;
/* this would fail as the config is cannot be cast around like that */
}
// --- or ---
#OnOpen
public void onOpen(Session session, ServerEndpointConfig config)
{
/* For #OnOpen, the websocket implementation would assume
that the ServerEndpointConfig to be a declared PathParam
*/
}
// --- or ---
#OnOpen
public void onOpen(Session session, MyCustomServerEndpointConfig config)
{
/* Again, for #OnOpen, the websocket implementation would assume
that the MyCustomServerEndpointConfig to be a declared PathParam
*/
}
You can access the EndpointConfig during the life of the Endpoint object instance, but under a limited time. The javax.websocket.Endpoint.onOpen(Session,Endpoint), annotated #OnOpen methods, or via the use of CDI. The EndpointConfig is not available in any other way or at any other time.
However, you can always access the UserProperties via the Session.getUserProperties() call, which is available always. This User Properties map is always available, be it via the annotated techniques (such as a Session parameter during #OnOpen, #OnClose, #OnError, or #OnMessage calls), via CDI injection of the Session, or even with the use of non-annotated websockets that extend from javax.websocket.Endpoint.
How Upgrade Works
As stated before, every one of the defined endpoints will have a ServerEndpointConfig associated with it.
Those ServerEndpointConfigs are a single instance that represents the default state of the EndpointConfig that are eventually made available to the Endpoint Instances that are possibly and eventually created.
When a incoming upgrade request arrives, it has go through the following on the JSR.
does the path match any of the ServerEndpointConfig.getPath() entries
If no match, return 404 to upgrade
pass upgrade request into ServerEndpointConfig.Configurator.checkOrigin()
If not valid, return error to upgrade response
create HandshakeResponse
pass upgrade request into ServerEndpointConfig.Configurator.getNegotiatedSubprotocol()
store answer in HandshakeResponse
pass upgrade request into ServerEndpointConfig.Configurator.getNegotiatedExtensions()
store answer in HandshakeResponse
Create new endpoint specific ServerEndpointConfig object. copy encoders, decoders, and User Properties. This new ServerEndpointConfig wraps default for path, extensions, endpoint class, subprotocols, configurator.
pass upgrade request, response, and new ServerEndpointConfig into ServerEndpointConfig.Configurator.modifyHandshake()
call ServerEndpointConfig.getEndpointClass()
use class on ServerEndpointConfig.Configurator.getEndpointInstance(Class)
create Session, associate endpoint instance and EndpointConfig object.
Inform endpoint instance of connect
annotated methods that want EndpointConfig gets the one associated with this Session.
calls to Session.getUserProperties() returns EndpointConfig.getUserProperties()
To note, the ServerEndpointConfig.Configurator is a singleton, per mapped ServerContainer endpoint.
This is intentional, and desired, to allow implementors several features.
to return the same Endpoint instance for multiple peers if they so desire. The so called stateless approach to websocket writing.
to have a single point of management of expensive resources for all Endpoint instances
If the implementations created a new Configurator for every handshake, this technique would not be possible.
(Disclosure: I write and maintain the JSR-356 implementation for Jetty 9)
Preface
It's unclear whether you're wanting the HttpServletRequest, the HttpSession, or properties out of the HttpSession. My answer will show how to get the HttpSession or individual properties.
I've omitted null and index bounds checks for brevity.
Cautions
It's tricky. The answer from Martin Andersson is not correct because the same instance of ServerEndpointConfig.Configurator is used for every connection, hence a race condition exists. While the docs state that "The implementation creates a new instance of the configurator per logical endpoint," the spec does not clearly define a "logical endpoint." Based on the context of all the places that phrase is used, it appears to mean the binding of a class, configurator, path, and other options, i.e., a ServerEndpointConfig, which is clearly shared. Anyway you can easily see if an implementation is using the same instance by printing out its toString() from within modifyHandshake(...).
More surprisingly the answer from Joakim Erdfelt also does not work reliably. The text of JSR 356 itself does not mention EndpointConfig.getUserProperties(), it is only in the JavaDoc, and nowhere does it seem to be specified what its exact relationship is to Session.getUserProperties(). In practice some implementations (e.g., Glassfish) return the same Map instance for all calls to ServerEndpointConfig.getUserProperties() while others (e.g., Tomcat 8) don't. You can check by printing out the map contents before modifying it within modifyHandshake(...).
To verify, I copied the code directly from the other answers and then tested it against a multithreaded client I wrote. In both cases I observed the incorrect session being associated with the endpoint instance.
Outline of Solutions
I've developed two solutions, which I've verified work correctly when tested against a multithreaded client. There are two key tricks.
First, use a filter with the same path as the WebSocket. This will give you access to the HttpServletRequest and HttpSession. It also gives you a chance to create a session if it doesn't already exist (although in that case using an HTTP session at all seems dubious).
Second, find some properties that exist in both the WebSocket Session and HttpServletRequest or HttpSession. It turns out there are two candidates: getUserPrincipal() and getRequestParameterMap(). I will show you how to abuse both of them :)
Solution using User Principal
The easiest way is to take advantage of Session.getUserPrincipal() and HttpServletRequest.getUserPrincipal(). The downside is this could interfere with other legitimate uses of this property, so use it only if you are prepared for those implications.
If you want to store just one string, such as a user ID, this is actually not too much of an abuse, though it presumably should be set in some container managed way rather than overriding the wrapper as I'll show you. Anyway you would do that by just overriding Principal.getName(). Then you don't even need to cast it in the Endpoint. But if you can stomach it, you can also pass the whole HttpSession object as follows.
PrincipalWithSession.java
package example1;
import java.security.Principal;
import javax.servlet.http.HttpSession;
public class PrincipalWithSession implements Principal {
private final HttpSession session;
public PrincipalWithSession(HttpSession session) {
this.session = session;
}
public HttpSession getSession() {
return session;
}
#Override
public String getName() {
return ""; // whatever is appropriate for your app, e.g., user ID
}
}
WebSocketFilter.java
package example1;
import java.io.IOException;
import java.security.Principal;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
#WebFilter("/example1")
public class WebSocketFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
final PrincipalWithSession p = new PrincipalWithSession(httpRequest.getSession());
HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) {
#Override
public Principal getUserPrincipal() {
return p;
}
};
chain.doFilter(wrappedRequest, response);
}
public void init(FilterConfig config) throws ServletException { }
public void destroy() { }
}
WebSocketEndpoint.java
package example1;
import javax.servlet.http.HttpSession;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
#ServerEndpoint("/example1")
public class WebSocketEndpoint {
private HttpSession httpSession;
#OnOpen
public void onOpen(Session webSocketSession) {
httpSession = ((PrincipalWithSession) webSocketSession.getUserPrincipal()).getSession();
}
#OnMessage
public String demo(String msg) {
return msg + "; (example 1) session ID " + httpSession.getId();
}
}
Solution Using Request Parameters
The second option uses Session.getRequestParameterMap() and HttpServletRequest.getParameterMap(). Note that it uses ServerEndpointConfig.getUserProperties() but it is safe in this case because we're always putting the same object into the map, so whether it's shared makes no difference. The unique session identifier is passed not through the user parameters but instead through the request parameters, which is unique per request.
This solution is slightly less hacky because it does not interfere with the user principal property. Note that if you need to pass through the actual request parameters in addition to the one that's inserted, you can easily do so: just start with the existing request parameter map instead of a new empty one as shown here. But take care that the user cannot spoof the special parameter added in the filter by supplying their own request parameter by the same name in the actual HTTP request.
SessionTracker.java
/* A simple, typical, general-purpose servlet session tracker */
package example2;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
#WebListener
public class SessionTracker implements ServletContextListener, HttpSessionListener {
private final ConcurrentMap<String, HttpSession> sessions = new ConcurrentHashMap<>();
#Override
public void contextInitialized(ServletContextEvent event) {
event.getServletContext().setAttribute(getClass().getName(), this);
}
#Override
public void contextDestroyed(ServletContextEvent event) {
}
#Override
public void sessionCreated(HttpSessionEvent event) {
sessions.put(event.getSession().getId(), event.getSession());
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
sessions.remove(event.getSession().getId());
}
public HttpSession getSessionById(String id) {
return sessions.get(id);
}
}
WebSocketFilter.java
package example2;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
#WebFilter("/example2")
public class WebSocketFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
final Map<String, String[]> fakedParams = Collections.singletonMap("sessionId",
new String[] { httpRequest.getSession().getId() });
HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) {
#Override
public Map<String, String[]> getParameterMap() {
return fakedParams;
}
};
chain.doFilter(wrappedRequest, response);
}
#Override
public void init(FilterConfig config) throws ServletException { }
#Override
public void destroy() { }
}
WebSocketEndpoint.java
package example2;
import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
import javax.websocket.HandshakeResponse;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
#ServerEndpoint(value = "/example2", configurator = WebSocketEndpoint.Configurator.class)
public class WebSocketEndpoint {
private HttpSession httpSession;
#OnOpen
public void onOpen(Session webSocketSession, EndpointConfig config) {
String sessionId = webSocketSession.getRequestParameterMap().get("sessionId").get(0);
SessionTracker tracker =
(SessionTracker) config.getUserProperties().get(SessionTracker.class.getName());
httpSession = tracker.getSessionById(sessionId);
}
#OnMessage
public String demo(String msg) {
return msg + "; (example 2) session ID " + httpSession.getId();
}
public static class Configurator extends ServerEndpointConfig.Configurator {
#Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request,
HandshakeResponse response) {
Object tracker = ((HttpSession) request.getHttpSession()).getServletContext().getAttribute(
SessionTracker.class.getName());
// This is safe to do because it's the same instance of SessionTracker all the time
sec.getUserProperties().put(SessionTracker.class.getName(), tracker);
super.modifyHandshake(sec, request, response);
}
}
}
Solution for Single Properties
If you only need certain properties out of the HttpSession and not the whole HttpSession itself, like say a user ID, then you could do away with the whole SessionTracker business and just put the necessary parameters in the map you return from your override of HttpServletRequestWrapper.getParameterMap(). Then you can also get rid of the custom Configurator; your properties will be conveniently accessible from Session.getRequestParameterMap() in the Endpoint.
WebSocketFilter.java
package example5;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
#WebFilter("/example5")
public class WebSocketFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
final Map<String, String[]> props = new HashMap<>();
// Add properties of interest from session; session ID
// is just for example
props.put("sessionId", new String[] { httpRequest.getSession().getId() });
HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) {
#Override
public Map<String, String[]> getParameterMap() {
return props;
}
};
chain.doFilter(wrappedRequest, response);
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
}
WebSocketEndpoint.java
package example5;
import java.util.List;
import java.util.Map;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
#ServerEndpoint("/example5")
public class WebSocketEndpoint {
private Map<String, List<String>> params;
#OnOpen
public void onOpen(Session session) {
params = session.getRequestParameterMap();
}
#OnMessage
public String demo(String msg) {
return msg + "; (example 5) session ID " + params.get("sessionId").get(0);
}
}
Is it possible?
Let's review the Java API for WebSocket specification to see if getting hold of the HttpSession object is possible. The specification says on page 29:
Because websocket connections are initiated with an http request,
there is an association between the HttpSession under which a client
is operating and any websockets that are established within that
HttpSession. The API allows access in the opening handshake to the
unique HttpSession corresponding to that same client.
So yes it is possible.
However, I don't think it is possible for you to get hold of a reference to the HttpServletRequest object though. You could listen for all new servlet requests using a ServletRequestListener, but you would still have to figure out which request belong to which server endpoint. Please let me know if you find a solution!
Abstract how-to
How-to is loosely described on pages 13 and 14 in the specification and exemplified by me in code under the next heading.
In English, we will need to intercept the handshake process to get hold of a HttpSession object. To then transfer the HttpSession reference to our server endpoint, we also need to intercept when the container creates the server endpoint instance and manually inject the reference. We do all of this by providing our own ServerEndpointConfig.Configurator and override the methods modifyHandshake() and getEndpointInstance().
The custom configurator will be instantiated once per logical ServerEndpoint (See the JavaDoc).
Code example
This is the server endpoint class (I provide the implementation of the CustomConfigurator class after this code snippet):
#ServerEndpoint(value = "/myserverendpoint", configurator = CustomConfigurator.class)
public class MyServerEndpoint
{
private HttpSession httpSession;
public void setHttpSession(HttpSession httpSession) {
if (this.httpSession != null) {
throw new IllegalStateException("HttpSession has already been set!");
}
this.httpSession = httpSession;
}
#OnOpen
public void onOpen(Session session, EndpointConfig config) {
System.out.println("My Session Id: " + httpSession.getId());
}
}
And this is the custom configurator:
public class CustomConfigurator extends ServerEndpointConfig.Configurator
{
private HttpSession httpSession;
// modifyHandshake() is called before getEndpointInstance()!
#Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
httpSession = (HttpSession) request.getHttpSession();
super.modifyHandshake(sec, request, response);
}
#Override
public <T> T getEndpointInstance(Class<T> endpointClass) throws InstantiationException {
T endpoint = super.getEndpointInstance(endpointClass);
if (endpoint instanceof MyServerEndpoint) {
// The injection point:
((MyServerEndpoint) endpoint).setHttpSession(httpSession);
}
else {
throw new InstantiationException(
MessageFormat.format("Expected instanceof \"{0}\". Got instanceof \"{1}\".",
MyServerEndpoint.class, endpoint.getClass()));
}
return endpoint;
}
}
All above answers worth reading, but none of them solves OP's (and my) problem.
You can access HttpSession when a WS end point is opening and pass it to newly created end point instance but no one guarantees there exist HttpSession instance!
So we need step 0 before this hacking (I hate JSR 365 implementation of WebSocket).
Websocket - httpSession returns null
All possible solutions are based upon:
A. Client browser implementations maintains Session ID via Cookie value passed as an HTTP Header, or (if cookies are disabled) it is managed by Servlet container which will generate Session ID postfix for generated URLs
B. You can access HTTP Request Headers only during HTTP handshake; after that, it is Websocket Protocol
So that...
Solution 1: use "handshake" to access HTTP
Solution 2: In your JavaScript on the client side, dynamically generate HTTP Session ID parameter and send first message (via Websocket) containing this Session ID. Connect "endpoint" to the cache / utility class maintaining Session ID -> Session mapping; avoid memory leaks, you can use Session Listener for instance to remove session from cache.
P.S. I appreciate answers from Martin Andersson and Joakim Erdfelt.
Unfortunately, solution of Martin is not thread safe...
The only way that works across all applications servers is use ThreadLocal. See:
https://java.net/jira/browse/WEBSOCKET_SPEC-235

Java Servlet 404 issues

I am working on a simple program redirection by making some calls to a servlet. However, for some reason, I am continuously getting a 404 error every time I try to construct the code in this particular project build.
It may be of note that I am building this project in Eclipse and am using Apache Tomcat.
here is my coding....
HTML first:
To save some aggrivation, this is the particular snippet on where the problems arise. My connection to the MySQL database along with the database reads are operating perfectly fine.
<form action="objectServer" method="get">
<select name="choice">
<% while (rs.next()){ %>
<option value="<%=rs.getString(2) %>"><%=rs.getString(2) %></option>
<% }
MysqlConnection.close(connect);
%>
</select>
<br />
<input type="submit" value="View the Descrition!" />
</form>
Java code:
The first is a simple java class that validates whether an item has been selected and acts to help redirect.
package com.program.service;
public class Service {
public boolean redirect(String selected){
if( (selected == null) || (selected == "") ){
return false;
} else {
return true;
}
}
}
And finally, the servlet itself.
package com.program.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.wines.service.Service;
#WebServlet("/objectServer")
public class InfoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* #see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String selected = request.getParameter("choice");
Service service = new Service();
boolean redirector = service.redirect(selected);
if(redirector){
response.sendRedirect("next_page.jsp");
} else {
response.sendRedirect("this_page.jsp");
}
}
}
As one can see, this is a REALLY simple program that should be redirecting to the next page, but for some reason I am missing something that I just cannot see at this moment. I don't don't know whether it's an issue with my coding, my server, or something else I could have missed. As a note, the web.xml has not been programmed in this particular application as of yet.
Depending on your setup, your jsp's are most likely can be only accessed through Servlets. In this case, you will need to use "forward" instead of "redirect". Please understand the differences between "forward" and "redirect". Here is a good read: Forward versus redirect
Example Forward:
RequestDispatcher dispatcher = aRequest.getRequestDispatcher("this_page.jsp");
dispatcher.forward(aRequest, aResponse);
Example redirect:
response.sendRedirect(absoluteOrRelativeURL); // e.g absoluteOrRelativeURL= "objectServer"
if your JSP pages are resides under the WEB-INF, you cannot redirect to the JSP page. this is because the content under the WEB-INFare restricted from the direct access. therefore you have to forward the request for that resource. that can be done by modifying you method as follows. ( i have assumed that your jsp pages are resides directly under the WEB-INF directory)
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String selected = request.getParameter("choice");
Service service = new Service();
boolean redirector = service.redirect(selected);
if(redirector){
request.getRequestDispatcher("WEB-INF/next_page.jsp").forward(request,response);
} else {
request.getRequestDispatcher("WEB-INF/this_page.jsp").forward(request,response);
}
}
further more, if you wish to make a ridirect for particular resource, make sure to do the redirect for a servlet mapping. you cannot make a redirect for a particular resource resides under the WEB-INF directory.

How to validate a request to a JSP page in Struts2

A request to an Action can be validated using the Interceptors. Also, Direct request to a JSP page can be avoided using an empty action declaration. It is something like this;
<action name="home" >
<result>home.jsp</result>
</action>
I want to validate this calling to the JSP page. As I feel that a simple approach can be adding an Action (ActionSupport) with an Interceptor which checks the action name( and do some validation using session etc). But I've a doubt that whether it reduces the performance,because that action class doesn't do anything and just execute its execute() only(useless task...), and where there should be an empty Action .(BUT: as I ve read some docs on Struts2, it is said that even though we don't add an Action calss,the framework itself adds and action class which returns "success" therefore,adding an action class or not ourselves, doesn't affect newly )
Any way, I d like to know what your best approaches are to validate or authorized access to some JSP pages.( number of pages can be many.. not only to one jsp page)
Added:
Example:
Lets say that, there are some restricted pages where all users can't have access,for example user's account page can be visited only to logged in users.There can be more such type of pages. If a request comes to a such page,the user has to be validated.Then, if the request comes through a empty/anonymous action (as explained in above code snip -only action name-no real class) how to validate such requests to JSP pages? Should an action class be used for this?
If your attention is to secure some part of your application so as only Authenticated as well authorize use can access that part, than you have two option
Use an interceptor based authentication
User a security API like Spring -security
Interceptor based authentication is quite easy. here is an example how to do this, but such authentication is not recommended for a production based and real life application since its really a very simple case.
if you are looking for a complete authentication system, i suggest you to look at Spring security.Its quite easy and configurable, all you need to tell the underlying spring mechanism which all areas and under secure tag and Spring security will intercept them before your action get called and only successful and authorize action will get called by spring security.
//This is for authorization
package com.kogent.action;
import java.io.IOException;
import java.util.List;
import java.util.regex.Pattern;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.dispatcher.ng.ExecuteOperations;
import org.apache.struts2.dispatcher.ng.InitOperations;
import org.apache.struts2.dispatcher.ng.PrepareOperations;
import org.apache.struts2.dispatcher.ng.filter.FilterHostConfig;
import org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter;
public class SessionController extends StrutsPrepareAndExecuteFilter {
protected PrepareOperations prepare;
protected ExecuteOperations execute;
protected List<Pattern> excludedPatterns = null;
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);
init.initLogging(config);
Dispatcher dispatcher = init.initDispatcher(config);
init.initStaticContentLoader(config, dispatcher);
prepare = new PrepareOperations(filterConfig.getServletContext(),
dispatcher);
execute = new ExecuteOperations(filterConfig.getServletContext(),
dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
} finally {
init.cleanup();
}
}
/**
* Callback for post initialization
*/
protected void postInit(Dispatcher dispatcher, FilterConfig filterConfig) {
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
prepare.setEncodingAndLocale(request, response);
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
if (excludedPatterns != null
&& prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
request = prepare.wrapRequest(request);
ActionMapping mapping = prepare.findActionMapping(request,
response, true);
if (mapping == null) {
boolean handled = execute.executeStaticResourceRequest(
request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
//here you have to identify the whether the user have access to requested resource or not
//allow him if he was access.
//if(someCondition)
execute.executeAction(request, response, mapping);
//else{
//redirect the user how do you want it to be.
ActionMapping modfiedActionMapping = new ActionMapping();
modfiedActionMapping.setName("someActionName");
modfiedActionMapping.setNamespace("someNameSpace");
execute.executeAction(request, response, modfiedActionMapping);
//}
}
}
} finally {
prepare.cleanupRequest(request);
}
}
public void destroy() {
prepare.cleanupDispatcher();
}
}
<filter>
<filter-name>struts2</filter-name>
<filter-class>Point to your customized filter</filter-class>
</filter>

Categories

Resources