一篇博客帮你搞定Jwt

前言:

在当今的互联网世界中,随着应用程序和网络服务的不断增多,用户身份验证和授权变得至关重要。传统的身份验证方式,如基于会话的认证,已经逐渐显露出一些局限性和安全风险。

JSON Web Token(JWT)作为一种现代化的身份验证和授权解决方案,被广泛采用并逐渐成为业界标准。它以其简单、可靠和安全的特性,成为众多开发人员和技术团队的首选。

本博客将帮助你深入了解JWT的概念、工作原理以及如何在实际应用中使用JWT来保护你的应用程序。

一, Jwt的介绍:

1.1Jwt是什么?

JWT (JSON Web Token) 是一种用于身份验证和授权的开放标准,而传统的会话(session)是一种在服务器端存储用户状态的机制。下面是 JWT 和传统的会话机制之间的优劣势比较:

JWT 的优势

  1. 无状态性:JWT 是无状态的,即服务器不需要在后端存储会话信息。每个请求都包含了自身的认证和授权信息,这使得 JWT 在分布式系统中更易于扩展和维护。
  2. 跨域支持:由于 JWT 是通过在请求的头部或参数中传递信息来进行身份验证和授权的,因此它能够轻松地应对跨域请求,并且不受同源策略的限制。
  3. 可扩展性:权JWT 的载荷部分可以自定义添加额外的信息,因此可以在令牌中携带更多的用户相关数据,如用户角色、限等。

JWT 的劣势

  1. 安全性依赖于密钥管理:JWT 使用密钥对令牌进行签名,以确保其完整性和真实性。因此,密钥的安全管理变得非常重要,一旦密钥泄露,黑客就可能篡改或伪造有效的 JWT 令牌。
  2. 无法主动使令牌失效:一旦 JWT 令牌签发,它的有效期内都是有效的,并且服务器无法主动使其失效。如果需要吊销令牌,只能等待令牌过期或者更新密钥。
  3. 增加网络传输量:JWT 将用户身份信息直接存储在令牌中,因此每个请求都会携带这些信息,导致网络传输量增加。

传统的会话机制(例如session)的优势:

  1. 支持主动注销:传统会话机制可以通过在服务器端删除会话信息来主动注销用户。
  2. 较强的安全性:由于会话信息存储在服务器端,黑客很难篡改或伪造会话状态。

传统的会话机制的劣势:

  1. 需要在服务器端存储会话信息:每个会话都需要在服务器端维护相应的状态信息,这对于分布式系统来说可能不太适用。
  2. 不支持跨域请求:会话机制通常依赖于浏览器发送 Cookie 来进行身份验证,因此不适合处理跨域请求。

总的来说,JWT 在无状态、跨域支持和可扩展性方面具有优势,而传统的会话机制则在主动注销和安全性方面更为有利。选择使用哪种机制取决于具体的需求和应用场景。

1.2使用Jwt的好处

 有那么多的技术用于跨域身份验证我们为什么要用JWT呢?

        JWT的精髓在于:“去中心化”,数据是保存在客户端的。 

如果使用传统的session的话:

按之前的方法:

        1、用户向服务器发送用户名和密码

        2、服务器验证后,相关的数据会保存在会话中。

        3、服务器向用户返回session_id,session信息写入用户的cookie中。

        4、用户的每个后续的请求都将在cookie中取出session_id传给服务器。

        5、服务器接收后与之前存储的数据进行对比,确认用户身份。

对于这种方法优化的话,应该持久化session数据,写入数据库或者文件持久层等。收到请求后,验证服务从持久层请求数据。

但是这种方法工作量大,由于依赖持久的数据库,会有风险。
 

1.3Jwt的组成

一篇博客帮你搞定Jwt_第1张图片

JWT(JSON Web Token)由三个部分组成:头部(Header)、载荷(Payload)和签名(Signature)。

  1. 头部(Header):包含了关于该JWT的元数据信息,以 JSON 对象的形式表示。通常包含两个字段:

    • "alg"(算法):指定用于签名 JWT 的算法,例如 HMAC SHA256 或 RSA。
    • "typ"(类型):指定令牌的类型,一般都是 "JWT"。
  2. 载荷(Payload):存储实际的信息数据,也是以 JSON 对象的形式表示。载荷可以包含一些预定义的声明(Claims),或者自定义的声明。预定义的声明包括:

    • "iss"(Issuer):令牌的发行者。
    • "sub"(Subject):令牌所面向的用户。
    • "aud"(Audience):令牌的接收者。
    • "exp"(Expiration Time):令牌的过期时间。
    • "nbf"(Not Before):令牌的生效时间。
    • "iat"(Issued At):令牌的发行时间。
    • "jti"(JWT ID):令牌的唯一标识符。
  3. 签名(Signature):使用密钥通过选定的算法对头部和载荷进行签名生成的字符串。签名用于验证 JWT 的完整性和真实性。具体的签名算法根据头部的 "alg" 字段确定。

