JWT学习进阶


JWT入门学习

JWT

简介

JSON Web 令牌

官网网址:https://jwt.io

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输.在数据传输过程中还可以完成数据加密、签名等相关处理

基于传统的Session认证

HTTP协议是一种无状态的协议,因此每次请求时,要再一次的进行用户认证.为了识别是那个用户发出的请求,采用的是Session-cookie的机制.在服务器中存储用户登录信息,保存在session中,同时将登陆信息响应给浏览器,保存为cookie,以便下次请求再次发送,用于识别身份信息.

暴露的问题

  1. 由于session都是保存在内存中,随着认证用户的增多,服务端的开销会明显增大
  2. 用户认证之后,服务端做认证记录,意味着用户下次请求必须在这台服务器上,才可以拿到授权资源,这样在分布式的应用上,相应的限制了负载均衡的能力,限制了应用的扩展能力.需要实现session共享机制
  3. 基于cookie来进行用户识别,容易被截获,容易受到跨站请求伪造的攻击(CSRF)

基于JWT认证

image-20211125212133101

认证流程

  1. 前端将用户名和密码发送给后端
  2. 后端核对用户名和密码成功后,将用户ID等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token),形成的JWT就是一个形同lll.zzz.xxx的字符串
  3. 后端将JTW字符串作为登陆成功的返回结果返回给前端.前端可以将返回的结果保存在localStoragesessionStorage上,退出登陆时前端删除保存的JWT即可
  4. 前端在每次请求时将JWT放入HTTP Header中的Authorization位.(解决XSS和XSRF问题)
  5. 后端检查是否存在,如存在验证JWT的有效性.例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)
  6. 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果.

优势

  • 简洁(Compact):可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
  • 自包含(Self-contained): 负载中包含了所有用户所需要的信息,避免了多次查询数据库
  • 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持
  • 不需要在服务端保存会话信息,特别适用于分布式微服务

JWT结构

token string =====> header.payload.signature		 token

##令牌组成
1、标头(Header)
2、有效载荷(Payload)
3、签名(Signature)
JWT通常如下所示: xxxx.yyyyy.zzzzz 即Header.Payload.Signature




#Header
-标头通常由两部分组成: 令牌的类型(JWT)和所使用的签名算法,例如HMAC SHA256RSA. 它会使用Base64编码组成JWT结构的第一部分
-注意: Base64是一种编码,也就是说,它是可以被翻译回原来的样子,并不是一种加密过程.
{
  "alg": "SHA256",
  "typ": "JWT"
}


###Payload 不要放敏感信息
-令牌的第二部分是有效负载,其中包含声明. 声明是有关实体(通常是用户)和其他数据的声明,同样的,它会使用Base64编码组成JWT结构的第二部分

{
  "sub": "123456789",
  "name": "John Doe",
  "admin": true
}

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

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


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

使用JWT

<dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.18.2</version>
        </dependency>

生成token

@Test
    void contextLoads() {
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND, 90);
        //生成令牌 链式调用
        String token = JWT.create()
                //payload
                .withClaim("username", "张三")
                .withClaim("username", 1)
                //设置过期时间
                .withExpiresAt(instance.getTime())
                //设置签名 保密 复杂
                .sign(Algorithm.HMAC256("token@HDLSDJDKF"));
        System.out.println("输出令牌:" + token);
    }

输出令牌:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzgwNjQ2MTAsInVzZXJuYW1lIjoxfQ.-nUfEvCRcc_03dBjJp2EvuP93IL2Ns-ReParKO1u3G8

验签对象

 @Test
    void checkJWT(){
        //构建验签对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token@HDLSDJDKF")).build();
        DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzgwNjUxOTYsInVzZXJJRCI6MSwidXNlcm5hbWUiOiLlvKDkuIkifQ.gNbzYHZsOMyd8rEGyPtTImRFnTQb6dhOa5jMzZq_lL0");
        Claim username = verify.getClaim("username");
        Claim userID = verify.getClaim("userID");
        //注意类型问题,否则拿不到对应的数据为null
        System.out.println(userID.asInt());
        System.out.println(username.asString());
    }

常见异常信息

-SignatureVerificationException: 签名不一致异常

-TokenExpireException: 令牌过期异常

-AlgorithmMismatchException: 算法不匹配异常

-InvalidClaimException: 失效的payload异常

封装工具类

public class JwtUtils {
    private static final String SECRECY = "luxiaobai@lsy";

    /**
     * 生成token
     * @param map 传入payload
     */
    public static String getToken(Map<String, String> map){
        JWTCreator.Builder builder = JWT.create();
        map.forEach(builder::withClaim);
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND, 90);
        builder.withExpiresAt(instance.getTime());
        return builder.sign(Algorithm.HMAC256(SECRECY));
    }

    /**
     * 验证token
     */
    public static void checkToken(String token){
       JWT.require(Algorithm.HMAC256(SECRECY)).build().verify(token);
    }

    /**
     * 获取token中的payload
     */
    public static DecodedJWT getPayload(String token){
        return JWT.require(Algorithm.HMAC256(SECRECY)).build().verify(token);
    }
}

