天天看點

springboot+jwt做api的token認證生成token驗證token是否有效擷取token中jwt資訊(主要使用者資訊)過濾器驗證token

本篇和大家分享jwt(json web token)的使用,她主要用來生成接口通路的token和驗證,其單獨結合springboot來開發api接口token驗證很是友善,由于jwt的token中存儲有使用者的資訊并且有加密,是以适用于分布式,這樣直接吧資訊存儲在使用者本地減速了服務端存儲sessiion或token的壓力;如下快速使用:

<!--jwt-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>
<!--阿裡 FastJson依賴-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.44</version>
</dependency>           

一般使用jwt來達到3種結果:

  • 生成token
  • 驗證token是否有效
  • 擷取token中jwt資訊(主要使用者資訊)

引入了jjwt依賴後,要生成token很友善;對于一個token來說,代表的是唯一并且不可逆的,是以我們在生成時需要增加一些唯一資料進去,比如下面的id:

long currentTime = System.currentTimeMillis();
return Jwts.builder()
        .setId(UUID.randomUUID().toString())
        .setIssuedAt(new Date(currentTime))  //簽發時間
        .setSubject("system")  //說明
        .setIssuer("shenniu003") //簽發者資訊
        .setAudience("custom")  //接收使用者
        .compressWith(CompressionCodecs.GZIP)  //資料壓縮方式

        .signWith(SignatureAlgorithm.HS256, encryKey) //加密方式
        .setExpiration(new Date(currentTime + secondTimeOut * 1000))  //過期時間戳
        .addClaims(claimMaps) //cla資訊
        .compact();           

通過uuid來标記唯一id資訊;當然在對token加密時需要用到秘鑰,jwt很是友善她支援了很多中加密方式如:HS256,HS265,Md5等複雜及常用的加密方式;

jwt生成的token中内容分為3個部分:head資訊,payload資訊,sign資訊,通常我們要做的是往payload增加一些使用者資訊(比如:賬号,昵稱,權限等,但不包含密碼);在對jwt的token有一定了解後,我們來看下真實生成的token值:

eyJhbGciOiJIUzI1NiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAFWMTQ7CIBSE7_LWkPDzaEsP4QnYINCIptX4INE0vbtg4sLlfPPN7HAtGWbwg1BKL4GrcbEcIwpujZF8iiEpjXFapAAG2ReYpUEcR2VxYED13Nb0ppLW3hP1eEnblqsQuiFfY0OhUrl3I70evweU_aFSejZhd7DlcDv5NTmYHUilHTD3rf_hAccHRTv--7YAAAA.i4xwoQtaWI0-dwHWN8uZ4DBm-vfli5bavYU9lRYxU5E           

token生成的時都會伴随者有一個失效的時間,在這我們可以通過setExpiration函數設定過期時間,記住jwt的有效時間不是滑動的,也就是說不做任何處理時,當到達第一次設定的失效時間時,就基本沒用了,要擷取token是否過期可以使用如下方式:

public static boolean isExpiration(String token, String encryKey) {
    try {
        return getClaimsBody(token, encryKey)
                .getExpiration()
                .before(new Date());
    } catch (ExpiredJwtException ex) {
        return true;
    }
}           

這裡使用了date的before來用擷取的過期時間和目前時間對比,判斷是否繼續有效,需要注意的是如果在token失效後再通過getClaimsBody(token, encryKey)擷取資訊,此時會報ExpiredJwtException錯誤,我們即可認為過期。

通常我們要把登入使用者資訊存儲在jwt生成的token中,這裡可以通過

addClaims(claimMaps) //cla資訊           

傳遞map來設定資訊,反過來要擷取token中的使用者資訊,我們需要這樣做:

return Jwts.parser()
        .setSigningKey(encryKey)
        .parseClaimsJws(token)
        .getBody();           

