热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JWT原理和整合Springboot实现登录认证

目录1、JWT的结构2、使用JWT3、封装工具类3.1、引入依赖3.2、生成token3.3、解析token3.4、封装工具类4、整合pringboot5、前端页

目录

1、JWT的结构

2、使用JWT

3、封装工具类

3.1、引入依赖

3.2、生成token

3.3、解析token

3.4、封装工具类

4、整合pringboot

5、前端页面解析token



1、JWT的结构

        JWT 最后的形式就是个字符串,它由头部、载荷签名这三部分组成,中间以「.」分隔。像下面这样:

 Header:
        标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。它会使用 Base64 编码组成 JWT 结构的第一部分。

        注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

Payload:
        令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用 Base64 编码组成 JWT 结构的第二部分

Signature:
        前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过

如:
第三部分内容:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),私钥);

签名目的:
        最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

        如果JWT的前2部分的内容被修改了,服务器接收到token后,会先获取前两部分的内容+私钥加密后和token中的第三部分的签名进行对比。如果一致就是合法的

信息安全问题:

在这里大家一定会问一个问题:Base64是一种编码,是可逆的,那么我的信息不就被暴露了吗?

        是的。所以,在JWT中,不应该在负载里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏    感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第    三方通过Base64解码就能很快地知道你的密码了。因此JWT适合用于向Web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。

2、使用JWT

        1、在用户登录网站的时候,需要输入用户名、密码或者短信验证的方式登录,登录请求到达服务端的时候,服务端对账号、密码进行验证,然后计算出 JWT 字符串,返回给客户端。

        2、客户端拿到这个 JWT 字符串后,存储到 COOKIE 或者 浏览器的 LocalStorage 中。

        3、再次发送请求,比如请求用户设置页面的时候,在 HTTP 请求头中加入 JWT 字符串,或者直接放到请求主体中。

        4、服务端拿到这串 JWT 字符串后,使用 base64的头部和 base64 的载荷部分加上私钥,通过HMACSHA256算法计算签名部分,比较计算结果和传来的签名部分是否一致,如果一致,说明此次请求没有问题,如果不一致,说明请求过期或者是非法请求。

        保证安全性的关键就是 私钥和HMACSHA256 或者与它同类型的加密算法,因为加密过程是不可逆的,所以不能根据传到前端的 JWT 传反解到密钥信息。另外,不同的头部和载荷加密之后得到的签名都是不同的,所以,如果有人改了载荷部分的信息,那最后加密出的结果肯定就和改之前的不一样的,所以,最后验证的结果就是不合法的请求。

私钥如果不小心泄露会怎么样?

        就算私钥泄露了,强盗也无法修改token携带的信息。如果修改,后台还是无法校验通过。如果不做更改,直接用呢,那就没有办法了,为了更大程度上防止被强盗盗取,应该使用 HTTPS 协议而不是 HTTP 协议,这样可以有效的防止一些中间劫持攻击行为。

3、封装工具类


3.1、引入依赖


com.auth0java-jwt3.4.0

3.2、生成token

&#64;Testpublic void makeToken(){//1、日历类Calendar calendar&#61;Calendar.getInstance();//2、设置时间为 1小时过期calendar.add(Calendar.SECOND,60);Map map&#61;new HashMap<>();//3、生成令牌String token &#61; JWT.create().withHeader(map)//第一部分 头使用默认值 签名算法:HS256 type:jwt.withClaim("username", "rk")//第二部分.withClaim("age", 20).withIssuedAt(new Date(System.currentTimeMillis()))//设置令牌发放时间.withExpiresAt(calendar.getTime())//设置过期时间.sign(Algorithm.HMAC256("LUOLIN!&#64;$%#&#64;"));//第三部分 设置签名&#xff0c;LUOLIN!&#64;$%#&#64;为秘钥//4、输出令牌System.out.println(token);}

3.3、解析token

