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

目录

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 传反解到密钥信息。另外,不同的头部和载荷加密之后得到的签名都是不同的,所以,如果有人改了载荷部分的信息,那最后加密出的结果肯定就和改之前的不一样的,所以,最后验证的结果就是不合法的请求。

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

        如果单纯的依靠 JSON Web Token 解决用户认证的所有问题,那么系统的安全性将是脆弱的。由于 JWT令牌存储于客户端中,一旦客户端存储的令牌发生泄露事件或者被攻击,攻击者就可以轻而易举的伪造用户身份去修改/删除系统资源,按 JWT 自带过期时间,但在过期之前,攻击者可以肆无忌惮的操作系统数据。通过算法来校验用户身份合法性是 JWT 的优势,同时也是最大的弊端——它太过于依赖算法。

        为了更大程度上防止被强盗盗取,应该使用 HTTPS 协议而不是 HTTP 协议,这样可以有效的防止一些中间劫持攻击行为。

3、封装工具类

3.1、引入依赖

<!--引入jwt-->
<dependency>
  <groupId>com.auth0</groupId>
  <artifactId>java-jwt</artifactId>
  <version>3.4.0</version>
</dependency>

3.2、生成token

  @Test
    public void makeToken(){
        //1、日历类
        Calendar calendar=Calendar.getInstance();
        //2、设置时间为 1小时过期
        calendar.add(Calendar.SECOND,60);
        Map<String,Object> map=new HashMap<>();
        //3、生成令牌
        String token = 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!@$%#@"));//第三部分 设置签名,LUOLIN!@$%#@为秘钥
        //4、输出令牌
        System.out.println(token);
    }

3.3、解析token

 //解析token
    @Test
    public void parseToken(){
        try {
            String token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTYzNzk5OTAsImlhdCI6MTY1NjM3OTkzMCwiYWdlIjoyMCwidXNlcm5hbWUiOiJyayJ9.-Pcm-dox8IG2ZT_gve7ApVHCOvM1M9mHDicgDBm8H0k";
            //1、创建验证对象
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("LUOLIN!@$%#@")).build();
            //2、校验签名(校验token是否合法)   
            DecodedJWT jwt = jwtVerifier.verify(token);
            
            System.out.println("用户名:"+jwt.getClaim("username").asString());
            System.out.println("年龄:"+jwt.getClaim("age").asInt());
            SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            System.out.println("令牌发放时间:"+sdf.format(jwt.getIssuedAt()));
            System.out.println("令牌过期时间:"+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 = "token!Q@W3e4r";
    /**
     * 生成token
     * @param map  //传入payload
     * @return 返回token
     */
    public static String getToken(Map<String,String> map){
        JWTCreator.Builder builder = JWT.create();
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND,7);
        builder.withExpiresAt(instance.getTime());
        return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
    }
    /**
     * 验证token
     * @param token
     * @return
     */
    public static void verify(String token){
        JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
    }
    /**
     * 获取token中payload
     * @param token
     * @return
     */
    public static DecodedJWT getToken(String token){
        return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
    }
}

4、整合pringboot

登录Controller:

  @GetMapping("/user/login")
    public Map<String,Object> login(User user){
        Map<String, Object> map = new HashMap<>();
        try{
            //通过用户名和密码查找用户
            User userDB = userService.login(user);
            Map<String,String> payload =  new HashMap<>();
            payload.put("id",userDB.getId());
            payload.put("name",userDB.getName());
            //生成JWT的令牌
            String token = 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:

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

拦截器:

拦截器配置:

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())//将jwt拦截器添加进去
                .excludePathPatterns("/user/login")//放行登录接口
                .addPathPatterns("/**");      //拦截接口
    }
}
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //拦截处理
        Map<String, Object> map = new HashMap<>();
        //获取请求头中令牌
        String token = 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  jackson
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
        return false;
    }
}

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

测试接口:

    @PostMapping("/user/test")
    public Map<String,Object> test(HttpServletRequest request){
        Map<String, Object> map = new HashMap<>();
        //处理自己业务逻辑
        String token = request.getHeader("token");
        DecodedJWT verify = 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,其他接口就只需要获取token后来解析其中的值,处理自己的业务逻辑就行了

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

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

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

5、前端页面解析token

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

let str=token[1];
let user=JSON.parse(decodeURIComponent(escape(window.atob(str))));
console.log(user.username);

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Rk..

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

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

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

打赏作者

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

抵扣说明:

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

余额充值