你好,這裏是MarkerHub,今天,咱們來聊下bootshiro項目,先來看下簡介:前端
基於springboot2+ shiro+jwt的真正rest api資源無狀態認證權限管理框架,開發人員無需關注權限問題,後端開發完api,前端頁面配置便可。mysql
前端: usthe、angular5git
後端: springboot、shiro、jwt、druid、swagger二、mybatis、mybatis-generator、pagehelper、redisredis
用戶管理、 資源管理、 菜單管理、 API管理、 角色管理、 ...算法
這個項目,咱們能夠主要學習一下怎麼給表單的密碼動態加密的,因此,咱們先來研究一下注冊和登陸功能。spring
在項目的根目錄下,有個postman_test_example.json,這是一個postman的導出文件,咱們把這個文件從新導入到postman中,而後進行聯調。sql
分別對應着登陸,調用認證,註冊3個接口。數據庫
咱們先來看下注冊功能的測試。json
由於是個post請求,參數是json數據,因此放在body中,其中password和userKey是個參數來的,那麼這兩個參數哪裏來的呢?咱們看到Pre-request Script腳本中。segmentfault
這個腳本的大概意思是訪問http://localhost:8080/account/register?tokenKey=get 連接,獲取key和userKey,而後key通過AES算法加密以後獲得了參數password,因此咱們剛纔說註冊接口中的body的兩個參數就是這裏注入進去的。
passwork明顯通過了一層加密,這樣傳輸的過程當中,即便表單的數據被別人截取到了,也不能得到密碼,只有通過後端的AES解密以後,才能獲取到密碼。
那麼有兩個問題在這裏
咱們先來看第一個問題:
咱們在過濾器中找到了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】