webapp用戶身份認證方案 JSON WEB TOKEN 實現

webapp用戶身份認證方案 JSON WEB TOKEN 實現Deme示例,Java版javascript

本項目依賴於下面jar包:html

  • nimbus-jose-jwt-4.13.1.jar (一款開源的成熟的JSON WEB TOKEN 解決方法,本倉庫的代碼是對其的進一步封裝)
  • json-smart-2.0-RC2.jar和asm-1.0-RC1.jar (依賴jar包,主要用於JSONObject序列化)
  • cors-filter-2.2.1.jar和java-property-utils-1.9.1.jar(用於處理跨域ajax請求)
  • junit.jar(單元測試相關jar包)

核心類Jwt.java結構:前端

2個靜態方法createToken和validToken,分別用於生成TOKEN和校驗TOKEN; 定義了枚舉TokenState,用於表示驗證token時的結果,用戶可根據結果進行不一樣處理:java

  • EXPIRED token過時
  • INVALID token無效(包括token不合法,token格式不對,校驗時異常)
  • VALID token有效

使用示例jquery

獲取tokengit

Map<String , Object> payload=new HashMap<String, Object>();
Date date=new Date();
payload.put("uid", "291969452");//用戶id
payload.put("iat", date.getTime());//生成時間
payload.put("ext",date.getTime()+1000*60*60);//過時時間1小時
String token=Jwt.createToken(payload);
System.out.println("token:"+token);

 

校驗tokengithub

String token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiIyOTE5Njk0NTIiLCJpYXQiOjE0NjA0MzE4ODk2OTgsImV4dCI6MTQ2MDQzNTQ4OTY5OH0.RAa71BnklRMPyPhYBbxsfJdtXBnXeWevxcXLlwC2PrY";
Map<String, Object> result=Jwt.validToken(token);

String state=(String)result.get("state");
switch (TokenState.getTokenState(state)) {
case VALID:
    //To do somethings
    System.out.println("有效token");
    break;
case EXPIRED:
    System.out.println("過時token");
    break;
case INVALID:
    System.out.println("無效的token");
    break;
}

System.out.println("返回結果數據是:" +result.toString());

 

項目應用中代碼:web

JAT 工具類ajax

public class Jwt {
    
    
    /**
     * 祕鑰
     */
    private static final byte[] SECRET="3d990d2276917dfac04467df11fff26d".getBytes();
    
    /**
     * 初始化head部分的數據爲
     * {
     *         "alg":"HS256",
     *         "type":"JWT"
     * }
     */
    private static final JWSHeader header=new JWSHeader(JWSAlgorithm.HS256, JOSEObjectType.JWT, null, null, null, null, null, null, null, null, null, null, null);
    
    /**
     * 生成token,該方法只在用戶登陸成功後調用
     * 
     * @param Map集合,能夠存儲用戶id,token生成時間,token過時時間等自定義字段
     * @return token字符串,若失敗則返回null
     */
    public static String createToken(Map<String, Object> payload) {
        String tokenString=null;
        // 建立一個 JWS object
        JWSObject jwsObject = new JWSObject(header, new Payload(new JSONObject(payload)));
        try {
            // 將jwsObject 進行HMAC簽名
            jwsObject.sign(new MACSigner(SECRET));
            tokenString=jwsObject.serialize();
        } catch (JOSEException e) {
            System.err.println("簽名失敗:" + e.getMessage());
            e.printStackTrace();
        }
        return tokenString;
    }
    
    
    
