1. 程式人生 > >SpringMVC 整合 JWT驗證方式

SpringMVC 整合 JWT驗證方式

JWT官網: https://jwt.io/

這裡以java的ssm框架為例,整合jwt。

1.pom.xml 匯入jwt的包

<!-- jwt -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>2.2.0</version>
</dependency>

2.編寫jwt的工具類,包含加密解密功能

import com.auth0.jwt.JWTSigner;
import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.internal.com.fasterxml.jackson.databind.ObjectMapper; import java.util.HashMap; import java.util.Map; public class JWT { private static final String SECRET = "XX#$%()(#*!()!KL<><MQLMNQNQJQK sdfkjsdrow32234545fdf>?N<:{LWPW";
private static final String EXP = "exp"; private static final String PAYLOAD = "payload"; //加密,傳入一個物件和有效期 public static <T> String sign(T object, long maxAge) { try { final JWTSigner signer = new JWTSigner(SECRET); final Map<String, Object> claims = new
HashMap<String, Object>(); ObjectMapper mapper = new ObjectMapper(); String jsonString = mapper.writeValueAsString(object); claims.put(PAYLOAD, jsonString); claims.put(EXP, System.currentTimeMillis() + maxAge); return signer.sign(claims); } catch(Exception e) { return null; } } //解密,傳入一個加密後的token字串和解密後的型別 public static<T> T unsign(String jwt, Class<T> classT) { final JWTVerifier verifier = new JWTVerifier(SECRET); try { final Map<String,Object> claims= verifier.verify(jwt); if (claims.containsKey(EXP) && claims.containsKey(PAYLOAD)) { long exp = (Long)claims.get(EXP); long currentTimeMillis = System.currentTimeMillis(); if (exp > currentTimeMillis) { String json = (String)claims.get(PAYLOAD); ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.readValue(json, classT); } } return null; } catch (Exception e) { return null; } } }

3.jwt有了,ssm要如何去利用,使用者驗證的第一步是登入,登入時根據使用者傳來的username和password到資料庫驗證身份,如果合法,便給該使用者jwt加密生成token

    //處理登入
    @RequestMapping(value="login", produces = "application/json; charset=utf-8")
    public @ResponseBody ResponseData login(HttpServletRequest request, @RequestParam( "email") String email,
            @RequestParam("password") String password) {
        Login login = new Login();
        login.setEmail(email);
        login.setPassword(password);
        ResponseData responseData = ResponseData.ok();
        //先到資料庫驗證
        Integer loginId = userService.checkLogin(login);
        if(null != loginId) {
            User user = userService.getUserByLoginId(loginId);
            login.setId(loginId);
            //給使用者jwt加密生成token
            String token = JWT.sign(login, 60L* 1000L* 30L);
            //封裝成物件返回給客戶端
            responseData.putDataValue("loginId", login.getId());
            responseData.putDataValue("token", token);
            responseData.putDataValue("user", user);
        }
        else{
            responseData =  ResponseData.customerError();
        }   
        return responseData;
    }

4.在使用者登入時,把loginId和token返回給前臺,以後使用者每次請求時,都得帶上這兩個引數,後臺拿到token後解密出loginId,與使用者傳遞過來的loginId比較,如果相同,則說明使用者身份合法。因為是每個登入過後的每個請求,這裡用springmvc的攔截器做

<mvc:interceptors>    
    <mvc:interceptor>    
        <!-- 匹配的是url路徑, 如果不配置或/**,將攔截所有的Controller -->  
        <mvc:mapping path="/**" />  
        <!-- /register 和 /login 不需要攔截-->  
        <mvc:exclude-mapping path="/register" />
        <mvc:exclude-mapping path="/login" />
        <bean class="com.xforce.charles.interceptor.TokenInterceptor"></bean>    
    </mvc:interceptor>  
    <!-- 當設定多個攔截器時,先按順序呼叫preHandle方法,然後逆序呼叫每個攔截器的postHandle和afterCompletion方法 -->  
</mvc:interceptors>

5.攔截器程式碼

import java.io.PrintWriter;

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

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSONObject;
import com.xforce.charles.model.Admin;
import com.xforce.charles.model.Login;
import com.xforce.charles.util.JWT;
import com.xforce.charles.util.ResponseData;

public class TokenInterceptor implements HandlerInterceptor{

    public void afterCompletion(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception arg3)
            throws Exception {
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler, ModelAndView model) throws Exception {
    }

    //攔截每個請求
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) throws Exception {
        response.setCharacterEncoding("utf-8");
        String token = request.getParameter("token");
        ResponseData responseData = ResponseData.ok();
        //token不存在
        if(null != token) {
            Login login = JWT.unsign(token, Login.class);
            String loginId = request.getParameter("loginId");
            //解密token後的loginId與使用者傳來的loginId不一致,一般都是token過期
            if(null != loginId && null != login) {
                if(Integer.parseInt(loginId) == login.getId()) {
                    return true;
                } else {
                    responseData = ResponseData.forbidden();
                    responseMessage(response, response.getWriter(), responseData);
                    return false;
                }
            } else {
                responseData = ResponseData.forbidden();
                responseMessage(response, response.getWriter(), responseData);
                return false;
            }
        } else {
            responseData = ResponseData.forbidden();
            responseMessage(response, response.getWriter(), responseData);
            return false;
        }
    }

    //請求不通過,返回錯誤資訊給客戶端
    private void responseMessage(HttpServletResponse response, PrintWriter out, ResponseData responseData) {
        responseData = ResponseData.forbidden();
        response.setContentType("application/json; charset=utf-8");  
        String json = JSONObject.toJSONString(responseData);
        out.print(json);
        out.flush();
        out.close();
    }

}

6.注意點:用@ResponseBody返回json資料時,有時會有亂碼,需要在springmvc的配置檔案裡面加以下配置(spring4以上)

<mvc:annotation-driven>
   <mvc:message-converters register-defaults="true">
    <bean class="org.springframework.http.converter.StringHttpMessageConverter">
      <property name="supportedMediaTypes" value = "text/plain;charset=UTF-8" />
    </bean>
   </mvc:message-converters>
</mvc:annotation-driven> 

7.最後分享一個類,用於返回給客戶端的萬能類,我覺得它可以滿足一般的介面

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

public class ResponseData {

    private final String message;
    private final int code;
    private final Map<String, Object> data = new HashMap<String, Object>();

    public String getMessage() {
        return message;
    }

    public int getCode() {
        return code;
    }

    public Map<String, Object> getData() {
        return data;
    }

    public ResponseData putDataValue(String key, Object value) {
        data.put(key, value);
        return this;
    }

    private ResponseData(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public static ResponseData ok() {
        return new ResponseData(200, "Ok");
    }

    public static ResponseData notFound() {
        return new ResponseData(404, "Not Found");
    }

    public static ResponseData badRequest() {
        return new ResponseData(400, "Bad Request");
    }

    public static ResponseData forbidden() {
        return new ResponseData(403, "Forbidden");
    }

    public static ResponseData unauthorized() {
        return new ResponseData(401, "unauthorized");
    }

    public static ResponseData serverInternalError() {
        return new ResponseData(500, "Server Internal Error");
    }

    public static ResponseData customerError() {
        return new ResponseData(1001, "customer Error");
    }
}