JWT 的基本结构如下:header.payload.signature,使用 Base64 编码进行序列化。最终的 JWT 是一个字符串,可以通过在两个点(.)处分隔三个部分来解析和验证。

1.4JWT问题和趋势

1、JWT默认不加密,但可以加密。生成原始令牌后,可以使用改令牌再次对其进行加密。

2、当JWT未加密方法是,一些私密数据无法通过JWT传输。

3、JWT不仅可用于认证,还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数。

4、JWT的最大缺点是服务器不保存会话状态,所以在使用期间不可能取消令牌或更改令牌的权限。也就是说,一旦JWT签发,在有效期内将会一直有效。

5、JWT本身包含认证信息,因此一旦信息泄露,任何人都可以获得令牌的所有权限。为了减少盗用,JWT的有效期不宜设置太长。对于某些重要操作,用户在使用时应该每次都进行进行身份验证。

6、为了减少盗用和窃取,JWT不建议使用HTTP协议来传输代码,而是使用加密的HTTPS协议进行传输。

1.5JWT的工作原理

  1. 用户通过提供有效的身份信息,例如用户名和密码,向服务器发送请求。

  2. 服务器验证用户身份信息,并生成一个JWT。

  3. 服务器将生成的JWT作为响应返回给客户端。

  4. 客户端收到JWT后,将其存储在本地,通常使用浏览器的本地存储或者Cookie。

  5. 客户端在后续的请求中,将JWT添加到请求的头部或其他适当位置进行传递。

  6. 服务器接收到请求后,使用密钥验证JWT的有效性和完整性,如果验证通过,则对请求进行处理。

  7. 客户端和服务器之间通过JWT实现无状态的身份验证和授权。

二,Jwt的工具类

对于JWT(JSON Web Token)的工具类,可以使用现有的第三方库来简化操作。以下是一个示例工具类的概述:

 
  
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

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

public class JwtUtils {
    private static final String SECRET_KEY = "your-secret-key";
    private static final long EXPIRATION_TIME = 86400000; // 24小时

    // 创建JWT
    public static String createJwt(String subject, Map claims) {
        Date now = new Date();
        Date expirationDate = new Date(now.getTime() + EXPIRATION_TIME);

        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(now)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }

    // 解析JWT
    public static Claims parseJwt(String token) {
        return Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
    }
}

使用该工具类,你可以创建和解析JWT。

创建JWT的示例代码:

Map claims = new HashMap<>();
claims.put("userId", 123456);
claims.put("role", "admin");

String jwt = JwtUtils.createJwt("[email protected]", claims);

解析JWT的示例代码:

Claims claims = JwtUtils.parseJwt(jwt);
String userId = claims.get("userId").toString();
String role = claims.get("role").toString();

请注意,在实际使用中,应该根据具体情况自定义更多的方法和逻辑,比如验证JWT的有效性、获取特定的声明等。此外,SECRET_KEY 应该保密存储,并且可以根据需要进行更改。

三,Jwt与SPA项目结合

JWTFilter:

package com.zking.vue.util;
 
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
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.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import io.jsonwebtoken.Claims;
 
/**
 * * JWT验证过滤器,配置顺序 :CorsFilte-->JwtFilter-->struts2中央控制器
 * 
 * @author Administrator
 *
 */
public class JwtFilter implements Filter {
 
	// 排除的URL,一般为登陆的URL(请改成自己登陆的URL)
	private static String EXCLUDE = "^/vue/userAction_login\\.action?.*$";
 
	private static Pattern PATTERN = Pattern.compile(EXCLUDE);
 
