springboot集成JWT小結

前言

SpringBoot項目集成"JWT",首先了解一下JWT,本文項目屬於前後端分離項目,後端要做攔截處理以及安全驗證,採用“JWT”生成token信息,前端登錄時將後端生成的token信息跟隨登錄回執信息傳到前端,之後前端每次請求均要攜帶token信息,以便後端攔截驗證。

JWT maven依賴處理

本文項目爲maven項目,需要引入maven依賴

<!--引入JWT依賴,由於是基於Java,所以需要的是java-jwt-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.4.0</version>
</dependency>

JWT工具類

JwtUtil工具類,特別說明的是跟TUser相關的信息,均可根據實際情況自定義

/**
 * @program: share
 * @description: 工具類
 * @author: Mr.Jkx
 * @create: 2020-03-11 19:20
 */
public class JwtUtil {

    /**
     * JWT_WEB_TTL:WEBAPP應用中token的有效時間,默認30分鐘
     */
    public static final long JWT_WEB_TTL = 30 * 60 * 1000;

    /**
     * 用戶登錄成功後生成Jwt
     * 使用Hs256算法  私匙使用用戶密碼
     *
     * @param user 登錄成功的user對象
     * @return
     */
    public static String createJWT(TUser user) {
        //指定簽名的時候使用的簽名算法,也就是header那部分,jjwt已經將這部分內容封裝好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        //生成JWT的時間
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        //創建payload的私有聲明(根據特定的業務需要添加,如果要拿這個做驗證,一般是需要和jwt的接收方提前溝通好驗證方式的)
        Map<String, Object> claims = new HashMap<>();
        // id和name爲自定義信息,只要用於後端攔截器攔截請求之後,解析JWT數據做安全驗證使用
        claims.put("id", user.getUserId());
        claims.put("username", user.getUserName());

        //生成簽名的時候使用的祕鑰secret,這個方法本地封裝了的,一般可以從本地配置文件中讀取,切記這個祕鑰不能外露哦。它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味着客戶端是可以自我簽發jwt了。
        String key = user.getPassword();
        //生成簽發人
        String subject = user.getUserName();
        //下面就是在爲payload添加各種標準聲明和私有聲明瞭
        //這裏其實就是new一個JwtBuilder,設置jwt的body
        JwtBuilder builder = Jwts.builder()
                //如果有私有聲明,一定要先設置這個自己創建的私有的聲明,這個是給builder的claim賦值,一旦寫在標準的聲明賦值之後,就是覆蓋了那些標準的聲明的
                .setClaims(claims)
                //設置jti(JWT ID):是JWT的唯一標識,根據業務需要,這個可以設置爲一個不重複的值,主要用來作爲一次性token,從而回避重放攻擊。
                .setId(UUIDUtil.getUUID())
                //iat: jwt的簽發時間
                .setIssuedAt(now)
                //代表這個JWT的主體,即它的所有人,這個是一個json格式的字符串,可以存放什麼userid,roldid之類的,作爲什麼用戶的唯一標誌。
                .setSubject(subject)
                //設置簽名使用的簽名算法和簽名使用的祕鑰
                .signWith(signatureAlgorithm, key);
        long expMillis = nowMillis + JWT_WEB_TTL;
        Date exp = new Date(expMillis);
        //設置過期時間
        builder.setExpiration(exp);
        return builder.compact();
    }

    /**
     * Token的解密
     *
     * @param token 加密後的token
     * @param user  用戶的對象
     * @return
     */
    public static Claims parseJWT(String token, TUser user) {
        //簽名祕鑰,和生成的簽名的祕鑰一模一樣
        String key = user.getPassword();

        //得到DefaultJwtParser
        Claims claims = Jwts.parser()
                //設置簽名的祕鑰
                .setSigningKey(key)
                //設置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }

    /**
     * 校驗token
     * 在這裏可以使用官方的校驗,我這裏校驗的是token中攜帶的用戶名於數據庫一致的話就校驗通過
     *
     * @param token
     * @param user
     * @return
     */
    public static Boolean isVerify(String token, TUser user) {
        //簽名祕鑰,和生成的簽名的祕鑰一模一樣
        String key = user.getPassword();

        //得到DefaultJwtParser
        Claims claims = Jwts.parser()
                //設置簽名的祕鑰
                .setSigningKey(key)
                //設置需要解析的jwt
                .parseClaimsJws(token).getBody();

        if (claims.get("username").equals(user.getUserName())) {
            return true;
        }

        return false;
    }
}

後臺根據JWT生成token

後端生成token信息很簡單,直接調用JWT的工具類方法即可

public Map<String, Object> selUser(TUser user) {
        Map<String, Object> loginMap = new HashMap<>();
        TUser tUser = tUserMapper.selUser(user);
        if (tUser != null) {
            // 登錄成功添加token緩存信息
            loginMap.put("statusCode", "200");
            // 調用token信息生成token信息
            loginMap.put("token", JwtUtil.createJWT(tUser));
        } else {
            loginMap.put("statusCode", "300");
        }
        return loginMap;
    }

登錄回執數據(測試數據)

{
    "statusCode": "200",
    "token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlkIjoiNDNkMjE0MjliNWQ0NDU4MTllMDkzN2ViMTc1NDYzMjgiLCJleHAiOjE1ODUyMDQ0NDgsImlhdCI6MTU4NTIwNDM4OCwianRpIjoiYmE1ZTdkOGI5NGQ3NDkzMmI2NGYxYmU4N2YzNzYxMzYiLCJ1c2VybmFtZSI6ImFkbWluIn0.emrxqF9S2Yh7rn2yWdMJ56aMLNMS-VpgAs65VM2ErhA"
} 

請求攔截,token信息驗證

本文驗證方式爲,獲取前端請求攜帶的token信息,解析token信息,然後請求數據庫,驗證此數據是否存在,然後驗證數據的真實性,攔截器中的回執數據可自定義,此處不過多描述;可參閱作者自己整理的攔截器

/**
 * @program: share
 * @description: 登錄攔截器
 * @author: Mr.Jkx
 * @create: 2020-03-11 17:22
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(LoginInterceptor.class);
    @Resource
    private UserMapper UserMapper;

    /**
     * 預處理回調方法,實現處理器的預處理
     * 返回值:true表示繼續流程;false表示流程中斷,不會繼續調用其他的攔截器或處理器
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        // 處理OPTIONS請求
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "86400");
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Content-Type", "text/html;charset=utf-8");
        if (request.getMethod().equals("OPTIONS")) {
            response.setStatus(HttpServletResponse.SC_OK);
            return false;
        }
        String requestURI = request.getRequestURI();
        LOGGER.info("-------攔截請求鏈接:{},時間:{}-------", requestURI, DateUtil.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));

        // 獲取 token 信息
        String token = request.getHeader("token");
        if (StringUtils.isNotBlank(token)) {
            DecodedJWT decode = JWT.decode(token);
            // token 有效期
            Date expiresAt = decode.getExpiresAt();
            // 判斷此token信息是否在有效期內(時間設置參考“JwtUtil”)
            if (expiresAt.getTime() > new Date().getTime()) {
                // 操作用戶信息,此處“id”爲工具類自定義信息
                String userId = decode.getClaim("id").asString();
                if (StringUtils.isNotBlank(userId)) {
                    TUser tUser = UserMapper.selectByPrimaryKey(userId);
                    if (null != tUser) {
                    	// 將得到的數據信息驗證真僞
                        if (JwtUtil.isVerify(token, tUser)) {
                            return true;
                        } else {
                            returnJson(response, "用戶數據錯誤");
                        }
                    } else {
                        returnJson(response, "用戶不存在");
                    }
                }
            } else {
                returnJson(response, "token失效");
            }
        } else {
            returnJson(response, "沒有token");
            returnJson(response, "requestURI");
        }
        return false;
    }

    /**
     * 後處理回調方法,實現處理器(controller)的後處理,但在渲染視圖之前
     * 此時我們可以通過modelAndView對模型數據進行處理或對視圖進行處理
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    /**
     * 整個請求處理完畢回調方法,即在視圖渲染完畢時回調,
     * 如性能監控中我們可以在此記錄結束時間並輸出消耗時間,
     * 還可以進行一些資源清理,類似於try-catch-finally中的finally,
     * 但僅調用處理器執行鏈中
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }

    /**
     * @Description: 攔截回執數據
     * @Author: Mr.Jkx
     * @date: 2020/3/12 13:46
     */
    private void returnJson(HttpServletResponse response, String msgData) {
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        try {
            writer = response.getWriter();
            Msg msg = Msg.fail().add("info", msgData);
            String res = JsonUtil.toJsonString(msg);
            writer.print(res);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }
}

本文爲作者爲解決問題查詢整理的一個小結,可能存在不足或者安全隱患,非常期待大家的評論留言,一方面檢驗自己的一個學習整理成果,另一方面還是想跟大家學習,歡迎大家批評指正,謝謝!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章