Session-based (Cookie-Based) Authentication

Table of Contents

1 Session简介

HTTP协议是无状态的,浏览器(客户端)向Web服务器发起的多个请求之间都是独立的。但有时,我们希望服务器记住客户端的状态,如购物网站记住用户的登录状态。

Session在是一种用来在客户端与服务器端之间保持状态的解决方案。其基于思路是: 客户端第一次访问时,服务器端生成一个 session id 返回给客户端(当然服务器端也会把这个session id存在内存中),客户端得到这个session id后,在后续的每个请求中会把这个session id传回服务器,这样服务器查询自己内存就知道这个客户端曾经访问过。

Session可以用来实现用户的登录认证,后文将介绍它。不过Session的用处还有很多,比如可以实现统计用户访问次数,这里有一个简单例子:https://docstore.mik.ua/orelly/java-ent/servlet/ch07_05.htm

说明:服务器端生成的session id传回客户端后,往往会保存在cookie中,所以 Session-based认证也称为Cookie-Based认证。

2 基于Session(Cookie)的认证

下面介绍基于Session来实现REST APIs认证过程的例子。图 1 (摘自:https://auth0.com/blog/angularjs-authentication-with-cookies-vs-token/ )左子图是基于Session实现认证的基本流程。

http_session_cookie_auth.png

Figure 1: 基于Session和基于Token的认证

2.1 客户端实现

客户端的步骤很简单:
第一步:登录(获取session id)。
把用户名和密码,通过 POST 请求发送到认证url。

如通过 POST 发送数据 { "username": "myuser", "password": "mypassword" }rest/auth/session

服务器端验证用户名和密码,如果正确,生成session对象,并把其id(即session id,它具有很好的随机性)返回给客户端。如,返回类似下面的json数据:

{
  "session": {
    "name":"JSESSIONID",
    "value":"9brW9p1hugTVFQo9rbLE0I4Y3SeC0UtweiRtgr"
  },
  "loginInfo": {
    "loginCount":2,
	"previousLoginTime":"2014-11-12T08:34:39.812+0000"
  }
}

客户端收到服务器返回的session id后,把它保存到cookie中,以便后续使用。

注:这个过程可以用Javascript代码显式地实现,也可以通过下面方法自动实现:服务器在返回登录成功的HTTP header中增加下面Set-Cookie相关内容:

Set-Cookie: JSESSIONID=9brW9p1hugTVFQo9rbLE0I4Y3SeC0UtweiRtgr; Path=/; HttpOnly

那么,浏览器接受到这个响应报文后就会自动把session id保存到cookie中。

第二步,客户端在调用REST API时,需要在HTTP请求报文头的Cookie字段中把登录时得到的session id附上,传回给服务器。即HTTP请求报文头中要包含下面内容:

Cookie: JSESSIONID=9brW9p1hugTVFQo9rbLE0I4Y3SeC0UtweiRtgr

如果客户端通过jQuery调用REST API,则通过配置 xhrFields: { withCredentials: true } 可以实现发送请求时自动附上Cookie到请求报文头。如:

$.ajax({
   url: your_url,
   xhrFields: {
      withCredentials: true
   }
   ......
});

服务器会验证这个session id关联的session对象是否存在于服务器内存中(即检查客户端是否登录过),不存在(未登录)就报错,存在就通过。

第三步,客户端登出。
发送 DELETE 请求(请求报文头的Cookie字段中有session id)到认证url,如 rest/auth/session

2.2 服务器端实现

下面介绍一个服务器端认证相关REST API的实现。

login url后端响应的实现:

@Path("/rest/auth")
public class AuthService {
    private final String userID = "admin";
    private final String password = "password";

    @POST
    @Path("/session")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response login(@Context HttpServletRequest req,
            UserCredential userCredential) {   // userCredential保存着前端传过来的json数据
        String username = userCredential.getUsername();  // 用户在前端输入的用户名
        String password = userCredential.getPassword();  // 用户在前端输入的密码

        if (username.equals(userID) && password.equals(password)) { // 一般要去数据库验证用户名这密码是否正确,这里为简单只允许admin/password通过
            // create session
            HttpSession session = Req.getSession();   // getSession()找不到session时就会在服务器创建一个session,如果创建新session则会自动把session id放入响应报文头的Set-Cookie字段中
            session.setAttribute("user", userName);
            // set session to be expired in 30 minutes
            session.setMaxInactiveInterval(30 * 60);

            String sessionId = session.getId()
            // 返回包含session id的json数据给前端
        } else {
            // 返回用户名或密码不匹配的错误
        }
    }
}

logout url后端响应的实现:

    @DELETE
    @Path("/session")
    @Consumes(MediaType.APPLICATION_JSON)
    public void logout(@Context HttpServletRequest req) {
        HttpSession session = req.getSession(false);
        // getSession(false)中false是意思是找不到session就返回null,而不是创建新session
        // getSession由web服务器实现,它的具体过程大致为:首先去客户端的请求报文中查找session id(往往在Cookie中查找),
        // 然后在web服务器内存中查找以这个session id为key的session对象是否存在,存在就返回它。


        // invalidate the session if exists
        if (session != null) {
            String userName = (String) session.getAttribute("user");

            session.invalidate();                    // 关键步骤,让session失效!

            logger.info("{} logout.", userName);
        } else {
            logger.warn("No session found");
        }
    }

所有的REST APIs可以通过在web.xml中配置下面的filter来确保用户登录后才能使用:

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.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AuthFilter implements Filter {
    private final static Logger logger = LoggerFactory.getLogger(AuthFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        String uri = req.getRequestURI();

        HttpSession session = req.getSession(false);
        // getSession(false)中false是意思是找不到session就返回null,而不是创建新session
        // getSession由web服务器实现,它的具体过程大致为:首先去客户端的请求报文中查找session id(往往在Cookie中查找),
        // 然后在web服务器内存中查找以这个session id为key的session对象是否存在,存在就返回它。

        if (uri.endsWith("/rest/auth/session")) {
            // 请求 /rest/auth/session 时(登录时),可能没有认证,这时显然没有session,应该放过检查
            chain.doFilter(request, response);
        } else {
            // 如果在服务器端找不到相应session,说明还没有登录过,返回登录页面
            if (session == null) {
                logger.warn("Unauthorized access request, uri={}", uri);
                res.sendRedirect("/login.html");
            } else {
                chain.doFilter(request, response);
            }
        }
    }

    @Override
    public void destroy() {
    }
}

3 Tips

3.1 查看Web服务器创建的Session

应用代码中,在HttpServletRequest对象上调用 getSession() 后,如果Web服务器找不到当前HttpServletRequest对象关联的Session对象,那么就会创建一个新的Session对象。

如何查看Web服务器中有多少个有效的Session对象呢?下面以Tomcat 7为例进行说明。
首先打开 jconsole ,连接上Tomcat进程,切换到MBeans标签下,然后按图 2 所示进行展开后,可找到activeSessions属性,它就是当前有效的Session数量。除此外,我们还可以查看所有的session id,使用操作 listSessionIds (图 2 中左边树的最后一行)即可。

http_session_tomcat_view_sessions.png

Figure 2: Tomcat 7中查看有效的Session对象(activeSessions对应的数字,这个例子中为2)

参考:https://stackoverflow.com/questions/4069444/getting-a-list-of-active-sessions-in-tomcat-using-java


Author: cig01

Created: <2017-03-01 Wed 00:00>

Last updated: <2017-12-27 Wed 23:25>

Creator: Emacs 25.3.1 (Org mode 9.1.4)