Jwt新⼿⼊门教程
Jwt的新⼿⼊门教程
1.Jwt究竟是什么东东?
先贴官⽹地址:
再贴官⽅的定义:
What is JSON Web Token?
JSON Web Token (JWT) is an open standard () that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
Although JWTs can be encrypted to also provide secrecy between parties, we will focus on signed tokens. Signed tokens can verify the integrity of the claims contained within it, while encrypted tokens hide those claims from other parties. When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.
我的理解总结:Jwt全称是Json Web Token,顾名思义就是⼀种通过Json⽅式来传输的令牌,并且他最⼤的特点就是signed tokens,签发者可以使⽤各种的加密⽅式对信息进⾏签名,更重要的是Jwt还能验证token是否被篡改或者token是否正确。当然了,这种⽅式注定是不安全的。我们很容易地就可以在官⽹得到这⼀结论。
可以看到,只要我们获得token字符串,就可以获取到⾥⾯的⼤部分信息,除了签名的密钥。
所以Jwt简单来说,就是简单地存储部分不那么重要的信息,通过Json,对客户端进⾏验证的⼀种⽅式。
2.Jwt的组成
从官⽹的Debugger界⾯,我们可以得知,Jwt由三部分组成。
第⼀部分:Header,Header通常由令牌的类型和加密的算法组成,也就是
{
"alg": "HS256",
"typ": "JWT"
}
这个Header的含义就是"alg"--Algorithm(算法) 是HS256。
第⼆部分: Payload,这部分主要是记录我们所存储的简单且不重要的信息。例如:⽤户名,过期时间,⽤户id等等。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
但是要注意的是,Payload记录的这些信息是完全公开的,所以千万不能把⽤户或者系统的敏感数据放到Payload中,Payload只是负责记录简单信息,并不具备加密的功能。
第三部分:Signature, 签名⾥是由三部分组成,Header的Base64编码,Payload的Base64编码,还有secret,然后通过指定的加密⽅式,例如HS256,进⾏加密后得出的字符串。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
可以说这部分是Jwt的重点,因为他承担着两个作⽤,第⼀个:验证JwtToken在传输过程中没有受到篡改第⼆个:验证签发⼈的⾝份
详细说⼀下这两个作⽤。
验证JwtToken在传输过程中没有受到篡改,这个原理就⽐较好理解,因为Signature中有⼀个字符串,也就是secret,这个secret是由我们来设置的,相当于私钥,只有我们⾃⼰才知道,别⼈是不知道的。那么在后续验证过程中,只要我们⾃⼰⽣产的Token与客户端传来的Token进⾏⽐对,如果⼀致那就证明该Token没有受到篡改,反之则证明客户端传来的Token是⾮法的。
验证签发⼈的⾝份,其实第⼆个作⽤是从第⼀个作⽤中展现⽽来的,因为如果能证明Token在传输过程中没有受到篡改,那就更加说明服务端是这个签名的签发者,因为只有签发者
其实secret我们也可以认为是盐(salt),我们知道如果单纯地在数据库中存储明⽂密码,或者是只经过⼀重MD5加密的密码,是⾮常的不安全,因为就算是经过MD5加密,仍然可以通过暴⼒穷举的⽅式来进⾏破解,可是如果在⽤户密码的基础上,加上我们⽣成的随机或固定的字符串,然后再进⾏加密,那么安全程度会⼤⼤提升。
如果不知道盐(salt)的童鞋,可以去bind搜索⼀下相关资料,其实理解起来就是⽤户密码+我们设置的随机或固定字符串再进⾏多重加密。
3.编码实现
我们来创建这⼏个类。
配置类:InterceptorConfig
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
@Bean
public JwtInterceptor authenticationInterceptor() {
return new JwtInterceptor();
}
}
控制类: UserController
@RestController
public class UserController {
@PostMapping("/login")
public Map<String, Object> login(@RequestBody User user) {
Map<String, Object> map = new HashMap<>();
String username = Username();
String password = Password();
// 省略账号密码验证
// 验证成功后发送token
String token = JwtUtil.sign(username, password);
if (token != null) {
map.put("code", "200");
map.put("message", "认证成功");
map.put("token", token);
return map;
}
map.put("code", "403");
map.put("message", "认证失败");
return map;
}
@GetMapping(value = "/api/test")
public String get(){
System.out.println("执⾏了get请求");
return "success";
}
}
服务类:UserService
@Service
public class UserService {
public String getPassword(){
return "admin";
}
}
实体类 User
@Data
public class User {
private String username;
private String password;
}
⾃定义类 JwtIntercepter
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
签名字符串是什么@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从 http 请求头中取出 token
String token = Header("token");
// 如果请求不是映射到⽅法直接通过
if(!(handler instanceof HandlerMethod)){
return true;
}
String username = UserNameByToken(request);
// 这边拿到的⽤户名应该去数据库查询获得密码,简略,步骤在service直接获取密码
boolean result = JwtUtil.verify(token,Password());
if(result){
System.out.println("通过");
return true;
}
}
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
Jwt⼯具类 JwtUtil
public class JwtUtil {
// Token⼀天后过期
public static final long EXPIRE_TIME = 1000 * 60 * 60 * 24;
//检验Token是否正确
public static boolean verify(String token, String username, String secret) {
try {
// 设置加密算法
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = quire(algorithm)
.withClaim("username", username)
.build();
// 效验TOKEN
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
public static String sign(String username, String secret) {
//现在系统的时间 + ⼀天
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
//对密码进⾏加密
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带username信息
ate()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
}
public static String getUserNameByToken(HttpServletRequest request) {
String token = Header("token");
DecodedJWT jwt = JWT.decode(token);
Claim("username")
.asString();
}
}
具体代码,我上传到github上:
这⾥代码的secret则是⽤户的密码。
我们先通过PostMan向/login接⼝发送我们的账号密码,得到Jwt根据我们的账号密码⽣成的token。
{
"code": "200",
"message": "认证成功",
"token": "JleHAiOjE2MzIzMTg1NjksInVzZXJuYW1lIjoiYWRtaW4ifQ.c_m3z11UOcFcS_hZN9KNlidzZ2j6y_Ugkb9awHQ3FGY"
}
这⾥认证成功后,服务器如果不经过我们其他的存储操作,是不会对⽣成的token进⾏持久化或其他控制的,所以⼀旦签发出去,这个token串就会变成⽆状态了。
接着,我们访问⼀下我们的api测试接⼝,因为我们在config⾥配置了全局,且重写了HandlerInterceptor类,除了/login路径,其他全路径都需要请求的Header(请求头)中带有token字段,且需验证token成功后才会允许访问,否则进⾏拦截。
4.管理JwtToken的状态
要做到管理JwtToken的状态,我们可以通过把token存储到Redis数据库中,通过设置key的过期时间,就可以做到对Jwt的过期操作,同时也能够对Token进⾏续签,失效等等操作。这部分先不去仔细探究,有个思路就可以了。具体的编码实现我相信也不难。
本⽂中所有的代码均已上传到github上,如有需要请下载。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论