前後分離項目,JWT使用filter完成的跨域問題

  1. 踩坑紀錄

踩坑紀錄

問題一:

當前後分離項目要進行權限管理時,通常使用JWT來並使用Filter進行權限驗證,當完成代碼後,發現前端傳送的自定義的requestHeaders:Authorization無法由後端的request.getHeader(“Authorization”)獲取,除錯發現是因為跨域問題造成

問題二:

當後端使用過濾器進行token權限驗證,發現當登入後,進入首頁時發送axios請求的請求頭token為null,報401錯誤。

解決方案:

解決問題一:

解決問題的關鍵在於,瀏覽器會在發送 Ajax 請求之前發送一個預請求,確認當前的接口是不是有效的接口,此時的請求方式是 OPTIONS 的請求方式。

因此,JWT 的過濾器需要先判斷該請求是否為預請求,如果是則需要給返回的響應頭中添加跨域相關的信息;如果不是,則按照一般接口進行 JWT 驗證。

當初發現是跨域問題時,簡單想說在Filter類上添加@CrossOrign就好,發現仍然無法獲取請求頭。後來查出在 Filter 上使用 @CrossOrigin 注解是無效的,因為 @CrossOrigin 是 Spring MVC 框架提供的注解,只能用於 Spring MVC 框架處理的請求上。而 Filter 是 Servlet 規範中的一個組件,不依賴於 Spring MVC 框架,因此無法使用 @CrossOrigin 注解。

解決問題的代碼如下:

@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    // 將 /login 加入白名單,即不需要進行 token 權限驗證
    private static final String[] WHITE_LIST = {
            "/login"
    };
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        // 處理瀏覽器的預請求
        if (request.getMethod().equals("OPTIONS")) {
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Methods", "POST,GET,PUT,OPTIONS,DELETE");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept,Authorization,token");
            return;
        }

        String authToken = request.getHeader("Authorization");
        String uri = request.getRequestURI();
        boolean isWhiteList = Arrays.stream(WHITE_LIST)
                .anyMatch(uri::endsWith);

        // 如果訪問的是白名單中的 uri,則不進行 token 驗證
        if (!isWhiteList) {
            if (authToken == null || authToken.isEmpty() || !authToken.startsWith("Bearer")) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Jwt token is required");
                return;
            }
            String token = authToken.substring("Bearer".length()).trim();
            // 驗證 token 是否有效,將 token 中包含的用戶信息存入 HttpServletRequest 中
            try {
                Claims claims = JwtTokenUtils.validateToken(token);
                request.setAttribute("claims", claims);
            } catch (AuthException e) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
                return;
            }
        }
        chain.doFilter(request, response);
    }
}

解決問題二:
研究後發現,若將 ***’Authorization’: `Bearer ${getToken()}`***,直接放在請求頭Headers中,首次發送會為空,需頁面刷新才正常,後來將其放到請求攔截器中可正常執行,調整後代碼如下:

function getToken() {
    const token = localStorage.getItem('token');
    console.log('token',token)
    //驗證token是否存在
    if (!token) {
        return null
    }
    //驗證token是否超時
    const decodedToken = jwt_decode(token);
    if (Date.now() >= decodedToken.exp * 1000) {
        //若超時,清空localStorage保存的個人資料
        localStorage.removeItem('token')
        localStorage.removeItem('role')
        localStorage.removeItem('username')
        localStorage.removeItem('menuData')
        localStorage.removeItem('lastLoginTime')
        localStorage.removeItem('employee')
    }
    console.log('token',token)
    return token
}

const http = axios.create({
    baseURL:'http://localhost:8181/api/',
    // timeout: 10000,
    headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json'
    }
})


// 添加請求攔截器
http.interceptors.request.use(config => {
        if (getToken()) {
            config.headers['Authorization'] = `Bearer ${getToken()}`;
        }
        // 在發送請求之前做點什麼
        return config;
    },
    function (error) {
        // 對請求做些什麼
        return Promise.reject(error);
});

轉載請注明來源,歡迎對文章中的引用來源進行考證,歡迎指出任何有錯誤或不夠清晰的表達。可以郵件至 b8954008@gmail.com