基于JWT的Token认证机制实现

在日常的开发中,常见的认证机制有几种,其中使用token是目前前后端分离项目最受欢迎的一种认证手段。token字面意思为令牌(一串字符串),目的就是客户端在访问服务器时要带上这个令牌,服务器然后去根据这个令牌进行认证是否允许你去访问。这个过程中,服务器时完全没有存储任何关于认证的信息的,因此使用token的认证方式也被称之为无状态的认证方式,而生成token又有很多种方式,比如服务器直接生成一个字符串(UUID)给客户端,并且存储在内存或者redis中,下次客户端访问时必须要带上这个token,但是既然token的生成方式有这么多种,使用的方法又是不同的,那么这时候jwt就产生了。

 

1.什么是JWT?

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

2.JWT组成

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名

头部(header)

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

载荷(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进行验证以及规则才行。

签证(signature)

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

header (base64后的)

payload (base64后的)

secret

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

然后这三部分分别都用.连接成一个完整的字符串,构成最终的jwt。

3.在Java中使用JWT(JJWT)

3.1什么是JJWT?

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(ApacheLicense,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

3.2JJWT的使用,下面代码演示工程使用的是springboot 

首先加入JJWT的依赖:

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.6.0</version>
        </dependency>

JJWT工具类:

@ConfigurationProperties("jwt.config")
public class JwtUtil {

    private String key ;

    private long ttl ;//一个小时

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public long getTtl() {
        return ttl;
    }

    public void setTtl(long ttl) {
        this.ttl = 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();
    }

}

在需要认证之后才能访问的模块中定义拦截器:

@Component
public class JwtInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private JwtUtil jwtUtil;

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

        //取出http header
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);
            try {
                Claims claims = jwtUtil.parseJWT(token);
                if (claims!=null){
                    String role = (String) claims.get("role");
                    if ("admin".equals(role)){
                        request.setAttribute("admin_claim",token);
                    }
                    if ("user".equals(role)){
                        request.setAttribute("user_claim",token);
                    }
                }
            }catch (Exception ex){
                ex.printStackTrace();
                throw new RuntimeException("令牌错误!");
            }
        }
        return true;
    }
}

在拦截器中,前台登录之后会把token放在请求头中,格式为(Bearer +token),把请求头的token拿到之后 通过上面JJWT的工具类进行解析,当然我们上面说过,在JWT的载荷中可以放自定义的认证信息,我们这里就放role作为角色的校验条件,当解析到的role是admin的时候就把token设置到以admin_claim为key的request域中,当role是user的时候就把token设置到user_claim为key的request域中,然后统一放行,拦截器return ture。

public Result addProblem(Problem problem) {
        String token = (String) request.getAttribute("user_claim");
        if (token == null) {
            return new Result(false, StatusCode.ACCESSERROR, "权限不足");
        }

        problem.setId(String.valueOf(idWorker.nextId()));
        problemRepository.save(problem);
        return new Result(true, StatusCode.OK, "添加问题成功");
    }

请求经过拦截器来到service层,在service层中取出当前request域中token,比如上面的添加问题的接口中需要有user角色的权限,就取出user_claim为key的value判断是否为空,为空的话就说明该请求认证不通过,返回拒绝访问。 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值