    /**
     * 校驗token是否合法,返回Map集合,集合中主要包含    state狀態碼   data鑑權成功後從token中提取的數據
     * 該方法在過濾器中調用,每次請求API時都校驗
     * @param token
     * @return  Map<String, Object>
     */
    public static Map<String, Object> validToken(String token) {
        Map<String, Object> resultMap = new HashMap<String, Object>();
        try {
            JWSObject jwsObject = JWSObject.parse(token);
            Payload payload = jwsObject.getPayload();
            JWSVerifier verifier = new MACVerifier(SECRET);

            if (jwsObject.verify(verifier)) {
                JSONObject jsonOBj = payload.toJSONObject();
                // token校驗成功(此時沒有校驗是否過時)
                resultMap.put("state", TokenState.VALID.toString());
                // 若payload包含ext字段,則校驗是否過時
                if (jsonOBj.containsKey("ext")) {
                    long extTime = Long.valueOf(jsonOBj.get("ext").toString());
                    long curTime = new Date().getTime();
                    // 過時了
                    if (curTime > extTime) {
                        resultMap.clear();
                        resultMap.put("state", TokenState.EXPIRED.toString());
                    }
                }
                resultMap.put("data", jsonOBj);

            } else {
                // 校驗失敗
                resultMap.put("state", TokenState.INVALID.toString());
            }

        } catch (Exception e) {
            //e.printStackTrace();
            // token格式不合法致使的異常
            resultMap.clear();
            resultMap.put("state", TokenState.INVALID.toString());
        }
        return resultMap;
    }    
    
}

 

TokenStatespring

package com.jwt;

/**
 * 枚舉,定義token的三種狀態
 * @author running@vip.163.com
 *
 */
 public enum TokenState {  
     /**
      * 過時
      */
    EXPIRED("EXPIRED"),
    /**
     * 無效(token不合法)
     */
    INVALID("INVALID"), 
    /**
     * 有效的
     */
    VALID("VALID");  
    
    private String  state;  
      
    private TokenState(String state) {  
        this.state = state;  
    }
    
    /**
     * 根據狀態字符串獲取token狀態枚舉對象
     * @param tokenState
     * @return
     */
    public static TokenState getTokenState(String tokenState){
        TokenState[] states=TokenState.values();
        TokenState ts=null;
        for (TokenState state : states) {
            if(state.toString().equals(tokenState)){
                ts=state;
                break;
            }
        }
        return ts;
    }
    public String toString() {
        return this.state;
    }
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    
}  

 

 

junit 測試結果

package com.jwt;


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

import org.junit.Test;
/**
 * 單元測試(請自行引入junit4  Jar包)
 */
public class JwtTestCase {
    @Test
    @SuppressWarnings("unchecked")
    public void test1() {
        // 正常生成token----------------------------------------------------------------------------------------------------
        String token = null;
        Map<String, Object> payload = new HashMap<String, Object>();
        Date date = new Date();
        payload.put("uid", "291969452");// 用戶id
        payload.put("iat", date.getTime());// 生成時間:當前
        payload.put("ext", date.getTime() + 2000 * 60 * 60);// 過時時間2小時
        token = Jwt.createToken(payload);
        System.out.println("新生成的token是:" + token+"\n立刻將該token進行校驗");
        Map<String, Object> resultMap = Jwt.validToken(token);
        System.out.println("校驗結果是:" + getResult((String)resultMap.get("state")) );
        HashMap<String,String> dataobj =  (HashMap<String,String>) resultMap.get("data");
        System.out.println("從token中取出的payload數據是:" +dataobj.toString());

    }

    public void test2() {
        // 校驗過時----------------------------------------------------------------------------------------------------
        String token = null;
        Map<String, Object> payload = new HashMap<String, Object>();
        Date date = new Date();
        payload.put("uid", "291969452");// 用戶id
        payload.put("iat", date.getTime());// 生成時間
        payload.put("ext", date.getTime());// 過時時間就是當前
        token = Jwt.createToken(payload);
        System.out.println("新生成的token是:" + token+"\n立刻將該token進行校驗");
        Map<String, Object> resultMap = Jwt.validToken(token);
        System.out.println("校驗結果是:" + getResult((String)resultMap.get("state")) );

    }
    
