JSON Web Token(JWT)?
官方文档是这样解释的:JSON Web Token(JWT)
是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,可以在各方之间作为JSON对象安全地传输信息。此信息可以通过数字签名进行验证和信任。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
虽然JWT可以加密以在各方之间提供保密,但只将专注于签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则隐藏其他方的声明。当使用公钥/私钥对签署令牌时,签名还证明只有持有私钥的一方是签署私钥的一方。
通俗来讲,JWT是一个含签名并携带用户相关信息的加密串,页面请求校验登录接口时,请求头中携带JWT串到后端服务,服务端通过签名加密串匹配校验,保证信息未被篡改。校验通过则认为是可靠的请求,将正常返回数据。
适合使用JWT的情况
授权:这是最常见的使用场景,解决单点登录问题。因为JWT使用起来轻便,开销小,服务端不用记录用户状态信息(无状态),所以使用比较广泛;
信息交换:JWT是在各个服务之间安全传输信息的好方法。因为JWT可以签名,例如,使用公钥/私钥对儿 - 可以确定请求方是合法的。此外,由于使用标头和有效负载计算签名,还可以验证内容是否未被篡改。
添加JWT依赖
<!-- Jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
自定义注释
@UserLoginToken
用来配置需要拦截器拦截的接口
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}
定义获取token的方法
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.example.demo.model.User;
import org.springframework.stereotype.Service;
@Service("TokenService")
public class TokenService {
public String getToken(User user){
String token = "";
token = JWT.create().withAudience(user.getId()) // 将 user_id 保存到token里
.sign(Algorithm.HMAC256(user.getPassword())); // 以 password 作为 token 的密钥
return token;
}
}
定义验证器
UserDao 可以参考Springboot 使用 JPA 连接 MySQL 数据库
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.example.demo.annotation.UserLoginToken;
import com.example.demo.jpaRepository.UserDao;
import com.example.demo.model.User;
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;
public class AuthenticationInterceptor implements HandlerInterceptor {
/* UserDao 可以参考JPA连接数据库:https://blog.csdn.net/weixin_45703665/article/details/103351737 */
@Autowired
UserDao userDao;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
// 从 http 请求头中获取 token
String token = request.getHeader("token");
// 如果不是映射到方法 直接通过
if (!(object instanceof HandlerMethod)) {
/* 如果不加这个判断,会直接拦截404报错,并返回500的报错 */
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
// 检查有没有需要用户权限的注释
if (method.isAnnotationPresent(UserLoginToken.class)) {
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if (userLoginToken.required()) {
if (token == null) {
response.setStatus(401);
return false;
}
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
response.setStatus(401); /* 返回401状态码 */
return false; /* 不再继续直接,直接结束进程 */
}
User user = userDao.findUserById(userId);
if(user == null){
/* 如果没有用户信息,则返回401状态码 */
response.setStatus(401);
return false;
}
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try{
/* 验证当前token是否有效 */
jwtVerifier.verify(token);
}catch (JWTVerificationException e){
response.setStatus(401);
return false;
}
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 {
}
}
创建拦截器
拦截器请参考:Springboot 拦截器(简单版,使用session存储)
import com.example.demo.interceptor.AuthenticationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration addInterceptor = registry.addInterceptor(authenticationInterceptor());
addInterceptor.addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor(){
return new AuthenticationInterceptor();
}
}
测试
数据库里的密码一定要加密,加密请参考Springboot BCrypt加密
import com.example.demo.annotation.UserLoginToken;
import com.example.demo.jpaRepository.UserDao;
import com.example.demo.model.User;
import com.example.demo.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("api")
public class UserController {
@Autowired
UserDao userDao;
@Autowired
TokenService tokenService;
@PostMapping("/login")
public Map<String, Object> login(String tel, String password){
Map<String, Object> map = new HashMap<>();
User user = userDao.findUserByTel(tel);
if(user == null){
map.put("msg", "用户不存在");
}else{
String token = tokenService.getToken(user);
map.put("msg", "登录成功");
map.put("token", token);
map.put("user", user);
}
return map;
}
@UserLoginToken
@GetMapping("/getMessage")
public String getMessage(){
return "你已通过验证";
}
}