服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - spring boot+jwt实现api的token认证详解

spring boot+jwt实现api的token认证详解

2021-06-22 13:26神牛003 Java教程

这篇文章主要给大家介绍了关于spring boot+jwt实现api的token认证的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一学习学习吧

前言

本篇和大家分享jwt(json web token)的使用,她主要用来生成接口访问的token和验证,其单独结合springboot来开发api接口token验证很是方便,由于jwt的token中存储有用户的信息并且有加密,所以适用于分布式,这样直接吧信息存储在用户本地减速了服务端存储sessiion或token的压力;

如下快速使用:

?
1
2
3
4
5
6
7
8
9
10
11
12
<!--jwt-->
<dependency>
 <groupid>io.jsonwebtoken</groupid>
 <artifactid>jjwt</artifactid>
 <version>0.9.0</version>
</dependency>
<!--阿里 fastjson依赖-->
<dependency>
 <groupid>com.alibaba</groupid>
 <artifactid>fastjson</artifactid>
 <version>1.2.44</version>
</dependency>

一般使用jwt来达到3种结果:

  • 生成token
  • 验证token是否有效
  • 获取token中jwt信息(主要用户信息)

生成token

引入了jjwt依赖后,要生成token很方便;对于一个token来说,代表的是唯一并且不可逆的,因此我们在生成时需要增加一些唯一数据进去,比如下面的id:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
long currenttime = system.currenttimemillis();
return jwts.builder()
  .setid(uuid.randomuuid().tostring())
  .setissuedat(new date(currenttime)) //签发时间
  .setsubject("system") //说明
  .setissuer("shenniu003") //签发者信息
  .setaudience("custom") //接收用户
  .compresswith(compressioncodecs.gzip) //数据压缩方式
 
  .signwith(signaturealgorithm.hs256, encrykey) //加密方式
  .setexpiration(new date(currenttime + secondtimeout * 1000)) //过期时间戳
  .addclaims(claimmaps) //cla信息
  .compact();

通过uuid来标记唯一id信息;当然在对token加密时需要用到秘钥,jwt很是方便她支持了很多中加密方式如:hs256,hs265,md5等复杂及常用的加密方式;

jwt生成的token中内容分为3个部分:head信息,payload信息,sign信息,通常我们要做的是往payload增加一些用户信息(比如:账号,昵称,权限等,但不包含密码);在对jwt的token有一定了解后,我们来看下真实生成的token值:

?
1
eyjhbgcioijiuzi1niisinppcci6ikdasvaifq.h4siaaaaaaaaafwmtq7cibse7_lwkpdzaesp4qnyinciptx4ine0vbtg4sllfppn7hatgwbwg1bkl4grcbeciwpujzf8iiepjxfapaag2reypuecr2vxyed13nb0pplw3hp1eenblqsquiffy0ohurl3i70evweu_afsejzhd7dlcdv5ntmyhuilhtd3rf_hacchrtv--7yaaaa.i4xwoqtawi0-dwhwn8uz4dbm-vfli5bavyu9lryxu5e

验证token是否有效

token生成的时都会伴随者有一个失效的时间,在这我们可以通过setexpiration函数设置过期时间,记住jwt的有效时间不是滑动的,也就是说不做任何处理时,当到达第一次设置的失效时间时,就基本没用了,要获取token是否过期可以使用如下方式:

?
1
2
3
4
5
6
7
8
9
public static boolean isexpiration(string token, string encrykey) {
 try {
  return getclaimsbody(token, encrykey)
    .getexpiration()
    .before(new date());
 } catch (expiredjwtexception ex) {
  return true;
 }
}

这里使用了date的before来用获取的过期时间和当前时间对比,判断是否继续有效,需要注意的是如果在token失效后再通过getclaimsbody(token, encrykey)获取信息,此时会报expiredjwtexception错误,我们即可认为过期。

获取token中jwt信息(主要用户信息)

通常我们要把登录用户信息存储在jwt生成的token中,这里可以通过 addclaims(claimmaps) 传递map来设置信息,反过来要获取token中的用户信息,我们需要这样做:

