zoukankan      html  css  js  c++  java
  • webapp用户身份认证方案 JSON WEB TOKEN 实现

    webapp用户身份认证方案 JSON WEB TOKEN 实现Deme示例,Java版

    本项目依赖于下面jar包:

    • 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时的结果,用户可根据结果进行不同处理:

    • EXPIRED token过期
    • INVALID token无效(包括token不合法,token格式不对,校验时异常)
    • VALID token有效

    使用示例

    获取token

    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);
    

    校验token

    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());
    

     

    项目应用中代码:

    JAT 工具类

    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;
        }    
        
    }

    TokenState

    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+"
    马上将该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+"
    马上将该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+"
    马上将该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=" 300px;" placeholder="token值"></textarea>
    		<br />
    		<br />
    		<button id="validtoken">点击解析上面的token</button><br/>
    		
    		<textarea id="result" readonly rows="5" cols="25" style=" 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

  • 相关阅读:
    Vue + better-scroll 入门教程
    Vue + Vant 实现顶部关键字搜索栏
    JS实现函数节流方法
    AngularJS服务及注入--Provider
    入门Webpack,看这篇就够了
    Vue.js中ref ($refs)用法举例总结
    从0开始做一个的Vue图片/ 文件选择(上传)组件[基础向]
    gulp自动化部署:gulp发布express项目(二)
    webstorm添加调试nodejs
    web window pixel等笔记
  • 原文地址:https://www.cnblogs.com/zuolun2017/p/7872309.html
Copyright © 2011-2022 走看看