    @SuppressWarnings("unchecked")
    public void test2_1() {
        // 不校驗過時(當payload中無過時ext字段時)----------------------------------------------------------------------------------------------------
        String token = null;
        Map<String, Object> payload = new HashMap<String, Object>();
        Date date = new Date();
        payload.put("uid", "291969452");// 用戶id
        payload.put("iat", date.getTime());// 生成時間
        token = Jwt.createToken(payload);
        System.out.println("新生成的token是:" + token+"\n立刻將該token進行校驗");
        Map<String, Object> resultMap = Jwt.validToken(token);
        System.out.println("校驗結果是:" + getResult((String)resultMap.get("state")) );
        HashMap<String,String> dataobj =  (HashMap<String,String>) resultMap.get("data");
        System.out.println("從token中取出的payload數據是:" +dataobj.toString());

    }
    
    public void test3() {
        // 校驗非法token的狀況----------------------------------------------------------------------------------------------------
        String token = null;
        Map<String, Object> payload = new HashMap<String, Object>();
        Date date = new Date();
        payload.put("uid", "291969452");// 用戶id
        payload.put("iat", date.getTime());// 生成時間
        payload.put("ext", date.getTime());// 過時時間就是當前
        
        token = Jwt.createToken(payload);
        System.out.println("新生成的token是:" + token);
        System.out.println("將新生成的token加點調料再來進行校驗");
        token = token + "YouAreSB";
        Map<String, Object> resultMap = Jwt.validToken(token);
        System.out.println("校驗結果是:" + getResult((String)resultMap.get("state")) );
        System.out.println("緣由是(非法token,payload參數可能通過中間人篡改,或者別人僞造的token)" );

    }
    
    public void test4() {
        // 校驗異常的狀況----------------------------------------------------------------------------------------------------
        String token = "123";
        System.out.println("我胡亂傳一個token:" + token);
        Map<String, Object> resultMap = Jwt.validToken(token);
        System.out.println("校驗結果是:" + getResult((String)resultMap.get("state")) );
        System.out.println("緣由是(token格式不合法致使的程序異常)");

    }
    
    
    public String getResult(String state) {
        switch (TokenState.getTokenState(state)) {
        case VALID:
            //To do somethings
            state = "有效token";
            break;
        case EXPIRED:
            state = "過時token";
            break;
        case INVALID:
            state = "無效的token";
            break;
        }
        return state;
    }

}

 

loginServlet 使用,具體使用springmvc仍是struts 能夠參考servlet寫法

package com.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.minidev.json.JSONObject;

import com.jwt.Jwt;
@WebServlet(urlPatterns="/servlet/login",loadOnStartup=1)
public class LoginServlet extends HttpServlet {

    private static final long serialVersionUID = 5285600116871825644L;
    
    /**
     * 校驗用戶名密碼
     */
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        String userName=request.getParameter("userName");
        String password =request.getParameter("password");
        JSONObject resultJSON=new JSONObject();
        
        //用戶名密碼校驗成功後,生成token返回客戶端
        if("admin".equals(userName)&&"123".equals(password)){
            //生成token
            Map<String , Object> payload=new HashMap<String, Object>();
            Date date=new Date();
            payload.put("uid", "admin");//用戶ID
            payload.put("iat", date.getTime());//生成時間
            payload.put("ext",date.getTime()+1000*60*60);//過時時間1小時
            String token=Jwt.createToken(payload);
            

            resultJSON.put("success", true);
            resultJSON.put("msg", "登錄成功");
            resultJSON.put("token", token);
            
        }else{
            resultJSON.put("success", false);
            resultJSON.put("msg", "用戶名密碼不對");
        }
        //輸出結果
        output(resultJSON.toJSONString(), response);
    
        
        
    }
    
    
    
    public void output(String jsonStr,HttpServletResponse response) throws IOException{
        response.setContentType("text/html;charset=UTF-8;");
        PrintWriter out = response.getWriter();
        out.println(jsonStr);
        out.flush();
        out.close();
        
    }

}

 

 每次請求都須要驗證token 是否有效

