Java架构师方案—基于JWT的Token认证(附完整项目代码)
导读
- 从这篇文章,读者能完全掌握JWT认证完整机制,跟着笔者的教程式文章以及DEMO源码一起试试。
- 笔者介绍了JWT核心逻辑,数据结构,优劣对比分析,使用场景。
- 就算现在不想学jwt,收藏起来,待需要的时候拿来即用。
1. JWT鉴权、session认证
谈论两者区别之前,我们先认识下认证、鉴权的定义。
Authentication/认证
几乎所有的系统都会需要用户登入来做进一步操作,像淘宝、京东电商系统的商品浏览、评论查看等功能是不需要用户登入的,但是如果用户下单、购买支付等功能就需要用户登入。因此在所有软件系统中,功能可区分为要求登入的操作和不要求登入的操作。
系统操作分类:需要用户登入的操作、不需要用户登入的操作。
用户登入、输入用户名和密码确认登入的过程就是认证。
Authorization/鉴权
鉴权是指在用户登入认证之后,验证用户是否拥有访问系统的权利。传统的鉴权是通过密码来验证的。这种方式的前提是,每个获得密码的用户都已经被授权。如果没有鉴权逻辑,那么每次用户使用一些需要用户登入的功能时,都要做一次登入认证操作。这样的体验是很糟糕的。
当用户点击一些功能或进行操作时候,系统会对用户进行校验权限,如果用户权限token无效,则用户就不能做任何事。
基于token认证和session认证
这是目前主流的两种认证方式,下面来看看相关逻辑过程。
session认证
基于session认证流程如下图所示:
流程分析:
1)用户在登入页面,输入用户名/密码等登入信息。
2)服务器后端验证登入信息是否正确,登入成功后创建一个sessionid,这个id具有唯一性,放入到cookie或请求header中。
3)用户后续的请求都会携带sessionid数据,服务端会验证sessionid的有效性,选择是否接受请求。
4)用户退出登入时候,服务端会删除内存中的session信息,sessionid失效,后续的请求将不再被接受。
问题:用户登入后,session信息会保存在服务器内存中,sessionid会保存在请求中,sessionid在服务器端生成的所有session中具有唯一性。如果sessinid被盗用,那么其他用户也能不用登入就能访问 需要登入的网页。
解决方案:
1)cookie设置HttpOnly属性,js脚本就无法读取到cookie信息,能有效的防止XSS攻击,窃取cookie内容,增加安全性。
2)不使用固定sessionid,每次登入都是另外一个唯一性的sessinid值,设置过期时间,因此就算sessionid值泄露,系统也会防住攻击。
token认证
token认证,服务器内存不会存放session信息。token数据将承载着用户登入状态信息。
流程图如下:
1)用户输入用户名/密码登入信息。
2)服务器验证登入信息,并返回jwt签名token。
3)jwt签名token存储在客户端的cookie或local storage中。
4)后续的请求都将携带token信息。
5)服务器解码token,验证token有效性,判断是否接受请求。
6)用户退出登入,客户端删除token,不会请求服务器,因为服务器没有存储token以及任何用户登入的状态信息。
2. 什么是JWT
一张图来快速了解jwt token的结构
运行demo项目:jackdking-login-jwt-token。登入后查看页面cookie信息如下所示:
Bearer+eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTYwMzQ2NzcwNywiZXhwIjoxNjAzNDcxMzA3fQ.LeuTshSD_mwD7xG9ZunAbyGNPaHwhAL7cD5Z5i2qPYc
jwt token数据可从浏览器cookie中查看到。
JWT Token结构token串由三个信息文本拼接成的,连接的符合是“.“,那这三个信息文本又是如何生成的、有什么样的意义?
这三个部分的信息文本分别是header(头部)、playload(载荷)、signature(验签),都是json数据。
headerjwt头部包括两部分信息:
- token类型
- 加密算法,通常使用 HMAC SHA256。
完整的json数据如下所示:
{
'typ': 'JWT',
'alg': 'HS256'
}
虽然我们使用JWT工具类生成jwt token,不需要自己去手动创建header数据,但是还是需要了解header信息文本的内容结构的。header的信息文本就是通过对这个json字符串进行base64加密得到的(属于对称加密)。
playload这一部分,除了jwt标准定义的属性,我们还可以放入用户的信息,但是因为这个信息文本是对称加密的,所以不建议放入敏感的数据,例如用户密码,身份证等,防止信息的泄露。载荷部分的信息分为三个部分,如下所示:
- 标准中注册的声明字段
- 私有的数据字段
- 公共的声明
标准字段:JWT的标准所定义的字段如下
iss: 该JWT的签发者
sub: 该JWT所面向的用户
aud: 接收该JWT的一方
exp(expires): 什么时候过期,这里是一个Unix时间戳
iat(issued at): 在什么时候签发的
jti(jwt identify): jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
nbf: 定义在什么时间之前,该jwt都是不可用的.
公共字段:公共的部分存放用户信息,或者业务相关的数据,但是这部分是明文,在浏览器即可查看到,建议不要存放敏感的信息。
私有字段:这部分的数据是会进行base64加密的,虽然不是明文信息,在浏览器不能直接查看,但是base64是对称加密方式,解密后还是能看到数据,因此也不要存放敏感信息。
总之,jwt的token长串里不要存放敏感的用户信息或业务信息。
signature/签名这部分是个最重要的地方,这部分决定了这个jwt token串是否有效。下面就来看看签名的生成逻辑。
jwt的第三部分是由base64加密后的串拼接而成,拼接符号是“.“,拼接后的字符串:header.payload。然后再将拼接后的串和jwt的秘钥一起进行组合加密,加密算法通常是HMAC-SHA256,这个是由header部分的alg字段声明决定的。这样就得到了第三部分文本信息。
到这里,jwt token长串的三个部分的文本信息就全部生成了,通过符号“.“拼接在一起就形成了jwt token。
但是我们还需要提醒开发者,第三部分生成签名的秘钥只能保存在服务端,不然就没办法保证jwt token的签发是由服务器完成的,如果存放在客户端泄露出去,那么任何人都能签发jwt token毫无阻碍的访问你的系统,这是极其危险的。
现在对jwt token结构有了清楚的认识吧,下面就让我门开始快速开发吧。
3. 快速开发JWT应用
实现JWT的demo获取方式,请查看文章底部。
demo项目一览
登入和退出接口
@PostMapping(value = "loginCheck")
@ResponseBody
public RestResponseBo loginCheck(@RequestParam String username,
@RequestParam String password,
HttpServletRequest request,
HttpServletResponse response) {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)){
return RestResponseBo.fail("用户名或者密码不能为空!");
}
if(!username.equals("admin") || !password.equals("admin"))
{
return RestResponseBo.fail("用户名或者密码不正确!");
}
String jwtToken = jwtTokenProvider.createToken(username);
//用户浏览器会存放两种cookie: userToken,userId。
jwtTokenProvider.saveJwtToken(jwtToken);
return RestResponseBo.ok();
}
@PostMapping(value = "loginOut")
@ResponseBody
public RestResponseBo loginOut() {
jwtTokenProvider.removeJwtToken();
return RestResponseBo.ok();
}
登入接口逻辑
1.获取username、password数据,并校验正确性。
2.如果校验通过,则根据username生成 jwt token,调用jwtTokenProvider.createToken(username);
3.成功生成token后,将token值放入到请求体request的cookie中,保存在浏览器。
退出逻辑
jwt退出逻辑,这里选择了服务器后端来删除cookie,但是其实删除cookie一般是放在用户客户端的。demo项目操作cookie都是在后端。
token存储在cookie
存储的cookie结构如代码所示。后续的请求中都会包含这个cookie。
public void saveJwtToken(String jwtToken) {
// TODO Auto-generated method stub
CookieUtil.addCookie("Authorization", "Bearer "+jwtToken);
}
拦截器
拦截器JdkApiInterceptor,会拦截除了登入接口和登入页面url外的所有接口。拦截代码逻辑如下。
1.使用jwt工具类jwtTokenProvider来获取cookie中的token串。
2.如果token为空,则拒绝请求并跳转到登入页面。
3.token不为空,则使用token是否有效的校验工具进行检测jwtTokenProvider.validateToken,无效就跳转到登入页面,有效则继续响应请求。
/**
* 拦截请求,在controller调用之前
* 返回 false:请求被拦截,返回
* 返回 true :请求OK,可以继续执行,放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception {
String jwtToken = jwtTokenProvider.resolveTokenFromCookie();//获取用户cookies 中的jwttoken
logger.info("解析得到的token值:{}",jwtToken);
//放开登入接口
// String uri = request.getRequestURI();
// logger.info("请求uri:" + uri);
//
// if(uri.equals("loginCheck"))
// return true;
//
//用户id和token都不为空
if (!StringUtils.isEmpty(jwtToken)) {
//根据userid生成唯一key从redis中查出唯一token
//如果唯一token为空 ,则拦截url重定向到登入页面
if (!jwtTokenProvider.validateToken(jwtToken)) {
response.sendRedirect("/login");
returnErrorResponse(response, "请登录...");
return false;
}
//用户id和token有一个为空,则重定向登入页面
} else {
response.sendRedirect("/login");
returnErrorResponse(response,"请登录...");
return false;
}
return true;
}
运行demo并查看效果
浏览器访问http://localhost:8080/login。
输入登入用户名/密码:admin/admin,登入成功跳转到index页面。
查看页面中的token cookie
可以看到,cookie中已经存放了jwt token串。
Bearer+eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTYwMzYwNDAxNywiZXhwIjoxNjAzNjA3NjE3fQ.IB4LFRV5haQE2oec7PLXFsFPqUbpnH1Du0kDqvos-cE
至此,jwt项目的开发就完成了,demo项目可直接运行。
4. JWT的优点和缺点分析
这里简单谈谈jwt鉴权的优缺点。
优点:
- 服务端不需要保存token,那么大量用户在线情况下,服务器端就不会像sessin认证那样那么大的压力,非常好扩展。
- 退出逻辑这块,jwt模式的退出不与服务器交互,客户端删除token的cookie即可。
- 支持网站,app,小程序等多种用户端的应用,对平台友好。
- jwt token串中载荷部分可以承载不敏感的用户数据和业务数据。
- jwt结构也非常简单,数据的字节占用还是比较少的,传输快。
缺点
- jwt的三个部分,信息承载的方式是明文和对称加密两种,不适合传输敏感信息。
- 比起sessionId,token串的数据量还是非常多的,传输时间比较慢。
- jwt token无法废弃。因为在服务器端是无状态的,不保存token信息,因此服务器端无法弃用。
- jwt token的有效期无法续签(一次性的),需要重新签发新的jwt。不像session能自动刷新有效期时间。
- 如果像更新jwt中的载荷中信息,那么就需要添加jwt的失效逻辑,就需要在服务器这边保存jwt,类似黑名单逻辑。
jwt适合的应用场景
- 只使用一次的场景:如一些验证邮件。
- 有效期短的场景。
- jwt不适合单点登入和会话管理的场景,因为这些场景需要在服务器侧保存jwt,还不如使用成熟框架比较多的session框架。
其实token认证的应用非常广泛,像Twitter、微信、QQ、GitHub的公共服务API都是使用token认证。但有些大型网站架构中会选择session+token结合一起。微服务架构网站的认证逻辑非常复杂,后续我们团队会继续输出相关联的架构方案文章,并附上完整的demo项目。请关注我们团队。
完整的demo项目,请关注公众号“前沿科技bot“并发送"jwt"获取。
- 本文标签: Spring Boot Java架构师方案
- 版权声明: 本站原创文章,于2020年10月25日由空白发布,转载请注明出处