新乡做网站公,兰州网络推广制度,安徽城乡建设厅网站焊工证查询,建设房屋出租网站文章目录 前言一、双Token方案介绍1. 令牌类型与功能2.双Token方案的优点3.实现流程 二、具体实现1.后端实现1.1 jwt工具类1.2 响应工具类1.3 实体类1.4 过滤器1.5 controller1.6 启动类 2、前端实现2.1 登录页面2.2 index页面2.3 请求拦截器和响应拦截器 效果展示 前言
更多j… 文章目录 前言一、双Token方案介绍1. 令牌类型与功能2.双Token方案的优点3.实现流程 二、具体实现1.后端实现1.1 jwt工具类1.2 响应工具类1.3 实体类1.4 过滤器1.5 controller1.6 启动类 2、前端实现2.1 登录页面2.2 index页面2.3 请求拦截器和响应拦截器 效果展示 前言
更多jwt相关文章 jwt理论介绍 springbootvue项目中使用jwt实现登录认证
本篇文章的代码是在springbootvue项目中使用jwt实现登录认证的基础上实现的Token自动续期的功能。 一、双Token方案介绍
双token解决方案是一种用于增强用户登录安全性和提升用户体验的认证机制。它主要涉及两个令牌访问令牌accessToken和刷新令牌refreshToken。以下是对双token解决方案的详细介绍
1. 令牌类型与功能
访问令牌accessToken 有效期较短通常设置为较短的时间如两小时或根据业务需求自定义如10分钟。 储存用户信息权限等包含用户相关信息如UserID、Username等。 用于前端与后端之间的通信认证前端在每次请求时携带此令牌进行校验。 刷新令牌refreshToken 有效期较长可以设置为一星期、一个月或更长时间具体根据业务需求自定义。 不储存额外信息只储存用户id用于在accessToken过期后重新生成新的accessToken。 由于有效期长因此降低了用户需要频繁登录的频率。 2.双Token方案的优点
增强安全性通过短期有效的accessToken和长期有效的refreshToken的结合即使accessToken泄露攻击者也只能在有限时间内进行模拟用户行为降低了安全风险。 提升用户体验由于refreshToken的存在用户无需频繁登录特别是在长时间操作或后台服务场景下提高了用户体验。 3.实现流程 登录用户输入用户名和密码进行登录后端验证成功后生成accessToken和refreshToken并发送给前端。 请求校验前端在每次请求时携带accessToken进行校验如果accessToken有效则允许请求继续如果无效但refreshToken有效则使用refreshToken重新生成accessToken。 令牌刷新当accessToken过期但refreshToken未过期时前端可以使用refreshToken向后端请求新的accessToken无需用户重新登录。 登出用户登出时后端需要同时使accessToken和refreshToken失效以确保用户登出后的安全性。 二、具体实现
1.后端实现
1.1 jwt工具类
package com.etime.util;import io.jsonwebtoken.*;import java.util.Date;
import java.util.Map;
import java.util.UUID;/*** Date 2024/6/10 10:04* Author liukang**/
public class JwtUtil {
// private static long expire 1000*60*5;// 单位是毫秒private static String secret secret;/*** 创建jwt* author liukang* date 10:36 2024/6/10* param expire* param map* return java.lang.String**/public static String generateToken(long expire, Map map){// 床jwt构造器JwtBuilder jwtBuilder Jwts.builder();// 生成jwt字符串String jwt jwtBuilder//头部.setHeaderParam(typ,JWT).setHeaderParam(alg,HS256)// 载荷.setClaims(map) // 设置多个自定义数据 位置只能放在前面如果放在后面那前面的载荷会失效.setId(UUID.randomUUID().toString())// 唯一标识.setIssuer(liukang)// 签发人.setIssuedAt(new Date())// 签发时间.setSubject(jwtDemo)// 主题.setExpiration(new Date(System.currentTimeMillis()expire))//过期时间// 自定义数据
// .claim(uname,liukang)// 签名.signWith(SignatureAlgorithm.HS256,secret).compact();return jwt;}/*** 创建jwt* author liukang* date 10:36 2024/6/10* param expire* return java.lang.String**/public static String generateToken(long expire){// 床jwt构造器JwtBuilder jwtBuilder Jwts.builder();// 生成jwt字符串String jwt jwtBuilder//头部.setHeaderParam(typ,JWT).setHeaderParam(alg,HS256)// 载荷.setId(UUID.randomUUID().toString())// 唯一标识.setIssuer(liukang)// 签发人.setIssuedAt(new Date())// 签发时间.setSubject(jwtDemo)// 主题.setExpiration(new Date(System.currentTimeMillis()expire))//过期时间// 自定义数据
// .claim(uname,liukang)// 签名.signWith(SignatureAlgorithm.HS256,secret).compact();return jwt;}/*** 解析jwt* author liukang* date 10:36 2024/6/10* param jwt* return io.jsonwebtoken.Claims**/public static Claims parseToken(String jwt){JwsClaims claimsJws Jwts.parser().setSigningKey(secret).parseClaimsJws(jwt);Claims playload claimsJws.getBody();return playload;}
}
1.2 响应工具类
代码如下示例
package com.etime.util;import com.etime.vo.ResponseModel;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;/*** Date 2024/6/10 10:00* Author liukang**/
public class ResponseUtil {public static void write(ResponseModel rm, HttpServletResponse response) throws IOException {// 构造响应头response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding(utf-8);// 解决跨域问题 设置跨域头response.setHeader(Access-Control-Allow-Origin,*);// 输出流PrintWriter out response.getWriter();// 输出out.write(new ObjectMapper().writeValueAsString(rm));// 关闭流out.close();}
}
1.3 实体类
登录用户实体类
package com.etime.entity;import lombok.Data;/*** Date 2024/6/10 10:39* Author liukang**/
Data
public class User {private String username;private String password;
}
响应vo类
package com.etime.vo;import lombok.Data;import java.util.Objects;/*** Date 2024/6/10 10:37* Author liukang**/
Data
public class ResponseModel {private Integer code;private String msg;private Object token;public ResponseModel(Integer code, String msg, Object token) {this.code code;this.msg msg;this.token token;}
}
1.4 过滤器
package com.etime.filter;import com.etime.util.JwtUtil;
import com.etime.util.ResponseUtil;
import com.etime.vo.ResponseModel;
import com.sun.deploy.net.HttpResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.util.StringUtils;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** Description jwt过滤器* Date 2024/6/10 9:46* Author liukang**/
WebFilter(urlPatterns /*) // 过滤所有路径
public class JwtFilter implements Filter {Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {// 得到两个对象HttpServletRequest request (HttpServletRequest) servletRequest;HttpServletResponse response (HttpServletResponse) servletResponse;//直接放行if(HttpMethod.OPTIONS.toString().equals(request.getMethod())){filterChain.doFilter(request,response);return;}String requestURI request.getRequestURI(); // 不含主机和端口号if(requestURI.contains(/login)){filterChain.doFilter(request,response);return;}// 得到请求头的信息accessTokenString token request.getHeader(accessToken);if(!StringUtils.hasText(token)){//响应前端错误的消息提示ResponseModel responseModel new ResponseModel(500,failure,令牌缺失);ResponseUtil.write(responseModel,response);return;}// 解析Token信息try {JwtUtil.parseToken(token);}catch (Exception e){//响应前端错误的消息提示ResponseModel responseModel new ResponseModel(401,failure,令牌过期);ResponseUtil.write(responseModel,response);return;}filterChain.doFilter(request,response);}
}
1.5 controller
登录Controller
package com.etime.controller;import com.etime.entity.User;
import com.etime.util.JwtUtil;
import com.etime.vo.ResponseModel;
import org.springframework.web.bind.annotation.*;import java.util.HashMap;
import java.util.Map;/*** Date 2024/6/10 10:38* Author liukang**/
RestController
CrossOrigin
public class LoginController {PostMapping(/login)public ResponseModel login(RequestBody User user){Integer code 200;String msg success;String accessToken null;String refreshToken null;Map tokenMap new HashMap();if(user.getUsername().equals(admin)user.getPassword().equals(123)){// 生成jwtaccessToken JwtUtil.generateToken(1000*10);// 设置有效期为10srefreshToken JwtUtil.generateToken(1000*30);// 设置有效期为30stokenMap.put(accessToken,accessToken);tokenMap.put(refreshToken,refreshToken);}else {code 500;msg failure;}return new ResponseModel(code,msg,tokenMap);}}
测试请求Controller
package com.etime.controller;import com.etime.vo.ResponseModel;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;/*** Date 2024/6/10 12:51* Author liukang**/
CrossOrigin
RestController
public class TestController {PostMapping(/test)public ResponseModel test() {return new ResponseModel(200,success,测试请求接口成功!);}}
刷新Token的Controller
package com.etime.controller;import com.etime.util.JwtUtil;
import com.etime.vo.ResponseModel;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.Map;/*** Date 2024/6/10 15:48* Author liukang**/
CrossOrigin
RestController
public class NewTokenController {GetMapping(/newToken)public ResponseModel newToken(){String accessToken JwtUtil.generateToken(1000*10);String refreshToken JwtUtil.generateToken(1000*30);Map tokenMap new HashMap();tokenMap.put(accessToken,accessToken);tokenMap.put(refreshToken,refreshToken);return new ResponseModel(200,success,tokenMap);}
}
1.6 启动类
package com.etime;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;/*** Author liukang* Date 2022/7/4 11:32*/
SpringBootApplication
ServletComponentScan(basePackages com.etime.filter)// 这个包下激活WebFilter这个注解
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class);}
}
2、前端实现
2.1 登录页面
templatediv classhelloform用户名input v-modelusername/br密码input v-modelpassword /brbutton clicklogin登录/button/form/div/templatescriptexport default {data () {return {username:,password:,}},methods:{login(){this.axios.post(http://localhost:8088/login,{username:this.username,password:this.password,}).then(response {console.log(response.data);if(response.data.code200){sessionStorage.setItem(accessToken,response.data.token.accessToken)sessionStorage.setItem(refreshToken,response.data.token.refreshToken)this.$router.push({ path: index});}}).catch(error {console.error(error);});}},}/scriptstyle scoped/style
2.2 index页面
templatedivbutton clicktest请求受保护的接口/button/div/templatescript
import intercepterConfig from ./js/configexport default {data () {return {}},methods:{test(){const accessToken sessionStorage.getItem(accessToken)let token nullif(accessToken){token accessToken}// console.log(token)this.axios.post(http://localhost:8088/test,{},/*{headers:{accessToken:token}}*/).then(response {// if(response.data.code200){console.log(response.data);// }}).catch(error {console.error(error);});},},}/scriptstyle scoped/style
2.3 请求拦截器和响应拦截器
import axios from axios;
//axios请求拦截器
axios.interceptors.request.use(config{// 正确的请求拦截器let token null;let url config.url// url.indexOf(/newToken)-1 如果是刷新Token的请求 不用在拦截器里面加accessToken 这个请求已经在请求头中设置accessToken加了会覆盖if(sessionStorage.getItem(accessToken)!null url.indexOf(/newToken)-1){token sessionStorage.getItem(accessToken)config.headers[accessToken] token}// 加入头信息的配置return config // 这句没写请求会发不出去},error{ // 出现异常的请求拦截器return Promise.reject(error)})
// axios响应拦截器
axios.interceptors.response.use(async res {// 判断 401状态码 自动续期if (res.data.code 401 !res.config.isRefresh) {//!res.config.isRefresh 不是刷新Token的请求才拦截 是则不拦截// 1.自动续期const res2 await getNewToken()if(res2.data.code 200){console.log(自动续期成功new Date().toLocaleString())// 2.更新sessionStorage里面的Token 没有这一步会死循环sessionStorage.setItem(accessToken,res2.data.token.accessToken)sessionStorage.setItem(refreshToken,res2.data.token.refreshToken)//3.重新发送请求res await axios.request(res.config)// res.config 代表请求的所有参数这里是上一次请求的所有参数包括url和携带的所有数据}}return res // 将重新请求的响应作为响应返回},error{return Promise.reject(error)})function getNewToken(){let url http://localhost:8088/newTokenlet token nullif(sessionStorage.getItem(refreshToken)!null){token sessionStorage.getItem(refreshToken)}return axios.get(url,{headers:{accessToken:token},isRefresh:true})// 注意这里参数是accessToken:token 因为后端过滤器里面获取的是accessToken所以要写这个不然过滤器通不过过滤器
}
效果展示
1.登录页面 2.输入用户名和密码点击【登录】 3.点击【请求受保护的资源】按钮 3.等待10秒accessToken过期但refreshToken未过期时点击【请求受保护的资源】按钮 4.等待30秒后refreshToken和accessToken都过期再次点击【请求受保护的资源】按钮