?
1
2
3
4
return jwts.parser()
  .setsigningkey(encrykey)
  .parseclaimsjws(token)
  .getbody();

此时body获取出来是claims类型,我们需要从中获取到用户信息,需要注意的是在addclaims存储信息的时候如果存储的map值没做过出来,那完整的实体对象存储进去后会映射成一个linkhasmap类型,如下:

spring boot+jwt实现api的token认证详解

因此通常会在存储的时候json化,如下代码:

?
1
2
3
claimmaps.foreach((key, val) -> {
 claimmaps.put(key, json.tojsonstring(val));
});

再来就是通过get方法获取我们存储进去的信息,并json反序列化:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 获取body某个值
*
* @param token
* @param encrykey
* @param key
* @return
*/
public static object getval(string token, string encrykey, string key) {
 return getjws(token, encrykey).getbody().get(key);
}
 
/**
 * 获取body某个值,json字符转实体
 *
 * @param token
 * @param encrykey
 * @param key
 * @param tclass
 * @param <t>
 * @return
 */
public static <t> t getvalbyt(string token, string encrykey, string key, class<t> tclass) {
 try {
  string strjson = getval(token, encrykey, key).tostring();
  return json.parseobject(strjson, tclass);
 } catch (exception ex) {
  return null;
 }
}

来到这里一个jwt的util代码基本就完成了,下面给出完整的代码例子,仅供参考:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
public class jwtutil {
 
 /**
  * 获取token - json化 map信息
  *
  * @param claimmaps
  * @param encrykey
  * @param secondtimeout
  * @return
  */
 public static string gettokenbyjson(map<string, object> claimmaps, string encrykey, int secondtimeout) {
  return gettoken(claimmaps, true, encrykey, secondtimeout);
 }
 
 /**
  * 获取token
  *
  * @param claimmaps
  * @param isjsonmpas
  * @param encrykey
  * @param secondtimeout
  * @return
  */
 public static string gettoken(map<string, object> claimmaps, boolean isjsonmpas, string encrykey, int secondtimeout) {
 
  if (isjsonmpas) {
   claimmaps.foreach((key, val) -> {
    claimmaps.put(key, json.tojsonstring(val));
   });
  }
  long currenttime = system.currenttimemillis();
  return jwts.builder()
    .setid(uuid.randomuuid().tostring())
    .setissuedat(new date(currenttime)) //签发时间
    .setsubject("system") //说明
    .setissuer("shenniu003") //签发者信息
    .setaudience("custom") //接收用户
    .compresswith(compressioncodecs.gzip) //数据压缩方式
 
    .signwith(signaturealgorithm.hs256, encrykey) //加密方式
    .setexpiration(new date(currenttime + secondtimeout * 1000)) //过期时间戳
    .addclaims(claimmaps) //cla信息
    .compact();
 }
 
 /**
  * 获取token中的claims信息
  *
  * @param token
  * @param encrykey
  * @return
  */
 private static jws<claims> getjws(string token, string encrykey) {
  return jwts.parser()
    .setsigningkey(encrykey)
    .parseclaimsjws(token);
 }
 
 public static string getsignature(string token, string encrykey) {
  try {
   return getjws(token, encrykey).getsignature();
  } catch (exception ex) {
   return "";
  }
 }
 
 /**
  * 获取token中head信息
  *
  * @param token
  * @param encrykey
  * @return
  */
 public static jwsheader getheader(string token, string encrykey) {
  try {
   return getjws(token, encrykey).getheader();
  } catch (exception ex) {
   return null;
  }
 }
 
 /**
  * 获取payload body信息
  *
  * @param token
  * @param encrykey
  * @return
  */
 public static claims getclaimsbody(string token, string encrykey) {
  return getjws(token, encrykey).getbody();
 }
 
 /**
  * 获取body某个值
  *
  * @param token
  * @param encrykey
  * @param key
  * @return
  */
 public static object getval(string token, string encrykey, string key) {
  return getjws(token, encrykey).getbody().get(key);
 }
 
