基于JWT 的token 认证机制的实例 全局异常处理

基于JWT 的token 认证机制的实现

文章目录

  • 基于JWT 的token 认证机制的实现
      • 1 jwt认知
        • 1.1 头部(Header)
        • 1.2 载荷(playload)
        • 1.3 签证(signature)
      • 2 JJWET 实现
        • 2.1 导入依赖
        • 2.2 创建第一个jwt
          • 2.2.1 结果
        • 2.3 解析第一个jwt
        • 2.3.1 结果:
        • 2.4 生成工具类
      • 3 权限验证实现
        • 3.1 拦截器
        • 3.2 权限验证
        • 3.3 全局异常处理
        • 3.4 测试:
          • 3.4.1 获取token
          • 3.4.2 访问受限制资源,并携带token

在上一篇博客里我给我大家说了4中,常见的认证机制,企业自主认证的机制中,我给大家带来一个基于jwt认证机制的实现。

1 jwt认知

**jwt定义:**JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

JWT本质:就是一个字符串,由三部分组成,头部、载荷与签名。

1.1 头部(Header)

头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。

{"typ":"JWT","alg":"HS256"}

在头部中指明了,签名算法是HS256算法。默认采用base64编码,编码后的字符串为:

http://tool.oschina.net/encrypt?type=3

{"typ":"JWT","alg":"HS256"} 明文
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 密文

1.2 载荷(playload)

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

(1)标准中注册的声明(建议但不强制使用)

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

(2)公共的声明

​ 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密

(3)私有的声明

​ 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

​ 这个指的就是自定义的claim。比如前面那个结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。

定义一个payload:

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

然后将其进行base64编码,得到Jwt的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

1.3 签证(signature)

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

http://tool.oschina.net/encrypt?type=2

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9  使用 HS256算法,使用secret, 生成签证
123456
5b2a3bdc166345add37e238e01c9c5ca3dea1bf5fbc81f619e34fc66e147ffde

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.5b2a3bdc166345add37e238e01c9c5ca3dea1bf5fbc81f619e34fc66e147ffde

2 JJWET 实现

2.1 导入依赖

        
            io.jsonwebtoken
            jjwt
            0.6.0
        

2.2 创建第一个jwt


 public static void main(String[] args) {
        JwtBuilder jwtBuilder = Jwts.builder().setId("1164032971834003456")
                                               .setSubject("小刘")
                                               .setIssuedAt(new Date())
                                               .signWith(SignatureAlgorithm.HS256,"liubijun");
        System.out.println(jwtBuilder.compact());






    }



2.2.1 结果
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMTY0MDMyOTcxODM0MDAzNDU2Iiwic3ViIjoi5bCP5YiYIiwiaWF0IjoxNTY2MzY4ODk2fQ.39RBcIhd0OiVW9BkA_JyBoUyMrRl-eEo-UHuUQ_BC2U

2.3 解析第一个jwt

   
    public static void main(String[] args) {
        
      Claims claims = Jwts.parser()
                    .setSigningKey("liubijun").parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMTY0MDMyOTcxODM0MDAzNDU2Iiwic3ViIjoi5bCP5YiYIiwiaWF0IjoxNTY2MzY4ODk2fQ.39RBcIhd0OiVW9BkA_JyBoUyMrRl-eEo-UHuUQ_BC2U").getBody();

            System.out.println("用户ID:"+ claims.getId());
            System.out.println("用户名称:"+ claims.getSubject());
            System.out.println("登录时间:"+  new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ").format(claims.getIssuedAt()));





    }

 

2.3.1 结果:

基于JWT 的token 认证机制的实例 全局异常处理_第1张图片

2.4 生成工具类

package com.liu.common.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 *  令牌生成和解析工具
 */
@Data
@ConfigurationProperties("jwt.config")
public class JwtUtil {

    private String key ;

    private long ttl ;//一个小时



    /**
     * 生成JWT
     *
     * @param id
     * @param subject
     * @return
     */
    public String createJWT(String id, String subject, String roles) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        JwtBuilder builder = Jwts.builder().setId(id)
                .setSubject(subject)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, key).claim("roles", roles);
        if (ttl > 0) {
            builder.setExpiration( new Date( nowMillis + ttl));
        }
        return builder.compact();
    }

    /**
     * 解析JWT
     * @param jwtStr
     * @return
     */
    public Claims parseJWT(String jwtStr){
        return  Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(jwtStr)
                .getBody();
    }




}