	private boolean OFF = true;// true关闭jwt令牌验证功能
 
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	}
 
	@Override
	public void destroy() {
	}
 
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;
		String path = req.getServletPath();
		if (OFF || isExcludeUrl(path)) {// 登陆直接放行
			chain.doFilter(request, response);
			return;
		}
 
		// 从客户端请求头中获得令牌并验证
		String jwt = req.getHeader(JwtUtils.JWT_HEADER_KEY);
		Claims claims = this.validateJwtToken(jwt);
		if (null == claims) {
			// resp.setCharacterEncoding("UTF-8");
			resp.sendError(403, "JWT令牌已过期或已失效");
			return;
		} else {
			String newJwt = JwtUtils.copyJwt(jwt, JwtUtils.JWT_WEB_TTL);
			resp.setHeader(JwtUtils.JWT_HEADER_KEY, newJwt);
			chain.doFilter(request, response);
		}
	}
 
	/**
	 * 验证jwt令牌,验证通过返回声明(包括公有和私有),返回null则表示验证失败
	 */
	private Claims validateJwtToken(String jwt) {
		Claims claims = null;
		try {
			if (null != jwt) {
				claims = JwtUtils.parseJwt(jwt);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return claims;
	}
 
	/**
	 * 是否为排除的URL
	 * 
	 * @param path
	 * @return
	 */
	private boolean isExcludeUrl(String path) {
		Matcher matcher = PATTERN.matcher(path);
		return matcher.matches();
	}
 
	// public static void main(String[] args) {
	// String path = "/sys/userAction_doLogin.action?username=zs&password=123";
	// Matcher matcher = PATTERN.matcher(path);
	// boolean b = matcher.matches();
	// System.out.println(b);
	// }
 
}

在没开启JWT之前我们可以通过地址栏输入的方式跳过登录步骤直接进入页面。

在开启JWT之后我们直接通过地址栏进行页面跳转是不成功的。

UserAction:

package com.zking.vue.web;
 
import java.util.HashMap;
import java.util.Map;
 
import com.fasterxml.jackson.databind.ObjectMapper;
import com.opensymphony.xwork2.ModelDriven;
import com.zking.base.web.BaseAction;
import com.zking.vue.biz.UserBiz;
import com.zking.vue.entity.User;
import com.zking.vue.util.JsonData;
import com.zking.vue.util.JwtUtils;
import com.zking.vue.util.PageBean;
import com.zking.vue.util.ResponseUtil;
import com.zking.vue.util.StringUtils;
 
public class UserAction extends BaseAction implements ModelDriven{
 
	private UserBiz userBiz;
	private User user = new User();
 
	public UserBiz getUserBiz() {
		return userBiz;
	}
 
	public void setUserBiz(UserBiz userBiz) {
		this.userBiz = userBiz;
	}
	 
	public String login() {
		ObjectMapper om = new ObjectMapper();
		JsonData jsonData = null;
		try {
			if(StringUtils.isBlank(user.getUname()) || StringUtils.isBlank(user.getPwd())) {
				jsonData = new JsonData(0, "用户或者密码为空", user);
			}else {
				User u = this.userBiz.login(user);
				Map claims = new HashMap();
				claims.put("uname",user.getUname());
				claims.put("pwd", user.getPwd());
				String jwt = JwtUtils.createJwt(claims, JwtUtils.JWT_WEB_TTL);
				response.setHeader(JwtUtils.JWT_HEADER_KEY, jwt);
				jsonData = new JsonData(1, "登录成功", u);
			}
		} catch (Exception e) {
			e.printStackTrace();
			jsonData = new JsonData(0, "用户或者密码错误", user);
		}finally {
			try {
				ResponseUtil.write(response, om.writeValueAsString(jsonData));
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		return null;
	}
	
	public String getAsyncData() {
		ObjectMapper om = new ObjectMapper();
		try {
			Thread.sleep(6000);
			ResponseUtil.write(response, om.writeValueAsString("http://www.javaxl.com"));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
 
	@Override
	public User getModel() {
		return user;
	}
}

 mutations.js:

export default {
  // type(事件类型): 其值为setEduName
  // payload:官方给它还取了一个高大上的名字:载荷,其实就是一个保存要传递参数的容器
  setEduName: (state, payload) => {
    state.eduName = payload.eduName;
  },
  setJwt:(state,payload)=>{
    state.jwt=payload.jwt;
  }
}

state.js:

export default{
  address:'默认值',
  jwt:''
}

http.js:

/**
 * vue项目对axios的全局配置
 */
import axios from 'axios'
import qs from 'qs'
 
//引入action模块,并添加至axios的类属性urls上
import action from '@/api/action'
axios.urls = action
 
// axios默认配置
axios.defaults.timeout = 10000; // 超时时间
// axios.defaults.baseURL = 'http://localhost:8080/j2ee15'; // 默认地址
axios.defaults.baseURL = action.SERVER;
 
//整理数据
// 只适用于 POST,PUT,PATCH,transformRequest` 允许在向服务器发送前,修改请求数据
axios.defaults.transformRequest = function(data) {
	data = qs.stringify(data);
	return data;
};
 
 
// 请求拦截器
axios.interceptors.request.use(function(config) {
	var jwt = window.vm.$store.getters.getJwt;
	config.headers['jwt'] = jwt;
	return config;
}, function(error) {
	return Promise.reject(error);
});
 
 
 
 
export default axios;

四,总结

整体的思路就是:1.我们通过登录,发送请求到后台JwtDemo中。2.在JwtDemo中会生成Jwt的串。3.将串放到响应头中去,通过控制台的网络中的标头可以查看到Jwt的串。4.请求头会被http.js中的响应拦截器拦截。5.拦截后会将Jwt的串放到state.js中。6.第二次发送queryRootNode请求,会被请求拦截器拦截,就会取出Jwt串,放到请求拦截器中,通过请求头可以查看到串,可以查看的原因是vuex,http.js.。7.请求会被提交到后台,通过JwtFilter类中获取请求头(更具校验获取登录信息)

你可能感兴趣的:(php,开发语言)