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小时内删除。
发表评论