3 权限验证实现

3.1 拦截器

在我们的项目中经常会用到权限的,验证。为了能够一劳永逸,我们使用拦截器来进行jwt的解析。

注意:无论解析是否成功,我们都要放行,毕竟不是所以的业务都需要权限验证。我们是在需要解析后,将将解析的结果放置到request域中,这样我们就可以通过获得request域,来获取前面传来的权限相关信息。本例是在service层 做的权限。

在jwt token解析的过程中,如果用户过期,则解析会报错。记得处理下异常,要不然报错一堆。


package com.liu.user.interceptor;

import com.liu.common.utils.JacksonUtil;
import com.liu.common.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created by Administrator on 2019/8/21 0021.
 */
@Component
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtUtil jwtUtil;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        log.info("进入了权限拦截器{}");
//          这里主要做的是做权限的解析,而不是拦截用户请求
        String header =request.getHeader("Authorization");
        log.info("header{}",StringUtils.isEmpty(header));
        if(!StringUtils.isEmpty(header) && header.startsWith("Bearer ")){
            final String token = header.substring(7);
            try{
                Claims claims = jwtUtil.parseJWT(token);
               log.info("claims{}",JacksonUtil.printJson(claims));
                if(claims!=null){
                    if("admin".equals(claims.get("roles"))){//如果是管理员
                        request.setAttribute("admin_claims", claims);
                    }
                    if("user".equals(claims.get("roles"))){//如果是用户
                        request.setAttribute("user_claims", claims);
                    }
                }
            }catch (Exception e){
                throw new RuntimeException("令牌不正确");
            }
        }

        return true;
    }
}

3.2 权限验证

  public void sendSMS(String mobile){
        
            Claims claims = (Claims) request.getAttribute("admin_claims");
            if(claims==null){
                throw new RuntimeException(CodeEnum.ACCESS_ERROR.getMessage());
            }


            String random =RandomStringUtils.randomNumeric(6);
            log.info("验证码为:{}",random);

            redisTemplate.boundHashOps(RedisKeyEnum.CAPTCHA_CODE.getKey()).put(mobile,random);//保存验证码到redis
            Map map = new HashMap<>();
            map.put("code",random);
            map.put("mobile",mobile);
            rabbitTemplate.convertAndSend("CAPTCHA_CODE",map); //发送验证码到rabbitmq

        }

我们可以看到在拦截器,或者是service层,我们都直接抛出了 异常,我们可以写一个全局的异常捕获类,来处理我们抛出的异常

3.3 全局异常处理

package com.liu.user.exception;

import com.liu.common.entity.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;

/**
 * 本文只是没有系统的处理异常,一般应用系统中,我们都会定义多个异常类,来处理不同的异常情况。
 然后通过异常处理类,处理不同种类的异常。
 */
@RestControllerAdvice
public class MyGlobalExceptionHandler {


    @ExceptionHandler({Exception.class})
    public Result handException(HttpServletRequest request ,Exception e) throws Exception{


        return new Result(false,10001,e.getMessage());

    }



}

3.4 测试:

3.4.1 获取token

基于JWT 的token 认证机制的实例 全局异常处理_第2张图片

3.4.2 访问受限制资源,并携带token

基于JWT 的token 认证机制的实例 全局异常处理_第3张图片

本小结,讲述完毕。

你可能感兴趣的:(springboot,jwt)