Token作⽤及原理
讲到Token的作⽤和原理,⽹上有很多相关的技术⽂章,通过搜集整理并加⼊⾃⼰的理解体会,做⼀个总结整理,希望可以帮助到更多有需要的⼈。
1、token作⽤及原理
Token,即令牌,是服务器产⽣的,具有随机性和不可预测性,它主要有两个作⽤:
(1)防⽌表单重复提交;
使⽤Token防表单重复提交步骤:
①在服务器端⽣成⼀个唯⼀的随机标识号,专业术语称为Token(令牌),同时在当前⽤户的Session域中保存这个Token;
②将Token发送到客户端的Form表单中,在Form表单中使⽤隐藏域来存储这个Token,表单提交的时候连同这个Token⼀起提交到服务器端;
③在服务器端判断客户端提交上来的Token与服务器端⽣成的Token是否⼀致,如果不⼀致,那就是重复
提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前⽤户的Session域中存储的标识号。
FromServlet.java
1import java.io.IOException;
2import javax.servlet.ServletException;
3import javax.servlet.http.HttpServlet;
4import javax.servlet.http.HttpServletRequest;
5import javax.servlet.http.HttpServletResponse;
6
7public class FormServlet extends HttpServlet {
8public void doGet(HttpServletRequest request, HttpServletResponse response)
9throws ServletException, IOException {
10            String token = Instance().makeToken();//创建令牌
11            System.out.println("在FormServlet中⽣成的token:"+token);
12            Session().setAttribute("token", token);  //在服务器使⽤session保存token(令牌)
13            RequestDispatcher("/Form.jsp").forward(request, response);//跳转到form.jsp页⾯
14        }
15
16public void doPost(HttpServletRequest request, HttpServletResponse response)
17throws ServletException, IOException {
18this.doGet(request, response);
19        }
20    }
TokenUtil.java
1import java.security.MessageDigest;
2import java.security.NoSuchAlgorithmException;
3import java.util.Random;
4import sun.misc.BASE64Encoder;
5
6public class TokenUtil {
7
8/*
9        *单例设计模式(保证类的对象在内存中只有⼀个)
10        *1、把类的构造函数私有
11        *2、⾃⼰创建⼀个类的对象
12        *3、对外提供⼀个公共的⽅法,返回类的对象
13*/
14private TokenUtil(){
15        }
16
17private static final TokenUtil instance = new TokenUtil();
18
19/**
20        * 返回类的对象
21        * @return
22*/
23public static TokenUtil getInstance(){
24return instance;
25        }
26
27/**
28        * ⽣成Token
29        * Token:Nv6RRuGEVvmGjB+jimI/gw==
30        * @return
31*/
32public String makeToken(){
33
34            String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";
35//数据指纹  128位长  16个字节  md5
36try {
37                MessageDigest md = Instance("md5");
38//对于给定数量的更新数据,digest ⽅法只能被调⽤⼀次。digest ⽅法被调⽤后,MessageDigest对象被重新设置成其初始状态。39byte md5[] =  md.Bytes());
40//base64编码--任意⼆进制编码明⽂字符  adfsdfsdfsf
41                BASE64Encoder encoder = new BASE64Encoder();
de(md5);
43            } catch (NoSuchAlgorithmException e) {
44throw new RuntimeException(e);
45            }
46        }
47    }
Form.jsp
1  <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
2    <%
3    String path = ContextPath();
4    String basePath = Scheme()+"://"+ServerName()+":"+ServerPort()+path+"/";
5    %>
6    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
7    <html>
8    <head>
9    <base href="<%=basePath%>">
10    <title>Form.jsp</title>
11    <meta http-equiv="pragma" content="no-cache">
12    <meta http-equiv="cache-control" content="no-cache">
13    <meta http-equiv="expires" content="0">
14    <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
15    <meta http-equiv="description" content="This is my page">
16    <!--
17        <link rel="stylesheet" type="text/css" href="styles.css">
18        -->
19    </head>
20    <body>
21        <form action="DoFormServlet" method="post">
22            <%--使⽤隐藏域存储⽣成的token--%>
23            <%--
24                <input type="hidden" name="token" value="<%=Attribute("token") %>">
25            --%>
26            <%--使⽤EL表达式取出存储在session中的token--%>
27            <input type="hidden" name="token" value="${token}" /> ⽤户名:<input
28                type="text" name="username"> <input type="submit" value="提交">
29        </form>
30    </body>
31    </html>
DoFormServlet.java
1import java.io.IOException;
2import javax.servlet.ServletException;
3import javax.servlet.http.HttpServlet;
4import javax.servlet.http.HttpServletRequest;
5import javax.servlet.http.HttpServletResponse;
6
7public class DoFormServlet extends HttpServlet {
8public void doGet(HttpServletRequest request, HttpServletResponse response)
9throws ServletException, IOException {
10boolean isRepeat = isRepeatSubmit(request);//判断⽤户是否是重复提交
11if(isRepeat){
12                request.setAttribute("MSG", "请不要重复提交");
13                System.out.println("请不要重复提交");
14                RequestDispatcher("/Msg.jsp").forward(request, response);
15return;
16            }
17            Session().removeAttribute("token");//移除session中的token
18            request.setAttribute("MSG","处理⽤户提交请求!!");
19            RequestDispatcher("/Msg.jsp").forward(request, response);
20            System.out.println("处理⽤户提交请求!!");
21        }
22
23/**
24        * 判断客户端提交上来的令牌和服务器端⽣成的令牌是否⼀致
25        * @param request
26        * @return
27        * true ⽤户重复提交了表单
28        * false ⽤户没有重复提交表单
29*/
30private boolean isRepeatSubmit(HttpServletRequest request) {
31            String client_token = Parameter("token");
32//1、如果⽤户提交的表单数据中没有token,则⽤户是重复提交了表单
33if(client_token==null){
34return true;
35            }
36//取出存储在Session中的token
37            String server_token = (String) Session().getAttribute("token");
38//2、如果当前⽤户的Session中不存在Token(令牌),则⽤户是重复提交了表单
39if(server_token==null){
40return true;
41            }
42//3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则⽤户是重复提交了表单
43if(!client_token.equals(server_token)){
44return true;
45            }
46return false;
47        }
48
49public void doPost(HttpServletRequest request, HttpServletResponse response)
50throws ServletException, IOException {
51this.doGet(request, response);
52        }
53    }
Msg.jsp
1 <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
2    <%
3    String path = ContextPath();
4    String basePath = Scheme()+"://"+ServerName()+":"+ServerPort()+path+"/";
5    %>
6
7    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
8    <html>
9      <head>
10        <base href="<%=basePath%>">
11        <title>result show</title>
12        <meta http-equiv="pragma" content="no-cache">
13        <meta http-equiv="cache-control" content="no-cache">
14        <meta http-equiv="expires" content="0">
15        <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
16        <meta http-equiv="description" content="This is my page">
17        <!--
18        <link rel="stylesheet" type="text/css" href="styles.css">
19        -->
20      </head>
21      <body>${MSG}</body>
22    </html>
(2)⽤来作⾝份验证
使⽤基于 Token 的⾝份验证流程如下:
①⽤户⾸次登录,将输⼊的账号和密码提交给服务器;
②服务器对输⼊内容进⾏校验,若账号和密码匹配则验证通过,登录成功,并⽣成⼀个token值,将其保存到数据库,并返回给客户端;
③客户端拿到返回的token值将其保存在本地(如cookie/localStorage),作为公共参数,以后每次请求服务器时都携带该token(放在响应头⾥),提交给服务器进⾏校验;
④服务器接收到请求后,⾸先验证是否携带token,若携带则取出请求头⾥的token值与数据库存储的token进⾏匹配校验,若token值相同则登录成功,且当前正处于登录状态,此时正常返回数据,让app显⽰数据;若不存在或两个值不⼀致,则说明原来的登录已经失效,此时返回错误状态码,提⽰⽤户跳转⾄登录界⾯重新登录;
⑤注意:⽤户每进⾏⼀次登录,登录成功后服务器都会更新⼀个token新值返回给客户端;
实现原理图:
引⼊java-jwt依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.3.0</version>
</dependency>
签名⼯具JwtUtil.java
1import com.auth0.jwt.JWT;
2import com.auth0.jwt.JWTVerifier;
3import com.auth0.jwt.algorithms.Algorithm;
4import com.ptions.JWTDecodeException;
5import com.auth0.jwt.interfaces.DecodedJWT;
6import com.fasterxml.jackson.annotation.ObjectIdGenerators;
7
8import java.io.UnsupportedEncodingException;
9import java.util.Date;
10import java.util.HashMap;
11import java.util.Map;
12
13public class JwtUtil {
14/**
15        * 过期时间⼀天,
16        * TODO 正式运⾏时修改为15分钟
17*/
18private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000;
19/**
20        * token私钥
21*/
22private static final String TOKEN_SECRET = "f26e587c28064d0e855e72c0a6a0e618"; 23
24/**
25        * 校验token是否正确
26        *
27        * @param token 密钥
28        * @return是否正确
29*/
30public static boolean verify(String token) {
31try {
32                Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
33                JWTVerifier verifier = quire(algorithm)
34                        .build();
35                DecodedJWT jwt = verifier.verify(token);
36return true;
37            } catch (Exception exception) {
38return false;
39            }
40        }
41
42/**
43        * ⽣成签名,15min后过期
44        *
45        * @param username ⽤户名
46        * @return加密的token
47*/
48public static String sign(String username,String userId) {
49try {
50//            过期时间
51                Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
52//            私钥及加密算法
53                Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
54//            设置头部信息
55                Map<String, Object> header = new HashMap<>(2);
56                header.put("typ", "JWT");
57                header.put("alg", "HS256");
58// 附带username,userId信息,⽣成签名
ate()
60                        .withHeader(header)
61                        .withClaim("loginName", username)
62                        .withClaim("userId",userId)
63                        .withExpiresAt(date)
64                        .sign(algorithm);
65            } catch (UnsupportedEncodingException e) {
66return null;
67            }
68        }
69    }
⾝份认证LoginController.java
import ity.ApiResponse;
import ity.User;
import ums.ApiResponseEnum;
import com.joe.service.IUserService;
import com.joe.util.ApiResponseUtil;
import com.joe.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Map;
@Controller
@RequestMapping("/")
public class LoginController {
@Autowired
private IUserService userService;
/**
* 登陆接⼝
*
* @return token
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public ApiResponse login(@RequestBody Map<String, String> map) {
String loginName = ("loginName");
String password = ("password");
//⾝份验证是否成功
boolean isSuccess = userService.checkUser(loginName, password);
if (isSuccess) {
User user = UserByLoginName(loginName);
if (user != null) {
//返回token
String token = JwtUtil.Name(), Id());
if (token != null) {
ApiResponse(token);
}
}
}
//返回登陆失败消息
ApiResponse(ApiResponseEnum.LOGIN_FAIL);
}
}
配置TokenInterceptor.java
1import com.alibaba.fastjson.JSONObject;
2import ity.ApiResponse;
3import ums.ApiResponseEnum;
4import com.joe.util.ApiResponseUtil;
5import com.joe.util.JwtUtil;
6import org.springframework.web.servlet.HandlerInterceptor;
7import org.springframework.web.servlet.ModelAndView;
8import javax.servlet.http.HttpServletRequest;
el表达式获取map的值9import javax.servlet.http.HttpServletResponse;
10import java.io.PrintWriter;
11
12public class TokenInterceptor implements HandlerInterceptor {
13
14        @Override
15public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
16            response.setCharacterEncoding("utf-8");
17            String token = Header("access_token");
18//token不存在
19if (null != token) {
20//验证token是否正确
21boolean result = JwtUtil.verify(token);
22if (result) {
23return true;
24                }
25            }
26            ApiResponse apiResponse = ApiResponse(ApiResponseEnum.AUTH_ERROR);
27            responseMessage(Writer(),apiResponse);
28return false;
29        }
30
31        @Override

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。