基於Jwt資源無狀態認證權限管理系統bootshiro

基本信息

你好,這裏是MarkerHub,今天,咱們來聊下bootshiro項目,先來看下簡介:前端

基於springboot2+ shiro+jwt的真正rest api資源無狀態認證權限管理框架,開發人員無需關注權限問題,後端開發完api,前端頁面配置便可。mysql

技術棧

前端: usthe、angular5git

後端: springboot、shiro、jwt、druid、swagger二、mybatis、mybatis-generator、pagehelper、redisredis

功能大綱

用戶管理、 資源管理、 菜單管理、 API管理、 角色管理、 ...算法

學習目的

  • restful接口設計
  • 數據傳輸動態祕鑰加密
  • jwt過時自動刷新
  • 預防Xss攻擊

安裝教程

  • fork 項目到本身的倉庫(歡迎star^.^)
  • clone 項目到本地
  • 用idea導入
  • 更改開發環境mysql數據庫和redis地址(前提安裝數據庫並導入usthe.sql建立數據庫usthe)
  • 運行BootshiroApplication
  • bootshiro就能夠提供api了 http://localhost:8080
  • 推薦使用postman進行api調試

官方文檔

模塊分析

這個項目,咱們能夠主要學習一下怎麼給表單的密碼動態加密的,因此,咱們先來研究一下注冊和登陸功能。spring

動態密鑰加密

註冊功能

在項目的根目錄下,有個postman_test_example.json,這是一個postman的導出文件,咱們把這個文件從新導入到postman中,而後進行聯調。sql

石墨文檔_e2vyt1ptES.png

分別對應着登陸,調用認證,註冊3個接口。數據庫

咱們先來看下注冊功能的測試。json

石墨文檔_WYoEel6Q4L.png

由於是個post請求,參數是json數據,因此放在body中,其中password和userKey是個參數來的,那麼這兩個參數哪裏來的呢?咱們看到Pre-request Script腳本中。segmentfault

石墨文檔_KKxyoNyQ3F.png

這個腳本的大概意思是訪問http://localhost:8080/account/register?tokenKey=get 連接,獲取key和userKey,而後key通過AES算法加密以後獲得了參數password,因此咱們剛纔說註冊接口中的body的兩個參數就是這裏注入進去的。

passwork明顯通過了一層加密,這樣傳輸的過程當中,即便表單的數據被別人截取到了,也不能得到密碼,只有通過後端的AES解密以後,才能獲取到密碼。

那麼有兩個問題在這裏

  • 獲取key和userKey的方法在哪?
  • 後端如何解密的?

咱們先來看第一個問題:

咱們在過濾器中找到了PasswordFilter,是一個基於用戶名密碼的過濾器,繼承AccessControlFilter,咱們看下代碼:

@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {

    // 判斷若爲獲取登陸註冊加密動態祕鑰請求
    if (isPasswordTokenGet(request)) {
        //動態生成祕鑰,redis存儲祕鑰供以後祕鑰驗證使用,設置有效期5秒用完即丟棄
        String tokenKey = CommonUtil.getRandomString(16);
        String userKey = CommonUtil.getRandomString(6);
        try {
            redisTemplate.opsForValue().set("TOKEN_KEY_"+ IpUtil.getIpFromRequest(WebUtils.toHttp(request)).toUpperCase()+userKey.toUpperCase(),tokenKey,5, TimeUnit.SECONDS);
            // 動態祕鑰response返回給前端
            Message message = new Message();
            message.ok(1000,"issued tokenKey success")
                    .addData("tokenKey",tokenKey).addData("userKey", userKey.toUpperCase());
            RequestResponseUtil.responseWrite(JSON.toJSONString(message),response);

        }catch (Exception e) {
            LOGGER.warn("簽發動態祕鑰失敗"+e.getMessage(),e);
            Message message = new Message();
            message.ok(1000,"issued tokenKey fail");
            RequestResponseUtil.responseWrite(JSON.toJSONString(message),response);
        }
        return false;
    }

    // 判斷是不是登陸請求
    if(isPasswordLoginPost(request)){
       ...
    }
    ...
}

而咱們看下isPasswordTokenGet(request)方法就知道,其實就知足咱們的條件:

private boolean isPasswordTokenGet(ServletRequest request) {

    String tokenKey = RequestResponseUtil.getParameter(request,"tokenKey");

    return (request instanceof HttpServletRequest)
            && "GET".equals(((HttpServletRequest) request).getMethod().toUpperCase())
            &&  "get".equals(tokenKey);
}
因此當咱們發起[http://localhost:8080/account/register?tokenKey=get](http://localhost:8080/account/register?tokenKey=get)請求的時候,就會進入到這個過濾器的這個條件中,就獲取到了key和userKey,是隨機生成的:
String tokenKey = CommonUtil.getRandomString(16);
String userKey = CommonUtil.getRandomString(6);
redisTemplate.opsForValue().set("TOKEN_KEY_"+ IpUtil.getIpFromRequest(WebUtils.toHttp(request)).toUpperCase()+userKey.toUpperCase(),tokenKey,5, TimeUnit.SECONDS);

存到了redis中,有效期爲5秒。因此這裏動態生成了密鑰,並redis存儲祕鑰供以後祕鑰驗證使用,設置有效期5秒用完即丟棄。 好了,咱們已經弄清楚了第一個問題,那麼來看看第二個問題。

咱們找到com.usthe.bootshiro.controller.AccountController#accountRegister方法,其中最關鍵的代碼以下:

// 從Redis取出密碼傳輸加密解密祕鑰
String tokenKey = redisTemplate.opsForValue().get("TOKEN_KEY_" + IpUtil.getIpFromRequest(WebUtils.toHttp(request)).toUpperCase()+userKey);
String realPassword = AesUtil.aesDecode(password, tokenKey);
String salt = CommonUtil.getRandomString(6);
// 存儲到數據庫的密碼爲 MD5(原密碼+鹽值)
authUser.setPassword(Md5Util.md5(realPassword + salt));
authUser.setSalt(salt);
authUser.setCreateTime(new Date());

能夠看出,tokenKey就是加密解密的重點key,因此AesUtil.aesDecode解密以後獲得正在的密碼,而後加鹽保存到數據庫中便可。 總結一下上面咱們的請求過程:

在註冊以前,咱們先經過過濾器獲取到了動態密鑰,而後前端提交form註冊表單以後先經過js給password進行AES加密,而後發送內容到達後臺,後臺在redis中獲取動態密鑰,而後進行解密獲取到真實的密碼,再進行註冊。

完美!

登陸功能

同註冊功能。

關於這個項目其餘的內容大部分都是與shiro相關的,這裏我就再也不多作分析啦。剛興趣的同窗能夠再去細看哈。

結束語

好啦,今天先到這裏哈,若是你喜歡個人文章,能夠來markerhub.com看更多開源項目解析。 > markerhub.com,梳理Java知識,解析開源項目,歡迎關注公衆號【MarkerHub】

相關文章
相關標籤/搜索