//解析token&#64;Testpublic void parseToken(){try {String token&#61;"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTYzNzk5OTAsImlhdCI6MTY1NjM3OTkzMCwiYWdlIjoyMCwidXNlcm5hbWUiOiJyayJ9.-Pcm-dox8IG2ZT_gve7ApVHCOvM1M9mHDicgDBm8H0k";//1、创建验证对象JWTVerifier jwtVerifier &#61; JWT.require(Algorithm.HMAC256("LUOLIN!&#64;$%#&#64;")).build();//2、校验签名(校验token是否合法) DecodedJWT jwt &#61; jwtVerifier.verify(token);System.out.println("用户名:"&#43;jwt.getClaim("username").asString());System.out.println("年龄:"&#43;jwt.getClaim("age").asInt());SimpleDateFormat sdf&#61;new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");System.out.println("令牌发放时间:"&#43;sdf.format(jwt.getIssuedAt()));System.out.println("令牌过期时间:"&#43;sdf.format(jwt.getExpiresAt()));}catch (TokenExpiredException e){System.out.println("token已过期!");}catch (JWTVerificationException e) {System.out.println("token不合法!");}}

常见异常:

SignatureVerificationException:                签名不一致异常
TokenExpiredException:                            令牌过期异常
AlgorithmMismatchException:                        算法不匹配异常
InvalidClaimException:                                失效的payload异常


3.4、封装工具类

public class JWTUtils {private static String TOKEN &#61; "token!Q&#64;W3e4r";/*** 生成token* &#64;param map //传入payload* &#64;return 返回token*/public static String getToken(Map map){JWTCreator.Builder builder &#61; JWT.create();map.forEach((k,v)->{builder.withClaim(k,v);});Calendar instance &#61; Calendar.getInstance();instance.add(Calendar.SECOND,7);builder.withExpiresAt(instance.getTime());return builder.sign(Algorithm.HMAC256(TOKEN)).toString();}/*** 验证token* &#64;param token* &#64;return*/public static void verify(String token){JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);}/*** 获取token中payload* &#64;param token* &#64;return*/public static DecodedJWT getToken(String token){return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);}
}

4、整合pringboot

登录Controller:

&#64;GetMapping("/user/login")public Map login(User user){Map map &#61; new HashMap<>();try{//通过用户名和密码查找用户User userDB &#61; userService.login(user);Map payload &#61; new HashMap<>();payload.put("id",userDB.getId());payload.put("name",userDB.getName());//生成JWT的令牌String token &#61; JWTUtils.getToken(payload);//返回值赋值map.put("state",true);map.put("msg","登录成功!");map.put("token",token);//响应token}catch (Exception e){map.put("state",false);map.put("msg",e.getMessage());}return map;}

登录Service:

&#64;Autowiredprivate UserDAO userDAO;&#64;Override&#64;Transactional(propagation &#61; Propagation.SUPPORTS)public User login(User user) {//根据接收用户名密码查询数据库User userDB &#61; userDAO.login(user);if(userDB!&#61;null){return userDB;}throw new RuntimeException("登录失败");}

拦截器:

拦截器配置:

&#64;Configuration
public class InterceptorConfig implements WebMvcConfigurer {&#64;Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new JWTInterceptor())//将jwt拦截器添加进去.excludePathPatterns("/user/login")//放行登录接口.addPathPatterns("/**"); //拦截接口}
}

public class JWTInterceptor implements HandlerInterceptor {&#64;Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//拦截处理Map map &#61; new HashMap<>();//获取请求头中令牌String token &#61; request.getHeader("token");try {JWTUtils.verify(token);//验证令牌return true;//放行请求} catch (SignatureVerificationException e) {e.printStackTrace();map.put("msg","无效签名!");}catch (TokenExpiredException e){e.printStackTrace();map.put("msg","token过期!");}catch (AlgorithmMismatchException e){e.printStackTrace();map.put("msg","token算法不一致!");}catch (Exception e){e.printStackTrace();map.put("msg","token无效!!");}map.put("state",false);//设置状态//将map 转化为json jacksonString json &#61; new ObjectMapper().writeValueAsString(map);response.setContentType("application/json;charset&#61;UTF-8");response.getWriter().println(json);return false;}
}

        拦截器的作用主要就是校验token 是否合法&#xff0c;每次发起请求前都会先通过拦截器校验token。