 /**
  * 获取body某个值,json字符转实体
  *
  * @param token
  * @param encrykey
  * @param key
  * @param tclass
  * @param <t>
  * @return
  */
 public static <t> t getvalbyt(string token, string encrykey, string key, class<t> tclass) {
  try {
   string strjson = getval(token, encrykey, key).tostring();
   return json.parseobject(strjson, tclass);
  } catch (exception ex) {
   return null;
  }
 }
 
 /**
  * 是否过期
  *
  * @param token
  * @param encrykey
  * @return
  */
 public static boolean isexpiration(string token, string encrykey) {
  try {
   return getclaimsbody(token, encrykey)
     .getexpiration()
     .before(new date());
  } catch (expiredjwtexception ex) {
   return true;
  }
 }
 
 public static string getsubject(string token, string encrykey) {
  try {
   return getclaimsbody(token, encrykey).getsubject();
  } catch (exception ex) {
   return "";
  }
 }
}

过滤器验证token

有了基本的jwtutil工具,我们需要用到springboot项目中,一般来说对于登录授权token验证可以通过过滤器来操作,这里创建一个authenfilter,用于对post请求过来的token做验证:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class authenfilter implements filter {
 @override
 public void dofilter(servletrequest servletrequest, servletresponse servletresponse, filterchain filterchain) throws ioexception, servletexception {
 
  httpservletrequest rq = (httpservletrequest) servletrequest;
  httpservletresponse rp = (httpservletresponse) servletresponse;
  rpbase rpbase = new rpbase();
  try {
   //只接受post
   if (!rq.getmethod().equalsignorecase("post")) {
    filterchain.dofilter(servletrequest, servletresponse);
    return;
   }
 
   string token = rq.getheader("token");
   if (stringutils.isempty(token)) {
    rpbase.setmsg("无token");
    return;
   }
 
   //jwt验证
   mouser mouser = jwtutil.getvalbyt(token, webconfig.token_encrykey, webconfig.login_user, mouser.class);
   if (mouser == null) {
    rpbase.setmsg("token已失效");
    return;
   }
 
   system.out.println("token用户:" + mouser.getnickname());
 
   filterchain.dofilter(servletrequest, servletresponse);
  } catch (exception ex) {
  } finally {
   if (!stringutils.isempty(rpbase.getmsg())) {
    rp.setcharacterencoding("utf-8");
    rpbase.setcode(httpstatus.bad_request.value());
    rp.getwriter().write(json.tojsonstring(rpbase));
   }
  }
 }
}

要是自定义过滤器authenfilter生效,还需要把她注册到容器中,这里通过编码方式,当然还可以通过@webfilter注解来加入到容器中:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@configuration
public class webfilterconfig {
 
 @bean
 public filterregistrationbean setfilter() {
 
  filterregistrationbean registrationbean = new filterregistrationbean();
  registrationbean.setfilter(new authenfilter());
  registrationbean.addurlpatterns("/api/*");
  registrationbean.setorder(filterregistrationbean.lowest_precedence);
 
  return registrationbean;
 }
}

注意addurlpatterns匹配的是过滤器作用的url连接,根据需求而定;为了验证效果,这里我创建了两个接口gettoken和t0,分别是获取token和post查询接口,代码如是:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@restcontroller
public class testcontroller {
 
 @postmapping("/api/t0")
 public string t0() throws myexception {
 
  return uuid.randomuuid().tostring();
 }
 
 @getmapping("/token/{username}")
 public string gettoken(@pathvariable string username) {
 
  mouser mouser = new mouser();
  mouser.setusername(username);
  mouser.setnickname(username);
 
  map<string, object> map = new hashmap<>();
  map.put(webconfig.login_user, mouser);
 
  return jwtutil.gettokenbyjson(map,
    webconfig.token_encrykey,
    webconfig.token_secondtimeout);
 }
}

最终要获通过head传递token值来访问t01接口,得到如下结果:

spring boot+jwt实现api的token认证详解

token在有效时间后访问直接失败,从新获取token并访问t01接口,得到成功的信息:

spring boot+jwt实现api的token认证详解

git地址: https://github.com/shenniubuxing3

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:https://www.cnblogs.com/wangrudong003/p/10122706.html

延伸 · 阅读

精彩推荐