此時body擷取出來是Claims類型,我們需要從中擷取到使用者資訊,需要注意的是在addClaims存儲資訊的時候如果存儲的map值沒做過出來,那完整的實體對象存儲進去後會映射成一個LinkHasMap類型,如下:

springboot+jwt做api的token認證生成token驗證token是否有效擷取token中jwt資訊(主要使用者資訊)過濾器驗證token

是以通常會在存儲的時候json化,如下代碼:

claimMaps.forEach((key, val) -> {
    claimMaps.put(key, JSON.toJSONString(val));
});           

再來就是通過get方法擷取我們存儲進去的資訊,并json反序列化:

/**
* 擷取body某個值
*
* @param token
* @param encryKey
* @param key
* @return
*/
public static Object getVal(String token, String encryKey, String key) {
    return getJws(token, encryKey).getBody().get(key);
}

/**
 * 擷取body某個值,json字元轉實體
 *
 * @param token
 * @param encryKey
 * @param key
 * @param tClass
 * @param <T>
 * @return
 */
public static <T> T getValByT(String token, String encryKey, String key, Class<T> tClass) {
    try {
        String strJson = getVal(token, encryKey, key).toString();
        return JSON.parseObject(strJson, tClass);
    } catch (Exception ex) {
        return null;
    }
}           

來到這裡一個Jwt的Util代碼基本就完成了,下面給出完整的代碼例子,僅供參考:

public class JwtUtil {

    /**
     * 擷取token - json化 map資訊
     *
     * @param claimMaps
     * @param encryKey
     * @param secondTimeOut
     * @return
     */
    public static String getTokenByJson(Map<String, Object> claimMaps, String encryKey, int secondTimeOut) {
        return getToken(claimMaps, true, encryKey, secondTimeOut);
    }

    /**
     * 擷取token
     *
     * @param claimMaps
     * @param isJsonMpas
     * @param encryKey
     * @param secondTimeOut
     * @return
     */
    public static String getToken(Map<String, Object> claimMaps, boolean isJsonMpas, String encryKey, int secondTimeOut) {

        if (isJsonMpas) {
            claimMaps.forEach((key, val) -> {
                claimMaps.put(key, JSON.toJSONString(val));
            });
        }
        long currentTime = System.currentTimeMillis();
        return Jwts.builder()
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date(currentTime))  //簽發時間
                .setSubject("system")  //說明
                .setIssuer("shenniu003") //簽發者資訊
                .setAudience("custom")  //接收使用者
                .compressWith(CompressionCodecs.GZIP)  //資料壓縮方式

