目录
JWT全称为JSON Web Token,是一种用于身份验证和授权的开放标准。它是一个紧凑且安全的方式,通过在不同系统之间传递可信任的信息来实现用户身份验证。JWT由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。
头部包含了描述令牌类型和所使用的加密算法的元数据。常见的加密算法有HMAC、RSA等。载荷包含了要传输的数据,比如用户ID、角色和其他相关的声明信息。载荷可以自定义,但需要遵循一定的规范。
签名是对头部和载荷进行签名生成的,用于验证令牌的真实性和完整性。它使用头部中指定的加密算法和密钥进行签名,并将签名与头部和载荷一起编码为一个字符串形式的JWT。
JWT的精髓在于:“去中心化”,数据是保存在客户端的。
JWT的工作流程通常如下:当用户登录时,服务器会生成一个JWT并返回给客户端。客户端在后续的请求中将这个JWT放在请求的头部或参数中发送给服务器。服务器在接收到请求后会解析JWT,并根据其中的信息进行身份验证和授权。
JWT的优点是可以避免在服务器端存储session信息,使得服务端无状态化,提高了系统的可扩展性。同时,JWT还具有跨域支持和自包含性的特点,使得它在分布式系统中广泛应用。
需要注意的是,JWT仅仅对于信息的完整性和真实性提供了保护,但并不加密具体的数据。因此,在使用JWT时,应避免在载荷中包含敏感信息,或者对敏感信息进行加密处理。另外,由于JWT是基于Token的认证方式,为了确保安全性,应采取适当的措施保护JWT的传输过程和存储过程。
JWT工具类是一个用于生成、解析和验证JSON Web Token的辅助类。它提供了一系列方法来简化JWT的处理过程,使得开发者可以方便地在应用中使用JWT进行身份验证和授权。
JWT工具类通常包括以下功能:
生成JWT:提供方法用于生成JWT,传入有效载荷和加密密钥等参数,生成符合规范的JWT字符串。
解析JWT:提供方法用于解析JWT,将JWT字符串解析成头部和载荷的JSON对象,并对签名进行验证。
验证JWT:提供方法用于验证JWT的有效性,包括验证签名是否正确、验证令牌是否过期等。
获取载荷信息:提供方法用于获取JWT中的载荷信息,比如用户ID、角色等。
刷新JWT:提供方法用于刷新JWT,生成新的JWT并返回给客户端,用于延长认证的有效期。
配置参数:提供方法用于配置JWT的相关参数,比如加密算法、密钥、过期时间等。
使用JWT工具类可以简化JWT的处理逻辑,减少代码重复性,并增加系统的安全性。通过封装好的方法,开发者可以方便地在不同的场景中使用JWT进行身份验证和授权,提高开发效率。但需要注意,在使用JWT工具类时,应确保密钥的安全性,避免泄露导致令牌被篡改或伪造。
- package com.zking.ssm.jwt;
-
- import java.util.Date;
- import java.util.Map;
- import java.util.UUID;
-
- import javax.crypto.SecretKey;
- import javax.crypto.spec.SecretKeySpec;
-
- import org.apache.commons.codec.binary.Base64;
-
- import io.jsonwebtoken.Claims;
- import io.jsonwebtoken.JwtBuilder;
- import io.jsonwebtoken.Jwts;
- import io.jsonwebtoken.SignatureAlgorithm;
-
- /**
- * JWT验证过滤器:配置顺序 CorsFilte->JwtUtilsr-->StrutsPrepareAndExecuteFilter
- *
- */
- public class JwtUtils {
- /**
- * JWT_WEB_TTL:WEBAPP应用中token的有效时间,默认30分钟
- */
- public static final long JWT_WEB_TTL = 30 * 60 * 1000;
-
- /**
- * 将jwt令牌保存到header中的key
- */
- public static final String JWT_HEADER_KEY = "jwt";
-
- // 指定签名的时候使用的签名算法,也就是header那部分,jwt已经将这部分内容封装好了。
- private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
- private static final String JWT_SECRET = "f356cdce935c42328ad2001d7e9552a3";// JWT密匙
- private static final SecretKey JWT_KEY;// 使用JWT密匙生成的加密key
-
- static {
- byte[] encodedKey = Base64.decodeBase64(JWT_SECRET);
- JWT_KEY = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
- }
-
- private JwtUtils() {
- }
-
- /**
- * 解密jwt,获得所有声明(包括标准和私有声明)
- *
- * @param jwt
- * @return
- * @throws Exception
- */
- public static Claims parseJwt(String jwt) {
- Claims claims = Jwts.parser()
- .setSigningKey(JWT_KEY)
- .parseClaimsJws(jwt)
- .getBody();
- return claims;
- }
-
- /**
- * 创建JWT令牌,签发时间为当前时间
- *
- * @param claims
- * 创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
- * @param ttlMillis
- * JWT的有效时间(单位毫秒),当前时间+有效时间=过期时间
- * @return jwt令牌
- */
- public static String createJwt(Map<String, Object> claims,
- long ttlMillis) {
- // 生成JWT的时间,即签发时间 2021-10-30 10:02:00 -> 30 10:32:00
-
- long nowMillis = System.currentTimeMillis();
-
-
- //链式语法:
- // 下面就是在为payload添加各种标准声明和私有声明了
- // 这里其实就是new一个JwtBuilder,设置jwt的body
- JwtBuilder builder = Jwts.builder()
- // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
- .setClaims(claims)
- // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
- // 可以在未登陆前作为身份标识使用
- .setId(UUID.randomUUID().toString().replace("-", ""))
- // iss(Issuser)签发者,写死
- .setIssuer("zking")
- // iat: jwt的签发时间
- .setIssuedAt(new Date(nowMillis))
- // 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可放数据{"uid":"zs"}。此处没放
- // .setSubject("{}")
- // 设置签名使用的签名算法和签名使用的秘钥
- .signWith(SIGNATURE_ALGORITHM, JWT_KEY)
- // 设置JWT的过期时间
- .setExpiration(new Date(nowMillis + ttlMillis));
-
- return builder.compact();
- }
-
- /**
- * 复制jwt,并重新设置签发时间(为当前时间)和失效时间
- *
- * @param jwt
- * 被复制的jwt令牌
- * @param ttlMillis
- * jwt的有效时间(单位毫秒),当前时间+有效时间=过期时间
- * @return
- */
- public static String copyJwt(String jwt, Long ttlMillis) {
- //解密JWT,获取所有的声明(私有和标准)
- //old
- Claims claims = parseJwt(jwt);
-
- // 生成JWT的时间,即签发时间
- long nowMillis = System.currentTimeMillis();
-
- // 下面就是在为payload添加各种标准声明和私有声明了
- // 这里其实就是new一个JwtBuilder,设置jwt的body
- JwtBuilder builder = Jwts.builder()
- // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
- .setClaims(claims)
- // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
- // 可以在未登陆前作为身份标识使用
- //.setId(UUID.randomUUID().toString().replace("-", ""))
- // iss(Issuser)签发者,写死
- // .setIssuer("zking")
- // iat: jwt的签发时间
- .setIssuedAt(new Date(nowMillis))
- // 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可放数据{"uid":"zs"}。此处没放
- // .setSubject("{}")
- // 设置签名使用的签名算法和签名使用的秘钥
- .signWith(SIGNATURE_ALGORITHM, JWT_KEY)
- // 设置JWT的过期时间
- .setExpiration(new Date(nowMillis + ttlMillis));
- return builder.compact();
- }
- }
-
-
-
-
后台要求:
1. 用户登录方法,放开用户信息生成jwt串保存到响应头中
- package com.zking.ssm.controller;
-
- import com.zking.ssm.service.IUserService;
- import com.zking.ssm.util.JsonResponseBody;
- import com.zking.ssm.util.PageBean;
- import com.zking.ssm.vo.UserVo;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.ResponseBody;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import com.zking.ssm.jwt.*;
-
- @Controller
- @RequestMapping("/user")
- public class UserController {
-
- @Autowired
- private IUserService userService;
-
- @RequestMapping("/userLogin")
- @ResponseBody
- public JsonResponseBody<?> userLogin(UserVo userVo, HttpServletResponse response){
- if(userVo.getUsername().equals("admin")&&userVo.getPassword().equals("123")){
- //私有要求claim
- Map<String,Object> json=new HashMap<String,Object>();
- json.put("username", userVo.getUsername());
- // 生成JWT,并设置到response响应头中
- String jwt=JwtUtils.createJwt(json, JwtUtils.JWT_WEB_TTL);
- response.setHeader(JwtUtils.JWT_HEADER_KEY, jwt);
- return new JsonResponseBody<>("用户登陆成功!",true,0,null);
- }else{
- return new JsonResponseBody<>("用户名或密码错误!",false,0,null);
- }
- }
-
- @RequestMapping("/userRegister")
- @ResponseBody
- public JsonResponseBody<?> userRegistered(UserVo userVo, HttpServletRequest request){
- int insertSelective = userService.insertSelective(userVo);
- if(insertSelective>0){
- return new JsonResponseBody<>("用户注册成功!",true,0,null);
- }else{
- return new JsonResponseBody<>("注册失败,请稍后!",false,0,null);
- }
- }
-
- @RequestMapping("/queryUserPager")
- @ResponseBody
- public JsonResponseBody<List<Map<String,Object>>>
- queryUserPager(UserVo userVo, HttpServletRequest request){
- try {
- PageBean pageBean=new PageBean();
- pageBean.setRequest(request);
- List<Map<String, Object>> users = userService.queryUserPager(userVo, pageBean);
- return new JsonResponseBody<>("OK",true,pageBean.getTotal(),users);
- } catch (Exception e) {
- e.printStackTrace();
- return new JsonResponseBody<>("分页查询用户信息失败!",false,0,null);
- }
-
- }
- }
2. 关闭JwtFilter中的OFF开关,开启jwt验证
- package com.zking.ssm.jwt;
-
- 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验证过滤器,配置顺序 :CorsFilter-->JwtFilter-->struts2中央控制器
- *
- * @author Administrator
- *
- */
- public class JwtFilter implements Filter {
-
- // 排除的URL,一般为登陆的URL(请改成自己登陆的URL)
- private static String EXCLUDE = "^/user/userLogin?.*$";
-
- private static Pattern PATTERN = Pattern.compile(EXCLUDE);
-
- private boolean OFF = false;// 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;
- //获取当前请求路径。只有登录的请求路径不进行校验之外,其他的URL请求路径必须进行JWT令牌校验
- //http://localhost:8080/ssh2/bookAction_queryBookPager.action
- //req.getServletPath()==/bookAction_queryBookPager.action
- String path = req.getServletPath();
- if (OFF || isExcludeUrl(path)) {// 登陆直接放行
- chain.doFilter(request, response);
- return;
- }
-
- // 从客户端请求头中获得令牌并验证
- //token=头.载荷.签名
- String jwt = req.getHeader(JwtUtils.JWT_HEADER_KEY);
- Claims claims = this.validateJwtToken(jwt);
- //在这里请各位大哥大姐从JWT令牌中提取payload中的声明部分
- //从声明部分中获取私有声明
- //获取私有声明中的User对象 -> Modules
- Boolean flag=false;
- if (null == claims) {
- // resp.setCharacterEncoding("UTF-8");
- resp.sendError(403, "JWT令牌已过期或已失效");
- return;
- } else {
-
- //1.获取已经解析后的payload(私有声明)
- //2.从私有声明中当前用户所对应的权限集合List<String>或者List<Module>
- //3.循环权限(Module[id,url])
- // OK,放行请求 chain.doFilter(request, response);
- // NO,发送错误信息的JSON
- // ObjectMapper mapper=new ObjectMapper()
- // mapper.writeValue(response.getOutputStream(),json)
-
- 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) {
- //该解析方法会验证:1)是否过期 2)签名是否成功
- 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);
- // }
-
- }
-
3. web.xml中要配置corsfilter,允许jwt使用请求头及响应头
- <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns="http://java.sun.com/xml/ns/javaee"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
- id="WebApp_ID" version="3.0">
- <display-name>Archetype Created Web Application</display-name>
-
- <!--1.实现Spring与Web集成,实现Spring上下文的初始化工作-->
- <!-- spring上下文配置文件 -->
- <context-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:spring.xml</param-value>
- </context-param>
- <!-- 读取Spring上下文的监听器 -->
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
-
- <!--2.配置中文乱码过滤器,使用Spring自带的过滤器-->
- <filter>
- <filter-name>encodingFilter</filter-name>
- <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
- <async-supported>true</async-supported>
- <init-param>
- <param-name>encoding</param-name>
- <param-value>UTF-8</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>encodingFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
-
- <!--CrosFilter跨域过滤器-->
- <filter>
- <filter-name>corsFilter</filter-name>
- <filter-class>com.zking.ssm.util.CorsFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>corsFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
-
- <!--JwtFilter-->
- <filter>
- <filter-name>jwtFilter</filter-name>
- <filter-class>com.zking.ssm.jwt.JwtFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>jwtFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
-
- <!--Spring MVC核心控制器-->
- <servlet>
- <servlet-name>SpringMVC</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <!--此参数可以不配置,默认值为:/WEB-INF/springmvc-servlet.xml-->
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>/WEB-INF/spring-mvc.xml</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- <!--web.xml 3.0的新特性,是否支持异步-->
- <async-supported>true</async-supported>
- </servlet>
- <servlet-mapping>
- <servlet-name>SpringMVC</servlet-name>
- <url-pattern>/</url-pattern>
- </servlet-mapping>
- </web-app>
前端代码:
state.js
- export default{
- wxName:'派大星',
- jwt:''
- }
mutations.js
- export default{
- // state指的是state.js文件中导出的对象
- // payload就是vue文件传递过来的参数
- setWxName:(state,payload)=>{
- state.wxName = payload.wxName
- },
- setJwt:(state,payload)=>{
- state.jwt = payload.jwt
- }
- }
-
getters.js
- export default{
- getWxName:(state)=>{
- return state.wxName;
- },
- getJwt:(state)=>{
- return state.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) {
- let jwt = window.vm.$store.getters.getJwt;
- if(jwt){
- config.headers['jwt']=jwt;
- }
- return config;
- }, function(error) {
- return Promise.reject(error);
- });
-
- // 响应拦截器
- axios.interceptors.response.use(function(response) {
- let jwt = response.headers['jwt'];
- if(jwt){
- // 要将响应头中的jwt串放入到state.js中
- window.vm.$store.commit('setJwt',{
- jwt:jwt
- });
- }
- return response;
- }, function(error) {
- return Promise.reject(error);
- });
-
-
- export default axios;
main.js
- // The Vue build version to load with the `import` command
- // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
- import Vue from 'vue'
-
- //开发环境下才会引入mockjs
- // process.env.MOCK && require('@/mock')
-
- // 新添加1
- import ElementUI from 'element-ui'
- // 新添加2,避免后期打包样式不同,要放在import App from './App';之前
- import 'element-ui/lib/theme-chalk/index.css'
-
- import App from './App'
- import router from './router'
- import store from './store'
-
- // 新添加3
- Vue.use(ElementUI)
- Vue.config.productionTip = false
-
- import axios from '@/api/http'
- import VueAxios from 'vue-axios'
-
- Vue.use(VueAxios,axios)
-
- /* eslint-disable no-new */
- window.vm = new Vue({
- el: '#app',
- router,
- store,
- data(){
- return {
- Bus:new Vue()
- }
- },
- components: { App },
- template: '
' - })