package com.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.minidev.json.JSONObject;

import com.jwt.Jwt;
@WebServlet(urlPatterns="/author/token",loadOnStartup=1,description="生成token的方法")
public class AuthorServlet extends HttpServlet {

	private static final long serialVersionUID = -8463692428988705309L;
	
	
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
			String token=request.getHeader("token");
			System.out.println(token);
			Map<String, Object> result=Jwt.validToken(token);
			//轉JSON並輸出
			PrintWriter out = response.getWriter();
			out.println(new JSONObject(result).toJSONString());
			out.flush();
			out.close();
	}
	
	public void doPut(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		Map<String , Object> payload=new HashMap<String, Object>();
		Date date=new Date();
		payload.put("uid", "291969452");//用戶id
		payload.put("iat", date.getTime());//生成時間
		payload.put("ext",date.getTime()+1000*60*60);//過時時間1小時
		String token=null;
		token=Jwt.createToken(payload);
		
		response.setContentType("text/html;charset=UTF-8;");
		Cookie cookie=new Cookie("token", token);
		cookie.setMaxAge(3600); 
		response.addCookie(cookie);
		PrintWriter out = response.getWriter();
		out.println(token);
		out.flush();
		out.close();
	}

}

 

調用獲取信息的接口

mainServlet

package com.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.minidev.json.JSONObject;
@WebServlet(urlPatterns="/servlet/getInfo",loadOnStartup=1)
public class mainServlet extends HttpServlet {

	private static final long serialVersionUID = -1643121334640537359L;

	@SuppressWarnings("unchecked")
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		System.out.println("正在調用獲取信息的接口");
		//將過濾器中存入的payload數據取出來
		HashMap<String, String> data=(HashMap<String, String>) request.getAttribute("data");
		//payload中的數據能夠用來作查詢,好比咱們在登錄成功時將用戶ID存到了payload中,咱們能夠將它取出來,去數據庫查詢這個用戶的全部信息;
		//而不是用request.getParameter("uid")方法來獲取前端傳給咱們的uid,由於前端的參數時可篡改的不徹底可信的,而咱們從payload中取出來的數據是從token中
		//解密取出來的,在祕鑰沒有被破解的狀況下,它是絕對可信的;這樣能夠避免別人用這個接口查詢非本身用戶ID的相關信息
		JSONObject resp=new JSONObject();
		resp.put("success", true);
		resp.put("msg", "成功");
		resp.put("data", data);
		output(resp.toJSONString(), response);
	}
	
	public void output(String jsonStr,HttpServletResponse response) throws IOException{
		response.setContentType("text/html;charset=UTF-8;");
		PrintWriter out = response.getWriter();
		out.println(jsonStr);
		out.flush();
		out.close();
		
	}
	
}

  

 

跨域過濾器

package com.filter;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import com.thetransactioncompany.cors.CORSConfiguration;
import com.thetransactioncompany.cors.CORSFilter;
/**
 * 服務端跨域處理過濾器,該過濾器須要依賴cors-filter-2.2.1.jar和java-property-utils-1.9.1.jar
 * @author running@vip.163.com
 *
 */
@WebFilter(urlPatterns={"/*"},asyncSupported=true,
initParams={
    @WebInitParam(name="cors.allowOrigin",value="*"),
    @WebInitParam(name="cors.supportedMethods",value="CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, TRACE"),
    @WebInitParam(name="cors.supportedHeaders",value="token,Accept, Origin, X-Requested-With, Content-Type, Last-Modified"),//注意,若是token字段放在請求頭傳到後端,這裏須要配置
    @WebInitParam(name="cors.exposedHeaders",value="Set-Cookie"),
    @WebInitParam(name="cors.supportsCredentials",value="true")
})
public class Filter0_CrossOriginResource extends CORSFilter implements javax.servlet.Filter{


