"本文是[链接]的一个章节,该系列文章将介绍 [链接] 这款 Java 博客系统是如何从无到有的,希望大家能通过它对 Solo 从设计到实现有个直观地了解、能为想参与贡献的人介绍清楚项目,也希望能为给重复发明重新定义博客系统的人做个参考 Solo 的用户登录状态是依赖浏览器客户端 Cookie 的。服务端依赖容器提供的会 .."

Solo 登录状态和会话

本贴最后更新于 200 天前,其中的信息可能已经时移世异

本文是《Solo 从设计到实现》的一个章节,该系列文章将介绍 Solo 这款 Java 博客系统是如何从无到有的,希望大家能通过它对 Solo 从设计到实现有个直观地了解、能为想参与贡献的人介绍清楚项目,也希望能为给重复发明重新定义博客系统的人做个参考 ❤️

Solo 的用户登录状态是依赖浏览器客户端 Cookie 的。服务端依赖容器提供的会话,如果会话没有过期,则直接使用,如果过期则通过 Cookie 进行验证,验证通过的话就再次创建会话。

方法 Solos#getCurrentUser

for (int i = 0; i < cookies.length; i++) {
    final Cookie cookie = cookies[i];
    if (!COOKIE_NAME.equals(cookie.getName())) {
        continue;
    }

    final String value = Crypts.decryptByAES(cookie.getValue(), COOKIE_SECRET);
    final JSONObject cookieJSONObject = new JSONObject(value);

    final String userId = cookieJSONObject.optString(Keys.OBJECT_ID);
    if (StringUtils.isBlank(userId)) {
        break;
    }

    JSONObject user = userRepository.get(userId);
    if (null == user) {
        break;
    }

    final String userPassword = user.optString(User.USER_PASSWORD);
    final String token = cookieJSONObject.optString(Keys.TOKEN);
    final String hashPassword = StringUtils.substringBeforeLast(token, ":");
    if (userPassword.equals(hashPassword)) {
        login(request, response, user);

        return currentUser(request);
    }
}

方法 Solos#login

final JSONObject cookieJSONObject = new JSONObject();
cookieJSONObject.put(Keys.OBJECT_ID, user.optString(Keys.OBJECT_ID));
cookieJSONObject.put(User.USER_PASSWORD, user.optString(User.USER_PASSWORD));

final String random = RandomStringUtils.random(16);
cookieJSONObject.put(Keys.TOKEN, user.optString(User.USER_PASSWORD) + ":" + random);

final String cookieValue = Crypts.encryptByAES(cookieJSONObject.toString(), COOKIE_SECRET);
final Cookie cookie = new Cookie(COOKIE_NAME, cookieValue);
cookie.setPath("/");
cookie.setMaxAge(COOKIE_EXPIRY);
cookie.setHttpOnly(COOKIE_HTTP_ONLY);

response.addCookie(cookie);

可以看出,Cookie 通过 AES 加密后写回客户端,保证了一定的安全性。需要注意的是,密钥是配置在 latke.props 中的(cookieSecret),如果不配置则每次启动时生成随机密钥。

具体判断是否的点是通过 AOP 拦截器实现的,ConsoleAuthAdvice.javaConsoleAdminAuthAdvice.java 分别用于判断是否登录以及是否是管理员登录。

@Override
public void doAdvice(final HTTPRequestContext context, final Map<String, Object> args) throws RequestProcessAdviceException {
    final HttpServletRequest request = context.getRequest();
    if (!Solos.isAdminLoggedIn(request)) {
        final JSONObject exception401 = new JSONObject();
        exception401.put(Keys.MSG, "Unauthorized to request [" + request.getRequestURI() + "]");
        exception401.put(Keys.STATUS_CODE, HttpServletResponse.SC_UNAUTHORIZED);

        throw new RequestProcessAdviceException(exception401);
    }
}

对了,在大多数情况下,我们不应该把 request 传到 service 层,因为这样做的话 service 层就变成有状态的了。这里的“状态”主要是用户会话状态。当然,这也不是绝对的,有的场景在服务层是需要获取用户状态的:


回到全文目录:《Solo 从设计到实现》

感谢    关注    收藏    赞同    反对    举报    分享
3 回帖    
请输入回帖内容...
  • vinasis      

    👍

    感谢    赞同    反对    举报    分享       评论    回复
  • EvilCodes      

    D 哥这个用的是 SpringMVC 还是传统的 Servlet

    1 回复 
    感谢    赞同    反对    举报    分享       评论    回复
  • 88250            

    Latke 是基于 Servlet 的,SpringMVC 也是。

    感谢    赞同    反对    举报    分享       评论    回复