评论

收藏

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

编程语言 编程语言 发布于:2021-10-06 15:39 | 阅读数:297 | 评论:0

这篇文章主要给大家介绍了关于spring boot+jwt实现api的token认证的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一学习学习吧
前言
本篇和大家分享jwt(json web token)的使用,她主要用来生成接口访问的token和验证,其单独结合springboot来开发api接口token验证很是方便,由于jwt的token中存储有用户的信息并且有加密,所以适用于分布式,这样直接吧信息存储在用户本地减速了服务端存储sessiion或token的压力;
如下快速使用:
<!--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:
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值:
eyjhbgcioijiuzi1niisinppcci6ikdasvaifq.h4siaaaaaaaaafwmtq7cibse7_lwkpdzaesp4qnyinciptx4ine0vbtg4sllfppn7hatgwbwg1bkl4grcbeciwpujzf8iiepjxfapaag2reypuecr2vxyed13nb0pplw3hp1eenblqsquiffy0ohurl3i70evweu_afsejzhd7dlcdv5ntmyhuilhtd3rf_hacchrtv--7yaaaa.i4xwoqtawi0-dwhwn8uz4dbm-vfli5bavyu9lryxu5e
验证token是否有效
token生成的时都会伴随者有一个失效的时间,在这我们可以通过setexpiration函数设置过期时间,记住jwt的有效时间不是滑动的,也就是说不做任何处理时,当到达第一次设置的失效时间时,就基本没用了,要获取token是否过期可以使用如下方式:
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中的用户信息,我们需要这样做:
return jwts.parser()
  .setsigningkey(encrykey)
  .parseclaimsjws(token)
  .getbody();
此时body获取出来是claims类型,我们需要从中获取到用户信息,需要注意的是在addclaims存储信息的时候如果存储的map值没做过出来,那完整的实体对象存储进去后会映射成一个linkhasmap类型,如下:
DSC0000.png

因此通常会在存储的时候json化,如下代码:
claimmaps.foreach((key, val) -> {
 claimmaps.put(key, json.tojsonstring(val));
});
再来就是通过get方法获取我们存储进去的信息,并json反序列化:
/**
* 获取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代码基本就完成了,下面给出完整的代码例子,仅供参考:
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做验证:
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注解来加入到容器中:
@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查询接口,代码如是:
@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接口,得到如下结果:
DSC0001.png

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

git地址: https://github.com/shenniubuxing3
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对CodeAE代码之家的支持。
原文链接:https://www.cnblogs.com/wangrudong003/p/10122706.html

关注下面的标签,发现更多相似文章