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


    目录

    1、JWT的结构

    2、使用JWT

    3、封装工具类

    3.1、引入依赖

    3.2、生成token

    3.3、解析token

    3.4、封装工具类

    4、整合pringboot


    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、引入依赖

    1. <!--引入jwt-->
    2. <dependency>
    3. <groupId>com.auth0</groupId>
    4. <artifactId>java-jwt</artifactId>
    5. <version>3.4.0</version>
    6. </dependency>

    3.2、生成token

    1. @Test
    2. public void makeToken(){
    3. //1、日历类
    4. Calendar calendar=Calendar.getInstance();
    5. //2、设置时间为 1小时过期
    6. calendar.add(Calendar.SECOND,60);
    7. Map<String,Object> map=new HashMap<>();
    8. //3、生成令牌
    9. String token = JWT.create()
    10. .withHeader(map)//第一部分 头使用默认值 签名算法:HS256 type:jwt
    11. .withClaim("username", "rk")//第二部分
    12. .withClaim("age", 20)
    13. .withIssuedAt(new Date(System.currentTimeMillis()))//设置令牌发放时间
    14. .withExpiresAt(calendar.getTime())//设置过期时间
    15. .sign(Algorithm.HMAC256("LUOLIN!@$%#@"));//第三部分 设置签名,LUOLIN!@$%#@为秘钥
    16. //4、输出令牌
    17. System.out.println(token);
    18. }

    3.3、解析token

    1. //解析token
    2. @Test
    3. public void parseToken(){
    4. try {
    5. String token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTYzNzk5OTAsImlhdCI6MTY1NjM3OTkzMCwiYWdlIjoyMCwidXNlcm5hbWUiOiJyayJ9.-Pcm-dox8IG2ZT_gve7ApVHCOvM1M9mHDicgDBm8H0k";
    6. //1、创建验证对象
    7. JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("LUOLIN!@$%#@")).build();
    8. //2、校验签名(校验token是否合法)
    9. DecodedJWT jwt = jwtVerifier.verify(token);
    10. System.out.println("用户名:"+jwt.getClaim("username").asString());
    11. System.out.println("年龄:"+jwt.getClaim("age").asInt());
    12. SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    13. System.out.println("令牌发放时间:"+sdf.format(jwt.getIssuedAt()));
    14. System.out.println("令牌过期时间:"+sdf.format(jwt.getExpiresAt()));
    15. }
    16. catch (TokenExpiredException e){
    17. System.out.println("token已过期!");
    18. }
    19. catch (JWTVerificationException e) {
    20. System.out.println("token不合法!");
    21. }
    22. }

    常见异常:

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

    3.4、封装工具类

    1. public class JWTUtils {
    2. private static String TOKEN = "token!Q@W3e4r";
    3. /**
    4. * 生成token
    5. * @param map //传入payload
    6. * @return 返回token
    7. */
    8. public static String getToken(Map<String,String> map){
    9. JWTCreator.Builder builder = JWT.create();
    10. map.forEach((k,v)->{
    11. builder.withClaim(k,v);
    12. });
    13. Calendar instance = Calendar.getInstance();
    14. instance.add(Calendar.SECOND,7);
    15. builder.withExpiresAt(instance.getTime());
    16. return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
    17. }
    18. /**
    19. * 验证token
    20. * @param token
    21. * @return
    22. */
    23. public static void verify(String token){
    24. JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
    25. }
    26. /**
    27. * 获取token中payload
    28. * @param token
    29. * @return
    30. */
    31. public static DecodedJWT getToken(String token){
    32. return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
    33. }
    34. }

    4、整合pringboot

    登录Controller:

    1. @GetMapping("/user/login")
    2. public Map<String,Object> login(User user){
    3. Map<String, Object> map = new HashMap<>();
    4. try{
    5. //通过用户名和密码查找用户
    6. User userDB = userService.login(user);
    7. Map<String,String> payload = new HashMap<>();
    8. payload.put("id",userDB.getId());
    9. payload.put("name",userDB.getName());
    10. //生成JWT的令牌
    11. String token = JWTUtils.getToken(payload);
    12. //返回值赋值
    13. map.put("state",true);
    14. map.put("msg","登录成功!");
    15. map.put("token",token);//响应token
    16. }catch (Exception e){
    17. map.put("state",false);
    18. map.put("msg",e.getMessage());
    19. }
    20. return map;
    21. }

    登录Service:

    1. @Autowired
    2. private UserDAO userDAO;
    3. @Override
    4. @Transactional(propagation = Propagation.SUPPORTS)
    5. public User login(User user) {
    6. //根据接收用户名密码查询数据库
    7. User userDB = userDAO.login(user);
    8. if(userDB!=null){
    9. return userDB;
    10. }
    11. throw new RuntimeException("登录失败");
    12. }

    拦截器:

    拦截器配置:

    1. @Configuration
    2. public class InterceptorConfig implements WebMvcConfigurer {
    3. @Override
    4. public void addInterceptors(InterceptorRegistry registry) {
    5. registry.addInterceptor(new JWTInterceptor())//将jwt拦截器添加进去
    6. .excludePathPatterns("/user/login")//放行登录接口
    7. .addPathPatterns("/**"); //拦截接口
    8. }
    9. }
    1. public class JWTInterceptor implements HandlerInterceptor {
    2. @Override
    3. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    4. //拦截处理
    5. Map<String, Object> map = new HashMap<>();
    6. //获取请求头中令牌
    7. String token = request.getHeader("token");
    8. try {
    9. JWTUtils.verify(token);//验证令牌
    10. return true;//放行请求
    11. } catch (SignatureVerificationException e) {
    12. e.printStackTrace();
    13. map.put("msg","无效签名!");
    14. }catch (TokenExpiredException e){
    15. e.printStackTrace();
    16. map.put("msg","token过期!");
    17. }catch (AlgorithmMismatchException e){
    18. e.printStackTrace();
    19. map.put("msg","token算法不一致!");
    20. }catch (Exception e){
    21. e.printStackTrace();
    22. map.put("msg","token无效!!");
    23. }
    24. map.put("state",false);//设置状态
    25. //将map 转化为json jackson
    26. String json = new ObjectMapper().writeValueAsString(map);
    27. response.setContentType("application/json;charset=UTF-8");
    28. response.getWriter().println(json);
    29. return false;
    30. }
    31. }

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

    测试接口:

    1. @PostMapping("/user/test")
    2. public Map<String,Object> test(HttpServletRequest request){
    3. Map<String, Object> map = new HashMap<>();
    4. //处理自己业务逻辑
    5. String token = request.getHeader("token");
    6. DecodedJWT verify = JWTUtils.verify(token);
    7. log.info("用户id: [{}]",verify.getClaim("id").asString());
    8. log.info("用户name: [{}]",verify.getClaim("name").asString());
    9. map.put("state",true);
    10. map.put("msg","请求成功!");
    11. return map;
    12. }

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

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

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

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

    5、前端页面解析token

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

  • 相关阅读:
    三、Spring Boot 整合视图层技术
    【Java 进阶篇】Java Servlet URL Patterns 详解
    零基础学python之流程控制
    计算机毕业设计基于springboot+vue+elementUI的网吧管理系统(源码+系统+mysql数据库+Lw文档)
    WSDM‘22推荐系统论文梳理
    Leetcode日练笔记41 [二叉树recursion专题] #250 Count Univalue Subtrees /Medium {Python}
    socket简介
    CentOS7 Hadoop3.3.0 安装与配置
    vue点击按钮复制输入框内容
    关于重构的基本步骤与注意事项
  • 原文地址:https://blog.csdn.net/m0_46979453/article/details/125490678