十幾行代碼實現分布式 Session

sandag 發佈 2020-01-08T11:05:47+00:00

前言最近喊同事吃飯的時候他在測接口,於是我就在他後面等了一會。他測的是一個需要登錄的接口,步驟如下1.先登錄系統從接口的request head中拿到cookie的值2.把這個cookie的值粘到postman請求的header中3.

前言

最近喊同事吃飯的時候他在測接口,於是我就在他後面等了一會。他測的是一個需要登錄的接口,步驟如下

1.先登錄系統從接口的request head中拿到cookie的值

2.把這個cookie的值粘到postman請求的header中

3.發送請求測試接口

我發一張圖,你大概可以想到測的姿勢

說真的,這波操作真的秀到我了,於是我把我的騷操作告訴了他,如果大家也沒用過這種方式的話,可否在下面評論一句學到了,我看看不會用的人多不。

寫登錄例子的時候我又覺得文章太短不好,嗯,太短不好。於是在前面分享一下分布式session的實現吧。分布式session在企業中基本上都會用到。因為登錄系統不可能只有一個,所以要在2個系統間共享session。我寫個demo,總共也就十幾行代碼,足夠幫你理解怎麼實現分布式session了。

十幾行代碼實現分布式session

請求controller

@RestController
@RequestMapping("user")
public class UserAuthController {

    // 1 hour
    private static final int TOKEN_EXPIRE_SECONDS = 1 * 60 * 60;
    private static final String COOKIE_NAME = "FL";

    @RequestMapping("login")
    public ServerResponse login(HttpServletResponse response) {
        // 這裡的cookie值我隨便寫了一個字符串"token"
        // 生產環境中token有一套複雜的生成規則,例如MD5(用戶名+登陸ip+當前時間)後的字符串
        // 登錄的時候我們先根據輸入的用戶名拿到用戶信息
        // 然後把 token->用戶信息 的映射關係放到redis中,或者mysql中
        Cookie cookie = new Cookie(COOKIE_NAME, "token");
        // 設置cookie的過期時間為1個小時,即1個小時內你都不用重新登陸
        cookie.setMaxAge(TOKEN_EXPIRE_SECONDS);
        response.addCookie(cookie);
        return ServerResponse.success();
    }

    @RequestMapping("cart")
    public ServerResponse cart() {
        return ServerResponse.success();
    }

}

可以看到我寫了2個接口,一個登陸接口,一個訪問購物車接口,ServerResponse是我定義的返回對象,其中訪問購物車接口需要登陸,我在攔截器中做了校驗

整個登陸過程做的是其實很簡單,生成一個cookie,名字為FL,值為字符串"token",(生產環境中token有一套複雜的生成規則,例如MD5(用戶名+登陸ip+當前時間)後的字符串)放在reponse中,並把token->用戶信息的映射關係放在redis中或者mysql中

放在redis中你可以使用命令

setnx token 用戶信息 1h

來保存映射關係。用mysql你可以用用戶表來保存映射關係

idtokentokenExpire用戶idtoken值token的失效時間

當然token的失效時間和cookie的失效時間保持一致,失效時間可以用

System.currentTimeMillis()+1h算出

登陸校驗攔截器

public class LoginInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Cookie cks [] = request.getCookies();
        if (cks == null || cks.length == 0) {
            this.processError(response);
            return false;
        }
        // 這裡cookie的名字在UserAuthController裡面定義了
        String cookieName = "FL";
        String token = null;
        for (Cookie ck : cks) {
            if (cookieName.equals(ck.getName())) {
                token = ck.getValue();
                break;
            }
        }
        if (token == null) {
            this.processError(response);
            return false;
        }
        // 在登錄的時候我們設置了 token-> 用戶信息 的映射關係
        // 根據token從redis或者mysql中拿到用戶信息,然後設置到requst請求中,供後續請求使用
        // 分布式session就這樣實現了
        request.setAttribute("userInfo", null);
        return true;
    }

    private void processError(HttpServletResponse response) throws IOException {
        response.reset();
        response.setContentType("application/json;charset=UTF-8");
        response.setCharacterEncoding("utf-8");
        response.getWriter().write("請登錄");
    }
}

上述代碼我沒做失效時間的驗證,簡單說一下。

如果用的是redis,根據token拿不到用戶信息,說明登錄信息已經失效了。

用的是mysql,只要判斷請求時間是否在token失效時間內,如果不在token失效時間內,則用戶需要重新登錄。

配置攔截器

@Configuration
public class DemoWebMvcConfigurerAdapter extends WebMvcConfigurationSupport {

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login");
    }
}

單機session,例如tomcat是把 token->用戶 的映射關係放在ConcurrentHashMap中

而分布式session是要我們自己維護這個映射關係,並且多個實例都能訪問到這個映射關係。

測試驗證

我直接訪問

http://127.0.0.1:8081/user/cart

頁面 顯示請登錄先訪問

http://127.0.0.1:8081/user/login

再訪問

http://127.0.0.1:8081/user/cart

正常返回

如下內容

{
    "status": 0,
    "msg": "success"
}

訪問http://127.0.0.1:8081/user/login Reponse Headers為

Content-Type: application/json;charset=UTF-8
Date: Wed, 25 Dec 2019 12:18:03 GMT
Set-Cookie: FL=token; Max-Age=3600; Expires=Wed, 25-Dec-2019 13:18:03 GMT
Transfer-Encoding: chunked

可以看到設置了cookie(FL=token)

再訪問http://127.0.0.1:8081/user/cart Request Headers為

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: max-age=0
Connection: keep-alive
Cookie: FL=token

可以看到帶上了cookie(FL=token)

秒測登錄接口

這個操作其實太簡單了,在postman中,你只要先請求一下登錄接口,在後續請求的接口中會自動帶上cookie,不用你每次都粘header。在cookie失效之前你都不用再次點登錄接口,一直測就行

關鍵字: