springboot 整合jwt

1、引入jwt的jar包

<!--JWT(Json Web Token)登錄支持-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

2、jwt工具類

package com.linhaijing.javaxxbjcommon.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * add by linhaijing 2020年7月3日21:14:24
 * JwtToken生成的工具類
 * JWT token的格式:header.payload.signature
 * header的格式(算法、token的類型):
 * {"alg": "HS512","typ": "JWT"}
 * payload的格式(用戶名、創建時間、生成時間):
 * {"sub":"wang","created":1489079981393,"exp":1489684781}
 * signature的生成算法:
 * HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
 * https://github.com/shenzhuan/mallplus on 2018/4/26.
 */
@Component
public class JwtTokenUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_CREATED = "created";
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;

    /**
     * 根據負責生成JWT的token
     */
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 從token中獲取JWT中的負載
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            LOGGER.error("JWT格式驗證失敗:{}", token);
        }
        return claims;
    }

    /**
     * 生成token的過期時間
     */
    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    /**
     * 從token中獲取登錄用戶名
     */
    public String getUserNameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 驗證token是否還有效
     *
     * @param token       客戶端傳入的token
     * @param loginName 從數據庫中查詢出來的用戶登錄名
     */
    public boolean validateToken(String token, String loginName) {
        if (StringUtils.isNotBlank(loginName)) {
            String username = getUserNameFromToken(token);
            return username.equals(loginName) && !isTokenExpired(token);
        }
        return false;
    }

    /**
     * 判斷token是否已經失效
     */
    public boolean isTokenExpired(String token) {
        Date expiredDate = getExpiredDateFromToken(token);
        return expiredDate.before(new Date());
    }

    /**
     * 從token中獲取過期時間
     */
    private Date getExpiredDateFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }

    /**
     * 根據用戶信息生成token
     */
//    public String generateToken(String loginName) {
//        Map<String, Object> claims = new HashMap<>();
//        claims.put(CLAIM_KEY_USERNAME, loginName);
//        claims.put(CLAIM_KEY_CREATED, new Date());
//        return generateToken(claims);
//    }

    public String generateToken(String loginName) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, loginName);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    /**
     * 判斷token是否可以被刷新
     */
    public boolean canRefresh(String token) {
        return !isTokenExpired(token);
    }

    /**
     * 刷新token
     */
    public String refreshToken(String token) {
        Claims claims = getClaimsFromToken(token);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }
}

3、登錄時返回jwt的token 

String token = jwtTokenUtil.generateToken("token中攜帶的用戶信息,比如:id,username");
resultMap.put("token", tokenHead + token);

4、前端保存token

c.request(c.config.loginSubmit, "post", JSON.stringify(userPO), "application/json", false, function (result) {
    if(result.code == 200){
        var data = result.data;
        showMsg("登錄成功");
        //保存token
        window.localStorage.setItem('token', data.token);
        _this.login = false;
        var url = window.location.href.split("targetPage=");
        if(url.length == 2){
            window.location.href = url[1];
        }else{
            window.location.href = "/index.html";
        }
    }else{
        showMsg(result.msg);
        _this.refreshCaptcha();
    }
})

5、前端請求接口時每個方法的header中攜帶token

c.request = function(url, get, data, contentType, async, callback) {
   $.ajax({
      url: url,
                type: get ? get : post,
                data: data,
                async: async,
                dataType: 'json',
                contentType: contentType,
                timeout: 100000,
                cache:false,
                processData: false,
                headers: {
                    Authorization: window.localStorage.getItem("token")
                },
                success: function(data) {
                if (typeof callback == "function") {
                    (function() {
                        layer.close(index);
                        callback(data);
                    })();
                }
            },
            error: function(XMLHttpRequest, textStatus, errorThrown) {
                if(XMLHttpRequest.responseText == 403){
                    showMsg("登錄已過期,請重新登錄");
                    // setTimeout(function () {
                        window.location.href = "/login.html";
                    // },1500);
                }
            layer.close(index);
            },
            //請求之前
            beforeSend: function() {
                  // index = layer.load(2, {time: 10*1000});
                index =layer.load();
            },
            complete: function(XMLHttpRequest, status) {
                if (status == 'timeout') {
                layer.close(index);
                } else if (status == 'error') {
                layer.close(index);
                }
      }
   });
};

6、後端對需要登錄的接口進行攔截,並校驗token是否過期,以及校驗後端對應的用戶信息緩存中是否存在,即判斷登錄是否過期

import com.linhaijing.javaxxbjcommon.utils.JwtTokenUtil;
import com.linhaijing.javaxxbjpcweb.util.GetGlobalUserBeanUtil;
import com.linhaijing.javaxxbjssoapi.po.UserPO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 功能描述:登錄攔截器
 *
 * @Author : 林海靜
 * @CreateTime: 2019/12/30 18:05
 */
@Slf4j
public class LoginInterceptor extends HandlerInterceptorAdapter {

    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    public LoginInterceptor(){}

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判斷jwt token
        UserPO userPO = GetGlobalUserBeanUtil.getGlobalUserBean();
        if(userPO == null){
            log.info("LoginInterceptor->jwt爲空");
            if (this.ajaxHandleFail(request, response)) {
                return false;
            }
            response.sendRedirect("/index.html");
            return false;
        }
        return true;
    }
    /**
    * 功能描述:判斷是否是ajax請求
    * @Author: 林海靜
    * @CreateTime: 2019/12/30 18:23
    * <>
    * @params:
    * @return:
    * @Exception:
    */
    private boolean ajaxHandleFail(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if ("XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"))) {
            response.setStatus(403);
            response.getWriter().write("403");
            log.info("ajax請求session超時了");
            response.getWriter().close();
            return true;
        } else {
            return false;
        }
    }

}
    @Autowired
    public static UserPO getGlobalUserBean() {
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        String tokenPre = tokenHeader;
        String authHeader = request.getHeader(tokenPre);
        log.info("getGlobalUserBean()---->authHeader=" + authHeader);
        if (authHeader != null && authHeader.startsWith("Bearer")) {
            String authToken = authHeader.substring("Bearer".length());
            //校驗token時間是否過期
            if (StringUtils.isNotBlank(authToken) && !jwtTokenUtil.isTokenExpired(authToken)) {
                String loginName = jwtTokenUtil.getUserNameFromToken(authToken);
                log.info("獲取 "+loginName+" 的信息");
                String key = GlobalConstants.JAVAXXBJ_USER_SESSION + ":" + loginName;
                return userServiceFeign.getBySessionKey(key);
            }
            log.info("getGlobalUserBean()---->jwt token已過期");
            return null;
        }
        log.info("getGlobalUserBean()---->用戶未登錄,獲取當前用戶信息:null");
        return null;
    }

 

7、如果過期,則返回403,前端跳轉到登錄界面

c.request = function(url, get, data, contentType, async, callback) {
   $.ajax({
      url: url,
                type: get ? get : post,
                data: data,
                async: async,
                dataType: 'json',
                contentType: contentType,
                timeout: 100000,
                cache:false,
                processData: false,
                headers: {
                    Authorization: window.localStorage.getItem("token")
                },
                success: function(data) {
                if (typeof callback == "function") {
                    (function() {
                        layer.close(index);
                        callback(data);
                    })();
                }
            },
            error: function(XMLHttpRequest, textStatus, errorThrown) {
                if(XMLHttpRequest.responseText == 403){
                    showMsg("登錄已過期,請重新登錄");
                        window.location.href = "/login.html";
                }
            layer.close(index);
            },
            //請求之前
            beforeSend: function() {
                  // index = layer.load(2, {time: 10*1000});
                index =layer.load();
            },
            complete: function(XMLHttpRequest, status) {
                if (status == 'timeout') {
                layer.close(index);
                } else if (status == 'error') {
                layer.close(index);
                }
      }
   });
};

8、系統退出時,刪除redis中的用戶信息,則表示服務端用戶信息已經過期,即使前端token還存在,但也不能進行登錄

 @Override
    @Transactional
    public ResultObject logout(UserPO userPO, String session) {

        String key = GlobalConstants.JAVAXXBJ_USER_SESSION + ":" + userPO.getLoginName();
        Boolean delReturn = jedisClient.del(key);
        if(!delReturn) throw new SysException("退出失敗,請重試");
        return ResultObject.ok();
    }

 

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