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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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 [email protected]
 *
 */
 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 是否有效

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

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 [email protected]
 *
 */
@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);
    }
    
}

复制代码

验证登陆过滤器

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

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 [email protected]

 *

 */

@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页面测试代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

<%@ 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

猜你喜欢

转载自blog.csdn.net/liao1990/article/details/81177065