Springboot整合JWT

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.18.2</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.23</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>
server.port=8999

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/jwt?characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=Sal12345

mybatis.type-aliases-package=com.luxiaobai.entity
mybatis.mapper-locations=classpath:com/luxiaobai/mapper/*.xml

logging.level.com.luxiaobai.dao=debug

主要代码

JwtInterceptor
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头中的令牌
        String token = request.getHeader("token");
        Map<String, Object> map = new HashMap<>();
        try {
            //验证令牌
            JwtUtils.getPayload(token);
            return true;
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            map.put("msg", "token过期");
        } catch (AlgorithmMismatchException e) {
            e.printStackTrace();
            map.put("msg", "算法不一致!");
        } catch (Exception e) {
            e.printStackTrace();
            map.put("msg", "token无效");
        }
        map.put("state", false);
        //将map转换为json jackson
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
        return false;

    }
}

Config
@Component
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())
                .addPathPatterns("/user/test")
                .excludePathPatterns("/user/login");
    }
}

Controller
@RestController
@Slf4j
public class TestController {

    @Autowired
    private UserServiceImpl userService;

    @GetMapping("/user/login")
    public Map<String, Object> login(User user) {
        log.info(("用户名[{}]" + user.getName()));
        log.info("密码[{}]" + user.getPassword());
        Map<String, Object> map = new HashMap<>();
        try {
            User userDb = userService.login(user);
            //生成令牌
            Map<String, String> payload = new HashMap<>();
            payload.put("userId", userDb.getId());
            payload.put("userName", userDb.getName());
            String token = JwtUtils.getToken(payload);
            map.put("token", token);
            map.put("state", true);
            map.put("msg", "认证成功");
        } catch (Exception e) {
            map.put("state", false);
            map.put("msg", e.getMessage());
        }
        return map;
    }

    @PostMapping("/user/test")
    public Map<String, Object> test(HttpServletRequest request) {
        String token = request.getHeader("token");
        log.info("token:[{}]", token);
        DecodedJWT payload = JwtUtils.getPayload(token);
        log.info("用户ID:[{}]", payload.getClaim("userId"));
        log.info("用户名:[{}]", payload.getClaim("userName"));
        Map<String, Object> map = new HashMap<>();
        //转换为拦截器  实际业务逻辑
//        try {
//            //验证令牌
//            DecodedJWT payload = JwtUtils.getPayload(token);
//            map.put("state", true);
//            map.put("msg", "认证成功");
//            return map;
//        } catch (TokenExpiredException e) {
//            e.printStackTrace();
//            map.put("msg", "token过期");
//        } catch (AlgorithmMismatchException e) {
//            e.printStackTrace();
//            map.put("msg", "算法不一致!");
//        } catch (Exception e) {
//            e.printStackTrace();
//            map.put("msg", "token无效");
//        }
        map.put("state", true);
        return map;
    }
}

源代码

[1]:https://github.com/luxiaobai007/JWT.git JWT源代码及学习笔记

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Boot进阶包括以下几个方面: 1. 深入理解Spring Boot:了解Spring Boot的核心原理和机制,包括自动配置、起步依赖、条件注解等。可以通过阅读官方文档、参考书籍以及查阅源码来进行学习。 2. 定制化配置:掌握如何通过自定义配置文件、属性注入、配置类等方式来定制化配置Spring Boot应用。例如,可以通过@ConfigurationProperties注解来读取外部的配置文件并注入到Bean中。 3. 微服务与云原生:学习如何使用Spring Boot构建微服务架构,并结合Docker、Kubernetes等云原生技术进行部署和管理。了解服务注册与发现、负载均衡、断路器等相关概念和工具,例如使用Spring Cloud、Netflix OSS等框架。 4. 性能优化与监控:掌握Spring Boot应用的性能优化技巧,包括数据库连接池的配置、缓存技术的选择和使用、异步编程等。此外,了解如何使用监控工具和指标来对Spring Boot应用进行监控和调优。 5. 安全与认证:学习如何在Spring Boot应用中添加安全机制,保护应用和用户的数据安全。可以使用Spring Security框架来进行认证和授权,同时还可以了解其他的安全相关技术,如OAuth2、JWT等。 6. 集成测试与持续集成:了解如何编写集成测试,并结合持续集成工具进行自动化测试和部署。可以使用工具如JUnit、Mockito、Jenkins等来实现自动化测试和持续集成的流程。 希望以上内容对你的Spring Boot进阶有所帮助!如有更多问题,请继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路小白&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值