- 什么是JWT
Json Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象的形式安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
尽管jwt可以被加密以提供双方之间的保密性,但我们将重点关注签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则向其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方才是签名方。
- 什么时候使用JWT
以下是JSON Web Tokens使用的一些场景:
授权:这是使用JWT最常见的场景。一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录(Single-Sign-On)是目前广泛使用JWT的一个特性,因为它的开销很小,而且能够轻松地跨不同的域使用。
信息交换:JSON Web Tokens 是一种在各方之间安全传输信息的好方法。因为jwt可以使用公钥/私钥对进行签名,因此可以确保发送者是他们所说的人。另外,由于签名是使用报头和有效负载计算的,所以您还可以验证内容没有被篡改。
- JWT结构
JSON Web Tokens 由三个部分组成,用点(.)隔开,分别是:
1、Header
2、Payload
3、Signature
如下形式:
xxxx.yyyy.zzzz
Header(头)
头通常由两部分组成:Token的类型(JWT)和 签名算法,如HMAC、SHA256或RSA。
示例:
{
"alg": "HS256",
"typ": "JWT"
}
这个JSON会被Base64Url编码,形成JWT的第一部分。
Payload(负载)
Token的第二部分是有效负载,它包含声明。声明是关于实体(通常是用户)和附加数据。有三种类型的声明:注册声明、公共声明和私有声明。
注册声明:这些是一组预定义的声明,这些声明不是强制性的,而是推荐的,以提供一组有用的、可互操作的声明。其中包括:iss(发行者)、exp(到期时间)、sub(主题)、aud(接受jwt的一方)和其他。
公共声明:这些可以由使用jwt的用户随意定义。一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。
私有声明:这些是为在同意使用它们的各方之间共享信息而创建的自定义声明,这些声明既不是注册的,也不是公开的。
示例:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后对有效负载进行Base64Url编码,以形成JSON Web Tokens的第二部分。
Signature(签名)
要创建签名部分,您必须获取编码的头、负载、密钥、算法,并对其进行签名。
示例:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
签名用于验证消息在整个过程中没有被更改,如果是使用私钥签名的Token的情况下,它还可以验证JWT的发送者是它所说的人。
JWT示例:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYWRtaW4iLCJpZCI6IjEiLCJleHAiOjE2MDY5NzA0ODd9.uMB243IGMnms3KtYPqZR4JJQoXePdzdBg8X8uaOkISI
接下来在springboot中使用jwt
加入依赖:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.11.0</version>
</dependency>
实体,DAO,Service类
@Entity
@Table(name = "T_USERS")
public class Users implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private String id ;
private String username ;
private String password ;
}
public interface UsersRepository extends JpaRepository<Users, String>, JpaSpecificationExecutor<Users> {
Users findByUsernameAndPassword(String username, String password) ;
}
@Service
public class UsersService {
@Resource
private UsersRepository usersRepository ;
public Users login(String username, String password) {
return usersRepository.findByUsernameAndPassword(username, password) ;
}
}
两个API接口,一个登录,一个作为测试资源接口
@RestController
@RequestMapping("/users")
public class UsersController {
@Resource
private UsersService usersService ;
@GetMapping("/login")
public String login(String username, String password) {
Users users = usersService.login(username, password) ;
if (users != null) {
return JwtUtils.genToken(users) ;
}
return null ;
}
}
@RestController
@RequestMapping("/res")
public class ResourceController {
@GetMapping("/info")
public Object infos() {
return "resource" ;
}
}
生成JWT 的工具类:
public class JwtUtils {
public static String genToken(Users users) {
Builder builder = JWT.create() ;
builder.withClaim("id", users.getId()) ;
builder.withClaim("name", users.getUsername()) ;
LocalDateTime ldt = LocalDateTime.now().plusMinutes(30) ;
// 过期时间
builder.withExpiresAt(Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant())) ;
// 算法
Algorithm algorithm = Algorithm.HMAC256("abc") ;
return builder.sign(algorithm) ;
}
}
为了简单,密码直接写死“abc”
这里可以看看是怎么生成的token。sign方法:
进入JWTCreator的sign方法
这里接看出来JWT 3部分是怎么生成的了。
拦截器
public class SecurityInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String token = extractToken(request) ;
if (token == null) {
throw new RuntimeException("缺失有效token信息") ;
}
JWTVerifier verify = JWT.require(Algorithm.HMAC256("abc")).build() ;
try {
verify.verify(token) ;
} catch (Exception e) {
throw new RuntimeException(e) ;
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
private String extractToken(HttpServletRequest request) {
String token = request.getHeader("access-token") ;
if (token == null) {
token = request.getParameter("access-token") ;
}
return token ;
}
}
拦截器配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/res/**") ;
}
}
到此所有的类完成
测试:
直接访问资源接口/res/info
这里没有写全局异常拦截,看到效果即可。
通过登录获取token
将token信息加入到其他接口(资源接口)的请求header中
都是很基础的东西。
完毕!!!
给个转发,关注吧,谢谢