Springboot入門教程(4)-使用jwt做登錄攔截

JWT,即JSON Web Tokens,是用來解決web項目登錄時的token問題的一個解決方案,目前使用的也比較多。官網(wǎng)顯示用于java的jwt庫一共有6個,這里我們以其中auth0的使用為例,說明一下如何在Springboot項目中用jwt做登錄攔截。

依舊是在我們的schoolmanager項目上做修改。首先在build.gradle的dependencies中添加依賴包

implementation 'com.auth0:java-jwt:3.8.3'

接著需要新建一個jwt的工具類,先新建一個utils的工具包,在里面新建一個JWTUtil的工具類,代碼如下

public class JWTUtil {
    private static final String SECRET = "euitrydbnseotu9347857025620";

    private static String ISSUER = "sys_user";

    /**
     * 生成token
     * @param claims
     * @param expireDatePoint  過期時間點
     * @return
     */
    public static String genToken(Map<String, String> claims, Date expireDatePoint){

        try {
            //使用HMAC256進行加密
            Algorithm algorithm = Algorithm.HMAC256(SECRET);

            //創(chuàng)建jwt
            JWTCreator.Builder builder = JWT.create().
                    withIssuer(ISSUER). //發(fā)行人
                    withExpiresAt(expireDatePoint); //過期時間點

            //傳入?yún)?shù)
            claims.forEach((key,value)-> {
                builder.withClaim(key, value);
            });

            //簽名加密
            return builder.sign(algorithm);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解密jwt
     * @param token
     * @return
     * @throws RuntimeException
     */
    public static Map<String,String> verifyToken(String token) throws RuntimeException{
        Algorithm algorithm = null;
        try {
            //使用HMAC256進行加密
            algorithm = Algorithm.HMAC256(SECRET);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException(e);
        }

        //解密
        JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
        Map<String, String> resultMap = new HashMap<>();
        try {
            DecodedJWT jwt =  verifier.verify(token);
            Map<String, Claim> map = jwt.getClaims();
            map.forEach((k,v) -> resultMap.put(k, v.asString()));
        } catch (TokenExpiredException e){

        }
        return resultMap;
    }
}

這里有兩個方法,一個加密生成token,一個解密。其中SECRET和ISSUER都是可以自己定義的加密參數(shù),還能傳入過期時間。解密的時候要注意的是捕獲了TokenExpiredException,就是token過期了,這時會返回一個空的resultMap。

然后就是寫攔截器了。新建一個interceptor的包,在里面新建一個TokenInterceptor類,代碼如下

@Component
public class TokenInterceptor implements HandlerInterceptor {
/**
     * 預處理回調(diào)方法,實現(xiàn)處理器的預處理(如檢查登陸),第三個參數(shù)為響應的處理器,自定義Controller
     * 返回值:true表示繼續(xù)流程(如調(diào)用下一個攔截器或處理器);false表示流程中斷
(如登錄檢查失敗),不會繼續(xù)調(diào)用其他的攔截器或處理器,此時我們需要通過response來產(chǎn)生響應;
   */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //前端可能會發(fā)送OPTIONS請求,要先過濾
        if (request.getMethod().equals("OPTIONS")){
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
        }
        response.setCharacterEncoding("utf-8");
        String token = request.getHeader("token");
        String responseData = "{\"code\":401,\"message\":\"Unauthorized\"}";
        //token不存在
        if(null != token) {
            Map<String, String> login = JWTUtil.verifyToken(token);
            String username = request.getHeader("username");
            //解密token后的loginId與用戶傳來的loginId不一致,一般都是token過期
            if(null != username && null != login) {
                if(username.equals(login.get("username"))) {
                    return true;
                }
                else{
                    responseMessage(response, response.getWriter(), responseData);
                    return false;
                }
            }
            else
            {
                responseMessage(response, response.getWriter(), responseData);
                return false;
            }
        }
        else
        {
            responseMessage(response, response.getWriter(), responseData);
            return false;
        }
    }

    private void responseMessage(HttpServletResponse response, PrintWriter out, String json) {
        response.setContentType("application/json; charset=utf-8");
        out.print(json);
        out.flush();
        out.close();
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

這個類繼承了HandlerInterceptor,它是Spring框架中的攔截器,用于對請求進行預處理和后處理,所以我們用它來攔截請求,檢驗token。預處理在preHandle中,這里會接收header中傳入的username和token,先看是否存在,然后解密token,看username是否一致,如果token過期,解密的resultMap為空,這里我們直接也返回false,提示前端重新登錄。實際項目中我們可能還需要提供token的刷新機制,這個我們后面再講。

之后要把這個攔截器配置到Springboot中,在interceptor包中新建一個WebConfigurer類,代碼如下

@Configuration
public class WebConfigurer implements WebMvcConfigurer {

    @Autowired
    private TokenInterceptor loginInterceptor;

    // 這個方法用來注冊攔截器,我們自己寫好的攔截器需要通過這里添加注冊才能生效
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // addPathPatterns("/**") 表示攔截所有的請求,
        // excludePathPatterns("/login", "/register") 表示除了登陸與注冊之外,因為登陸注冊不需要登陸也可以訪問
        registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login", "/register");
    }

}

WebMvcConfigurer是一個spring的配置類,用來代替原來的xml的配置方式。這里只使用了它的注冊攔截器的方法addInterceptors,其余方法的使用可以參考https://blog.csdn.net/zhangpower1993/article/details/89016503。這里我們攔截了除了login和register之外的其他所有路徑請求,因為這兩個請求肯定是不需要驗證token的。

最后我們寫一個簡單的登錄方法,之后來測試這個登錄攔截是否可用。我們直接在之前創(chuàng)建過的UserController中修改,添加一個login的方法。

@RestController
public class UserController {
    private String admin = "admin";
    private String psd = "qwertyuiop";

    @PostMapping(value = "/login")
    public ResponseData login(final String username, final String password)
    {
        ResponseData responseData = ResponseData.ok();
        if(username.equals(admin) && password.equals(psd)){
            Map<String, String> map = new HashMap<String, String>();
            map.put("username", username);
            String token = JWTUtil.genToken(map, new Date(System.currentTimeMillis() + 60L* 1000L * 30L));
            //封裝成對象返回給客戶端
            responseData.putDataValue("username", username);
            responseData.putDataValue("token", token);
        }
        else{
            responseData = ResponseData.customerError();
        }
        return responseData;
    }
}

為了簡單測試登錄功能,我們使用了固定的管理員賬號,用戶名和密碼都是固定的。這里設置了token過期時間是30分鐘。

然后我們就可以運行項目了,使用postman測試,先訪問addSubject接口,發(fā)現(xiàn)返回了401,證明攔截成功。


未登錄攔截

之后訪問登錄接口,獲得token


登錄成功

然后把username和token添加到header中,再次訪問addSubject接口,可以看到請求成功,數(shù)據(jù)庫中添加了新數(shù)據(jù)
添加了token的請求

最后如果需要測試token過期的情況,可以將過期時間調(diào)短,再進行測試,會發(fā)現(xiàn)token過期后再次返回401,提示用戶需要重新登錄。

本篇教程中的代碼依舊可以參考我在github上面的代碼https://github.com/ahuadoreen/studentmanager

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容