测试接口:

&#64;PostMapping("/user/test")public Map test(HttpServletRequest request){Map map &#61; new HashMap<>();//处理自己业务逻辑String token &#61; request.getHeader("token");DecodedJWT verify &#61; JWTUtils.verify(token);log.info("用户id: [{}]",verify.getClaim("id").asString());log.info("用户name: [{}]",verify.getClaim("name").asString());map.put("state",true);map.put("msg","请求成功!");return map;}

        拦截器用来校验token&#xff0c;其他接口就只需要获取token后来解析其中的值&#xff0c;处理自己的业务逻辑就行了

               未登录的情况下访问其它接口会被拦截器拦截。

                登录成功后返回token给前端界面。

        登录成功后携带token继续访问其它接口&#xff0c;拦截器校验通过。test接口也获取解析出来了token中携带的值。

5、前端页面解析token

//jwt的token解码方式&#xff1a;
//首先拿到token码然后以点为分隔符转为数组
let token&#61;localStorage.getItem(‘token’).split(".");console.log(token);
//拿到第二段token也就是负载的那段 进行window.atob方法的 base64的解算&#xff0c;
然后再用decodeURIComponent字符串解码方法 解析出字符串 然后再转成JSON对象
由于atob()方法解码无法对中文解析 所以要再用escape()方法对其重新编码
然后再用decodeURI解码方式解析出来let str&#61;token[1]&#xff1b;
let user&#61;JSON.parse(decodeURIComponent(escape(window.atob(str))));
console.log(user.username);


推荐阅读
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • .NetCoreWebApi生成Swagger接口文档的使用方法
    本文介绍了使用.NetCoreWebApi生成Swagger接口文档的方法,并详细说明了Swagger的定义和功能。通过使用Swagger,可以实现接口和服务的可视化,方便测试人员进行接口测试。同时,还提供了Github链接和具体的步骤,包括创建WebApi工程、引入swagger的包、配置XML文档文件和跨域处理。通过本文,读者可以了解到如何使用Swagger生成接口文档,并加深对Swagger的理解。 ... [详细]
  • 现在比较流行使用静态网站生成器来搭建网站,博客产品着陆页微信转发页面等。但每次都需要对服务器进行配置,也是一个重复但繁琐的工作。使用DockerWeb,只需5分钟就能搭建一个基于D ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • 如何提高PHP编程技能及推荐高级教程
    本文介绍了如何提高PHP编程技能的方法,推荐了一些高级教程。学习任何一种编程语言都需要长期的坚持和不懈的努力,本文提醒读者要有足够的耐心和时间投入。通过实践操作学习,可以更好地理解和掌握PHP语言的特异性,特别是单引号和双引号的用法。同时,本文也指出了只走马观花看整体而不深入学习的学习方式无法真正掌握这门语言,建议读者要从整体来考虑局部,培养大局观。最后,本文提醒读者完成一个像模像样的网站需要付出更多的努力和实践。 ... [详细]
  • LVS实现负载均衡的原理LVS负载均衡负载均衡集群是LoadBalance集群。是一种将网络上的访问流量分布于各个节点,以降低服务器压力,更好的向客户端 ... [详细]
  • 2021最新总结网易/腾讯/CVTE/字节面经分享(附答案解析)
    本文分享作者在2021年面试网易、腾讯、CVTE和字节等大型互联网企业的经历和问题,包括稳定性设计、数据库优化、分布式锁的设计等内容。同时提供了大厂最新面试真题笔记,并附带答案解析。 ... [详细]
  • 本文分享了一位Android开发者多年来对于Android开发所需掌握的技能的笔记,包括架构师基础、高级UI开源框架、Android Framework开发、性能优化、音视频精编源码解析、Flutter学习进阶、微信小程序开发以及百大框架源码解读等方面的知识。文章强调了技术栈和布局的重要性,鼓励开发者做好学习规划和技术布局,以提升自己的竞争力和市场价值。 ... [详细]
  • {moduleinfo:{card_count:[{count_phone:1,count:1}],search_count:[{count_phone:4 ... [详细]
author-avatar
书友52745226
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有