再次就是验证payload里的部分,比如是否过期,还有自定义的信息是否相同,加上已经前面验证的signature,这样就保证了这个签名的有效性
互斥登录/修改密码后登录的问题
场景:用户在多个设备或者其它浏览器/IP登录以后或者修改密码以后,那其它的设备就需要自动退出登录
- 用户修改密码之后签发一个新的token来覆盖之前的。但是之前的token只要在失效之前仍然是可以验证并且登录的
- 用户手动退出的时候也是需要token失效的,这就需要退出的时候直接清除或者覆盖客户端保存的token。同上,之前的token仍然有效,所以token的时间不能太长
- 设置黑名单,在签发新token的时候把之前未过期的token加入黑名单使之失效。但是这样就需要储存token,而jwt的优点就是不需要储存空间。
续签
场景:需要用户长期登录的应用,比如用你开发需要用户保持登录的应用,在使用token的时候保证用户体验不需要频繁登录操作,又要保证用户在一段时间之后登录过期重新登录,主要是一个用户体验和用户信息安全的问题。这个时候又要求token不能过长,只能通过续签来保持登录状态,又或者说用户登录的时候你需要获取用户在社交网站的信息,就需要获取社交网站授权的token,这也需要续签,不过这是向社交网站续签。
何时续签:
1.每次请求刷新 每次请求直接覆盖签发新的token
2.记录时间定时刷新 设置一个定时器,记录签发时间,然后到过期的时候签发新的
3.临期时刷新 访问的时候对比时间,如果即将过期就立即刷新。
4.过期后续签,这样设置过期时间的意义就在于如果token被盗取后黑客登录也需要续签,这样用户再次登录客户端就会发现用户登录异常。而且每次获取的token都是新的,旧的只能>获取一次续签的机会,也就说如果用户续签以后之前的token就不能再续签了。
5.jwt-autorefresh(JWT自动刷新)(有限制条件,在客户端定时刷新)。
JWT的优点
- 因为json的通用性,所以JWT是可以跨语言支持的,像C#,JavaScript,NodeJS,PHP等许多语言都可以使用
- 因为由了payload部分,所以JWT可以在自身存储一些其它业务逻辑所必要的非敏感信息
- 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的
- 它不需要在服务端保存会话信息,所以它易于应用的扩展
JWT 安全相关
- 不应该在jwt的payload部分存储敏感信息,因为该部分是客户端可解密的部分
- 保护好secret私钥。该私钥非常重要
- 如果可以,请使用https协议
JWT生成token的安全性分析
从JWT生成的token组成上来看,要想避免token被伪造,主要就得看签名部分了,而签名部分又有三部分组成,其中头部和载荷的base64编码,几乎是透明的,毫无安全性可言,那么最终守护token安全的重担就落在了加入的盐上面了!试想:如果生成token所用的盐与解析token时加入的盐是一样的。岂不是类似于中国人民银行把人民币防伪技术公开了?大家可以用这个盐来解析token,就能用来伪造token。这时,我们就需要对盐采用非对称加密的方式进行加密,以达到生成token与校验token方所用的盐不一致的安全效果!
非对称加密RSA介绍
基本原理:同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端私钥加密,持有私钥或公钥才可以解密公钥加密,持有私钥才可解密 优点:安全,难以破解 缺点:算法比较耗时,为了安全,可以接受 历史:三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字缩写:RSA。
JWT流程图
5.1 创建工程
环境:idea、maven、jdk8、springboot2.x
maven结构图
<?xml version="1.0" encoding="UTF-8"?>pom依赖
<project xmlns=“http://maven.apache.org/POM/4.0.0” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd”>
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.5.3
com.uncle
jwt-demo
0.0.1-SNAPSHOT
jwt-demo
Demo project for Spring Boot
<java.version>1.8</java.version>
com.auth0
java-jwt
3.4.0
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.alibaba
fastjson
1.2.48
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
5.2 自定义注解
package com.uncle.jwtdemo.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
-
@program: jwt-demo
-
@description: 跳过验证
-
@author: 步尔斯特
-
@create: 2021-08-07 16:18
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
package com.uncle.jwtdemo.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
-
@program: jwt-demo
-
@description: 需要登录才可以进行操作
-
@author: 步尔斯特
-
@create: 2021-08-07 16:19
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}
5.3 用户实体类
package com.uncle.jwtdemo.bean;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 16:21
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
String Id;
String username;
String password;
//获取token的方法
public String getToken(User user) {
String token=“”;
token= JWT.create().withAudience(user.getId())
.sign(Algorithm.HMAC256(user.getPassword()));
return token;
}
}
5.4 创建拦截器
package com.uncle.jwtdemo.config;
import com.uncle.jwtdemo.interceptor.AuthenticationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 16:25
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns(“/**”);
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
5.5 认证的拦截器
package com.uncle.jwtdemo.interceptor;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.uncle.jwtdemo.annotations.PassToken;
import com.uncle.jwtdemo.annotations.UserLoginToken;
import com.uncle.jwtdemo.bean.User;
import com.uncle.jwtdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 16:22
*/
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
String token = httpServletRequest.getHeader(“token”);// 从 http 请求头中取出 token
// 如果不是映射到方法直接通过
if (!(object instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
//检查是否有passtoken注释,有则跳过认证
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
return true;
}
}
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(UserLoginToken.class)) {
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if (userLoginToken.required()) {
// 执行认证
if (token == null) {
throw new RuntimeException(“无token,请重新登录”);
}
// 获取 token 中的 user id
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new RuntimeException(“401”);
}
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException(“用户不存在,请重新登录”);
}
// 验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException(“401”);
}
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Object o, Exception e) throws Exception {
}
}
5.6 用户业务
package com.uncle.jwtdemo.service;
import com.uncle.jwtdemo.bean.User;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 16:30
*/
public interface UserService {
User findByUsername(User user);
User findUserById(String id);
}
package com.uncle.jwtdemo.service.impl;
import com.uncle.jwtdemo.bean.User;
import com.uncle.jwtdemo.service.UserService;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 16:48
*/
@Service
public class UserServiceImpl implements UserService {
private static Map<String, User> userMap = new HashMap<>();
@Override
public User findByUsername(User user) {
return userMap.get(user.getUsername());
}
@Override
public User findUserById(String id) {
return userMap.get(id);
}
static {
userMap.put(“1”, new User(“1”, “zhangsan”, “123”));
userMap.put(“zhangsan”,new User(“1”,“zhangsan”,“123”));
}
}
5.7 token业务
package com.uncle.jwtdemo.service;
import com.uncle.jwtdemo.bean.User;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 16:37
*/
public interface TokenService {
String getToken(User userForBase);
}
package com.uncle.jwtdemo.service.impl;
import com.uncle.jwtdemo.bean.User;
import com.uncle.jwtdemo.service.TokenService;
import org.springframework.stereotype.Service;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 17:26
*/
@Service
public class TokenServiceImpl implements TokenService {
@Override
public String getToken(User userForBase) {
return userForBase.getToken(userForBase);
}
}
5.8 测试接口
package com.uncle.jwtdemo.controller;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.uncle.jwtdemo.annotations.UserLoginToken;
import com.uncle.jwtdemo.bean.User;
import com.uncle.jwtdemo.service.TokenService;
import com.uncle.jwtdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
-
@program: jwt-demo
-
@description:
-
@author: 步尔斯特
-
@create: 2021-08-07 16:27
*/
@RestController
@RequestMapping(“api”)
public class UserApi {
@Autowired
UserService userService;
@Autowired
TokenService tokenService;
//登录
@PostMapping(“/login”)
public Object login(@RequestBody User user) {
JSONObject jsonObject=new JSONObject();
User userForBase=userService.findByUsername(user);
if(userForBase==null){
try {
jsonObject.put(“message”,“登录失败,用户不存在”);
} catch (JSONException e) {
e.printStackTrace();
}
return jsonObject;
}else {
if(!userForBase.getPassword().equals(user.getPassword())){
try {
jsonObject.put(“message”,“登录失败,密码错误”);
} catch (JSONException e) {
e.printStackTrace();
}
return jsonObject;
}else {
String token = tokenService.getToken(userForBase);
try {
jsonObject.put(“token”, token);
} catch (JSONException e) {
e.printStackTrace();
}
try {
jsonObject.put(“user”, userForBase);
} catch (JSONException e) {
e.printStackTrace();
}
System.out.println(jsonObject);
return jsonObject;
}
}
}
@UserLoginToken
@GetMapping(“/getMessage”)
public String getMessage(){
return “你已通过验证”;
}
}
5.9 测试模版
GET http://localhost:8080/api/getMessage
Accept: application/json
token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIn0.ihOZFzg3ZGIbBMneRy-4RMqors1P3nuO-wRJnQtTzWQ
POST http://localhost:8080/api/login
Content-Type: application/json
{“username”: “zhangsan”,“password”: “123”}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
小编利用空余时间整理了一份《MySQL性能调优手册》,初衷也很简单,就是希望能够帮助到大家,减轻大家的负担和节省时间。
关于这个,给大家看一份学习大纲(PDF)文件,每一个分支里面会有详细的介绍。
这里都是以图片形式展示介绍,如要下载原文件以及更多的性能调优笔记(MySQL+Tomcat+JVM)!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
//localhost:8080/api/login
Content-Type: application/json
{“username”: “zhangsan”,“password”: “123”}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-8BwTBVTY-1713436505532)]
[外链图片转存中…(img-1PDAi8dQ-1713436505533)]
[外链图片转存中…(img-HKW3XdZp-1713436505533)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
小编利用空余时间整理了一份《MySQL性能调优手册》,初衷也很简单,就是希望能够帮助到大家,减轻大家的负担和节省时间。
关于这个,给大家看一份学习大纲(PDF)文件,每一个分支里面会有详细的介绍。
[外链图片转存中…(img-ijw5IQnT-1713436505533)]
这里都是以图片形式展示介绍,如要下载原文件以及更多的性能调优笔记(MySQL+Tomcat+JVM)!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!