    public void init(FilterConfig config) throws ServletException {
        System.out.println("跨域資源處理過濾器初始化了");
        super.init(config);
    }
    
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("跨域過濾器");
        super.doFilter(request, response, chain);
    }


    public void setConfiguration(CORSConfiguration config) {
        super.setConfiguration(config);
    }
    
}

 

驗證登錄過濾器

package com.filter;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.minidev.json.JSONObject;

import com.jwt.Jwt;
import com.jwt.TokenState;
/**
 * toekn校驗過濾器,全部的API接口請求都要通過該過濾器(除了登錄接口)
 * @author running@vip.163.com
 *
 */
@WebFilter(urlPatterns="/servlet/*")
public class Filter1_CheckToken  implements Filter {


	@Override
	public void doFilter(ServletRequest argo, ServletResponse arg1,
			FilterChain chain ) throws IOException, ServletException {
		HttpServletRequest request=(HttpServletRequest) argo;
		HttpServletResponse response=(HttpServletResponse) arg1;
//		response.setHeader("Access-Control-Allow-Origin", "*");
		if(request.getRequestURI().endsWith("/servlet/login")){
			//登錄接口不校驗token,直接放行
			chain.doFilter(request, response);
			return;
		}
		//其餘API接口一概校驗token
		System.out.println("開始校驗token");
		//從請求頭中獲取token
		String token=request.getHeader("token");
		Map<String, Object> resultMap=Jwt.validToken(token);
		TokenState state=TokenState.getTokenState((String)resultMap.get("state"));
		switch (state) {
		case VALID:
			//取出payload中數據,放入到request做用域中
			request.setAttribute("data", resultMap.get("data"));
			//放行
			chain.doFilter(request, response);
			break;
		case EXPIRED:
		case INVALID:
			System.out.println("無效token");
			//token過時或者無效,則輸出錯誤信息返回給ajax
			JSONObject outputMSg=new JSONObject();
			outputMSg.put("success", false);
			outputMSg.put("msg", "您的token不合法或者過時了,請從新登錄");
			output(outputMSg.toJSONString(), response);
			break;
		}
		
		
	}
	
	
	public void output(String jsonStr,HttpServletResponse response) throws IOException{
		response.setContentType("text/html;charset=UTF-8;");
		PrintWriter out = response.getWriter();
//		out.println();
		out.write(jsonStr);
		out.flush();
		out.close();
		
	}
	
	@Override
	public void init(FilterConfig arg0) throws ServletException {
		System.out.println("token過濾器初始化了");
	}

	@Override
	public void destroy() {
		
	}

}

  

jsp頁面測試代碼:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		<title></title>
	</head>
	<body>
		<button id="gettoken">點擊ajax獲取token</button>
		<textarea id="token" rows="5" cols="25" style="width: 300px;" placeholder="token值"></textarea>
		<br />
		<br />
		<button id="validtoken">點擊解析上面的token</button><br/>
		
		<textarea id="result" readonly rows="5" cols="25" style="width: 300px;" placeholder="數據解析結果"></textarea>
		
		
		<script src="jquery-2.1.0.js" type="text/javascript" charset="utf-8"></script>
		<script>
			$(function () {
				$("#gettoken").on("click",function () {
					$.ajax({
						type:"put",
						url:"http://localhost:8080/JWT/author/token",
						async:true,
						success:function(data){
							$("#token").val(data);
						}
					});
				});
				
				
				$("#validtoken").on('click',function (e) {
					var token=$.trim($("#token").val());
					if(!token.length){
						alert("請先獲取token");
						return;
					}
					$.ajax({
						type:"get",
						dataType:"json",
						url:"http://localhost:8080/JWT/author/token?r="+Math.random(),
						async:true,
						beforeSend: function(request) {
	                        request.setRequestHeader("token", token);
	                    },
						success:function (data) {
							$("#result").val(JSON.stringify(data));
						}
					});
				});
				
			})
		</script>
	</body>
</html>

  

 

具體代碼地址:https://github.com/bigmeow/JWT

相關文章
相關標籤/搜索