package project1; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class TestServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); out.write("Username: " + request.getRemoteUser() + "\n"); HttpSession session = request.getSession(true); Integer iCount = 0; if (!session.isNew()) { iCount = (Integer)session.getAttribute("count"); } iCount++; session.setAttribute("count", iCount); out.write("Count: " + iCount); } }And let's say that you protect the app with Basic authentication, like so:
<?xml version = '1.0' encoding = 'ISO-8859-1'?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"> <servlet> <servlet-name>TestServlet</servlet-name> <servlet-class>project1.TestServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>TestServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <security-constraint> <web-resource-collection> <web-resource-name>all</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>allusers</role-name> <role-name>allusers</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>Session Test</realm-name> </login-config> <security-role> <role-name>allusers</role-name> </security-role> </web-app>When you hit the app via HTTP the server will see that you haven't authenticated and will respond with a 401 and your browser will pop up a Basic auth box. The next time you make an HTTP request and this time include your credentials the server will:
- validate the creds
- see that you don't have a session
- create a session for the user
- squirrel the session away in memory on that server (i.e. in that JVM)
- issue a cookie named JSESSIONID
- and finally it will it execute the servlet.
Well for that you need to deploy a cluster. There's a whole lot of info out there about WebLogic Server clustering, so I'm not going to go into the details of how the AdminServer, managed servers and clusters work. This is a blog about security stuff in the Fusion stack so you didn't come here to read about managed servers and clusters anyway. The only reason I'm writing about all of this is because there are some aspects of this that affect security in a way you might not have thought of until now. Like what? Did you know that once you have a session established you don't have to present the credentials again? If you present the JSESSIONID cookie WebLogic will go look in the session, figure out that you've already authenticated and it will go ahead and service the request in the context of that user. In fact that's how HTML Forms-based authentication works - you present your username and password via HTTP POST (to j_security_check) and the WLS Server establishes the session and stores the subject and principals in the session. It's also how SAML authentication works. If you present a SAML assertion (via IdP-Initiated POST for example) the assertion is validated by WLS and a session gets established; subsequent requests include the session information and the SAML assertion isn't needed. All of the built in Authenticators and Identity Asserters as well as any custom one you might write works exactly the the same way. In all of these cases once a session is established you just submit the request with the session info (in the JSESSIONID cookie by default) and you can go ahead and invoke any URL in the app as that user. For the purposes of this demo I setup one AdminServer and a couple of Managed Servers (I called them server1 and server2). I then created a single cluster (called "Cluster-0" because that's the default) composed of those two servers. The result looks like this in the config.xml:
<server> <name>server1</name> <listen-port>7031</listen-port> <cluster>Cluster-0</cluster> <listen-address></listen-address> <jta-migratable-target> <user-preferred-server>server1</user-preferred-server> <cluster>Cluster-0</cluster> </jta-migratable-target> <server-diagnostic-config> <name>server1</name> <diagnostic-context-enabled>true</diagnostic-context-enabled> </server-diagnostic-config> </server> <server> <name>server2</name> <listen-port>7032</listen-port> <cluster>Cluster-0</cluster> <listen-address></listen-address> <jta-migratable-target> <user-preferred-server>server2</user-preferred-server> <cluster>Cluster-0</cluster> </jta-migratable-target> <server-diagnostic-config> <name>server2</name> <diagnostic-context-enabled>true</diagnostic-context-enabled> </server-diagnostic-config> </server> <cluster> <name>Cluster-0</name> <multicast-address>239.192.0.0</multicast-address> <multicast-port>7001</multicast-port> <cluster-messaging-mode>unicast</cluster-messaging-mode> </cluster>In the screen shot above you may have noticed that the WebLogic Deployment Descriptor has a setting called Store Type. The default is MEMORY and I changed it in my test to REPLICATE_IF_CLUSTERED. Here's the complete list of possibilities: If you want to test this yourself here's a little perl script I wrote to prove that out:
#!/usr/bin/perl $URL1 = "http://10.99.2.125:7031/SessionTest/"; $URL2 = "http://10.99.2.125:7032/SessionTest/"; #$URL2 = $URL1; $USERNAME = "testuser1"; $PASSWORD = "ABcd1234"; $DELAY = 5; use LWP; require HTTP::Request; require HTTP::Response; use HTTP::Cookies; $cookie_jar = HTTP::Cookies->new; # first request - forcibly include the credentials $request1 = HTTP::Request->new(GET => $URL1 ); $request1->authorization_basic($USERNAME, $PASSWORD); print "========================================================\n"; print "| REQUEST #1 |\n"; print "========================================================\n"; print "HTTP Request\n------------\n"; print $request1->as_string; print "\n"; $ua = LWP::UserAgent->new; $response1 = $ua->request($request1); print "HTTP Response\n-------------\n"; print $response1->as_string; print "========================================================\n"; #exit; print "Sleeping $DELAY seconds.\n"; sleep $DELAY; print "\g"; # extract any set-cookie headers and put them in the cookie jar # note: I do it this way rather than finding the JSESSIONID specifically in case the # cookie has been renamed. $cookie_jar->extract_cookies($response1); #$cookie_jar->scan( dumpCookies ); #print "Cookies: \n"; #print $cookie_jar->as_string(); # #print "----"; $request2 = HTTP::Request->new(GET => $URL2 ); #$request2->authorization_basic($USERNAME, $PASSWORD); $cookie_jar->add_cookie_header($request2); print "HTTP Request\n------------\n"; print $request2->as_string; print "\n"; $response2 = $ua->request($request2); print "HTTP Response\n-------------\n"; print $response2->as_string;
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.