                .signWith(SignatureAlgorithm.HS256, encryKey) //加密方式
                .setExpiration(new Date(currentTime + secondTimeOut * 1000))  //過期時間戳
                .addClaims(claimMaps) //cla資訊
                .compact();
    }

    /**
     * 擷取token中的claims資訊
     *
     * @param token
     * @param encryKey
     * @return
     */
    private static Jws<Claims> getJws(String token, String encryKey) {
        return Jwts.parser()
                .setSigningKey(encryKey)
                .parseClaimsJws(token);
    }

    public static String getSignature(String token, String encryKey) {
        try {
            return getJws(token, encryKey).getSignature();
        } catch (Exception ex) {
            return "";
        }
    }

    /**
     * 擷取token中head資訊
     *
     * @param token
     * @param encryKey
     * @return
     */
    public static JwsHeader getHeader(String token, String encryKey) {
        try {
            return getJws(token, encryKey).getHeader();
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * 擷取payload body資訊
     *
     * @param token
     * @param encryKey
     * @return
     */
    public static Claims getClaimsBody(String token, String encryKey) {
        return getJws(token, encryKey).getBody();
    }

    /**
     * 擷取body某個值
     *
     * @param token
     * @param encryKey
     * @param key
     * @return
     */
    public static Object getVal(String token, String encryKey, String key) {
        return getJws(token, encryKey).getBody().get(key);
    }

    /**
     * 擷取body某個值,json字元轉實體
     *
     * @param token
     * @param encryKey
     * @param key
     * @param tClass
     * @param <T>
     * @return
     */
    public static <T> T getValByT(String token, String encryKey, String key, Class<T> tClass) {
        try {
            String strJson = getVal(token, encryKey, key).toString();
            return JSON.parseObject(strJson, tClass);
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * 是否過期
     *
     * @param token
     * @param encryKey
     * @return
     */
    public static boolean isExpiration(String token, String encryKey) {
        try {
            return getClaimsBody(token, encryKey)
                    .getExpiration()
                    .before(new Date());
        } catch (ExpiredJwtException ex) {
            return true;
        }
    }

    public static String getSubject(String token, String encryKey) {
        try {
            return getClaimsBody(token, encryKey).getSubject();
        } catch (Exception ex) {
            return "";
        }
    }
}           

過濾器驗證token

有了基本的JwtUtil工具,我們需要用到springboot項目中,一般來說對于登入授權token驗證可以通過過濾器來操作,這裡建立一個AuthenFilter,用于對post請求過來的token做驗證:

public class AuthenFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest rq = (HttpServletRequest) servletRequest;
        HttpServletResponse rp = (HttpServletResponse) servletResponse;
        RpBase rpBase = new RpBase();
        try {
            //隻接受post
            if (!rq.getMethod().equalsIgnoreCase("post")) {
                filterChain.doFilter(servletRequest, servletResponse);
                return;
            }

            String token = rq.getHeader("token");
            if (StringUtils.isEmpty(token)) {
                rpBase.setMsg("無token");
                return;
            }

            //jwt驗證
            MoUser moUser = JwtUtil.getValByT(token, WebConfig.Token_EncryKey, WebConfig.Login_User, MoUser.class);
            if (moUser == null) {
                rpBase.setMsg("token已失效");
                return;
            }

            System.out.println("token使用者:" + moUser.getNickName());

            filterChain.doFilter(servletRequest, servletResponse);
        } catch (Exception ex) {
        } finally {
            if (!StringUtils.isEmpty(rpBase.getMsg())) {
                rp.setCharacterEncoding("utf-8");
                rpBase.setCode(HttpStatus.BAD_REQUEST.value());
                rp.getWriter().write(JSON.toJSONString(rpBase));
            }
        }
    }
}           

要是自定義過濾器AuthenFilter生效,還需要把她注冊到容器中,這裡通過編碼方式,當然還可以通過@WebFilter注解來加入到容器中:

@Configuration
public class WebFilterConfig {

    @Bean
    public FilterRegistrationBean setFilter() {

        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new AuthenFilter());
        registrationBean.addUrlPatterns("/api/*");
        registrationBean.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);

        return registrationBean;
    }
}           

注意addUrlPatterns比對的是過濾器作用的url連接配接,根據需求而定;為了驗證效果,這裡我建立了兩個接口getToken和t0,分别是擷取token和post查詢接口,代碼如是:

@RestController
public class TestController {

    @PostMapping("/api/t0")
    public String t0() throws MyException {

        return UUID.randomUUID().toString();
    }

    @GetMapping("/token/{userName}")
    public String getToken(@PathVariable String userName) {

        MoUser moUser = new MoUser();
        moUser.setUserName(userName);
        moUser.setNickName(userName);

        Map<String, Object> map = new HashMap<>();
        map.put(WebConfig.Login_User, moUser);

        return JwtUtil.getTokenByJson(map,
                WebConfig.Token_EncryKey,
                WebConfig.Token_SecondTimeOut);
    }
}           

最終要獲通過head傳遞token值來通路t01接口,得到如下結果:

springboot+jwt做api的token認證生成token驗證token是否有效擷取token中jwt資訊(主要使用者資訊)過濾器驗證token

token在有效時間後通路直接失敗,從新擷取token并通路t01接口,得到成功的資訊:

springboot+jwt做api的token認證生成token驗證token是否有效擷取token中jwt資訊(主要使用者資訊)過濾器驗證token