Maintaining user sessions in web services

Maintaining user sessions in web services

Maintaining user sessions in web services involves preserving the state of a user’s interactions with a web service across multiple requests. This is typically done to track user authentication, preferences, or any other stateful information that needs to persist throughout the user’s interaction with the service.

Maintaining user sessions in web services

Session Management Techniques

  • 1.  HTTP Cookies : Storing session information in cookies that are sent back and forth between the client and server.
  • 2.  URL Rewriting : Including session information in the URL parameters.
  • 3.  Hidden Form Fields : Passing session information through hidden fields in HTML forms.
  • 4.  Token-based Authentication : Using tokens like JWT (JSON Web Tokens) that are included in HTTP headers to maintain session information.
  • 5.  Server-side Session Management : Storing session information on the server, typically in a session store like Redis or in-memory store, and using a session ID to associate requests with stored session data.

Explanation in Java Example

Let’s consider a simple RESTful web service example to demonstrate how to maintain a user session using HTTP cookies and token-based authentication.

1. Dependencies (Maven pom.xml)
xml
<dependencies>
    <!-- Other dependencies -->
    <dependency>
        <groupId>javax.ws.rs</groupId>
        <artifactId>javax.ws.rs-api</artifactId>
        <version>2.1.1</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-server</artifactId>
        <version>2.29.1</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.containers</groupId>
        <artifactId>jersey-container-servlet</artifactId>
        <version>2.29.1</version>
    </dependency>
</dependencies>

2. User Session Resource (UserSessionResource.java)
java
import javax.ws.rs.*;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Path("/session")
public class UserSessionResource {

    private static Map<String, String> sessionStore = new HashMap<>();

    @POST
    @Path("/login")
    public Response login(@FormParam("username") String username) {
        String sessionId = UUID.randomUUID().toString();
        sessionStore.put(sessionId, username);
        NewCookie sessionCookie = new NewCookie("JSESSIONID", sessionId);
        return Response.ok("Login successful").cookie(sessionCookie).build();
    }

    @GET
    @Path("/profile")
    public Response getUserProfile(@CookieParam("JSESSIONID") Cookie cookie) {
        if (cookie != null && sessionStore.containsKey(cookie.getValue())) {
            String username = sessionStore.get(cookie.getValue());
            return Response.ok("User profile for " + username).build();
        } else {
            return Response.status(Response.Status.UNAUTHORIZED).entity("User not logged in").build();
        }
    }

    @POST
    @Path("/logout")
    public Response logout(@CookieParam("JSESSIONID") Cookie cookie) {
        if (cookie != null && sessionStore.containsKey(cookie.getValue())) {
            sessionStore.remove(cookie.getValue());
            NewCookie expiredCookie = new NewCookie("JSESSIONID", "", "/", "", "", 0, false);
            return Response.ok("Logout successful").cookie(expiredCookie).build();
        } else {
            return Response.status(Response.Status.BAD_REQUEST).entity("Invalid session").build();
        }
    }
}

3. Application Config Class (RestApplication.java)
java
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/api")
public class RestApplication extends Application {
    // No need to implement any methods
}

4. web.xml Configuration
xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <display-name>SessionApp</display-name>

    <servlet>
        <servlet-name>Jersey Web Application</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.example.RestApplication</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>Jersey Web Application</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>

</web-app>

Using Token-based Authentication (JWT)

1. Dependencies (Maven pom.xml)
xml
<dependencies>
    <!-- Other dependencies -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
</dependencies>

2. JWT Utility Class (JwtUtil.java
java
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

public class JwtUtil {

    private static final String SECRET_KEY = "secret";

    public static String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }

    public static String validateToken(String token) {
        return Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }
}

3. User Session Resource (UserSessionResource.java)
java
import javax.ws.rs.*;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/session")
public class UserSessionResource {

    @POST
    @Path("/login")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response login(@FormParam("username") String username) {
        String token = JwtUtil.generateToken(username);
        return Response.ok("Login successful").header(HttpHeaders.AUTHORIZATION, "Bearer " + token).build();
    }

    @GET
    @Path("/profile")
    public Response getUserProfile(@HeaderParam(HttpHeaders.AUTHORIZATION) String authHeader) {
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.substring("Bearer".length()).trim();
            try {
                String username = JwtUtil.validateToken(token);
                return Response.ok("User profile for " + username).build();
            } catch (Exception e) {
                return Response.status(Response.Status.UNAUTHORIZED).entity("Invalid token").build();
            }
        } else {
            return Response.status(Response.Status.UNAUTHORIZED).entity("Authorization header missing").build();
        }
    }

    @POST
    @Path("/logout")
    public Response logout() {
        // Token-based stateless approach, logout is typically handled client-side by removing the token
        return Response.ok("Logout successful").build();
    }
}

Explanation

  • 1.  HTTP Cookies :
    • A cookie named JSESSIONID is created during login and stored in the sessionStore.
    • This cookie is sent with subsequent requests to identify the session and retrieve the associated user information.
  • 2.  Token-based Authentication :
    • A JWT is generated during login and included in the Authorization header of subsequent requests.
    • The token is validated on the server side to extract user information and maintain the session.