Java安全验证之JWT实践

先给出JWT的官方文档html

什么是JWT?

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间做为JSON对象安全地传输信息。java

何时应该使用JWT?

  • 受权:这是使用JWT的最多见方式。一旦用户登陆,每一个后续请求将包括JWT,容许用户访问该令牌容许的路由,服务和资源。Single Sign On是一种如今普遍使用JWT的功能,由于它的开销很小,而且可以在不一样的域中轻松使用。
  • 信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。由于JWT能够经过签名(使用公钥/私钥对 )核实发送人的身份。此外,因为使用标头和有效负载计算签名,您还能够验证内容是否未被篡改。

JWT令牌结构

JWT令牌由Header、Payload、Signature三部分组成,每部分之间用点号分隔,一般的形式为xxxxx.yyyyy.zzzzz,下面分别对每部分作详细介绍。git

Header(头)

Header一般由两部分组成:令牌的类型,即JWT,以及使用的签名算法,例如HMAC SHA256或RSA。github

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

这个JSON被编码为Base64Url,造成JWT的第一部分。web

Payload(有效载荷)

JWT的第二部分是有效载荷,其中包含声明( claims)。声明包含实体(一般是用户)和其余自定义信息。声明有三种类型:registered, publicprivate claims算法

  • registered claims :这是一组预约义声明,不是强制性的,但建议使用,以提供一组有用的,可互操做的声明。其中包括: iss(发行人), exp(到期时间),sub(主题), aud(受众)。

请注意:声明名称只有三个字符,由于JWT须要保持简洁。数据库

  • public claims:这些能够由使用JWT的人随意定义。但为避免冲突,应在 IANA JSON Web Token Register中定义它们,或者将其定义为包含防冲突命名空间的URI。
  • private claims:这些是自定义声明,用于在赞成使用这些声明的各方之间共享信息,这些信息既没有注册也没有公开声明。

Payload通过Base64Url编码,造成JWT的第二部分。json

请注意:对于JWT令牌,虽然能够防止被篡改,但任何人均可以读取。除非加密,不然不要将秘密信息放在JWT的有效载荷或头元素中。api

Signature(签名)

Signature由Base64Url加密的Header、Payload再使用Header中指定的算法加密以后再和secret组成。跨域

若是要使用HMACSHA256算法,将按如下方式建立签名:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

Signature用于验证消息在此过程当中未被更改,而且,在使用私钥签名的令牌的状况下,它还能够验证JWT的发件人的身份。

总结

最后将JWT的三部分由点号分隔做为一个字符串,其能够在HTML和HTTP环境中轻松传递,而且比基于XML的标准的Token(如SAML)更加简洁。

JWT工做流程

在身份验证中,当用户使用其凭据成功登陆时,将返回JSON Web Token。因为令牌是凭证,所以必须很是当心以防止出现安全问题。通常状况下,您不该该将令牌保留的时间超过要求。

每当用户想要访问受保护的路由或资源时,用户发送JWT到相应的地址,一般在Authorization标头中。请求头的的内容应以下所示:

Authorization: Bearer <token>

在某些状况下,这能够是无状态受权机制。服务器的受保护路由将检查Authorization标头中的有效JWT ,若是存在,则容许用户访问受保护资源。若是JWT包含必要的数据,则能够减小查询数据库以进行某些操做的须要,尽管可能并不是老是如此。

若是在标Authorization头中发送Token,则跨域资源共享(CORS)将不会成为问题,由于它不使用cookie。

下图显示了如何获取JWT并用于访问API或资源:

JSON Web Token如何工做

  1. 应用程序或客户端向受权服务器发送受权请求。
  2. 授予受权后,受权服务器会向应用程序返回JWT。
  3. 应用程序使用JWT来访问受保护资源(如API)。

请注意:使用JWT时,Token中包含的全部信息都会向用户或其余方公开,即便他们没法更改。因此您不该该在令牌中放置秘密信息。

JWT中的JAVA实现

JWT的java实现很是多,详细何以查看官方文档

其中经常使用的有com.auth0.java-jwtio.jsonwebtoken.jjwt

这里我采用 jjwt 做为演示,由于他的Github中的Star比较多。

Maven依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.10.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.10.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.10.5</version>
    <scope>runtime</scope>
</dependency>
<!-- Uncomment this next dependency if you want to use RSASSA-PSS (PS256, PS384, PS512) algorithms:
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.60</version>
    <scope>runtime</scope>
</dependency>
-->

建立Token

//Sample method to construct a JWT
    public static String createJWT(String id, String issuer, String subject, long ttlMillis) {

        //The JWT signature algorithm we will be using to sign the token
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        //We will sign our JWT with our ApiKey secret
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary( APP_ID + APP_SECRET);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

        //Let's set the JWT Claims
        JwtBuilder builder = Jwts.builder().setId(id)
                .setIssuedAt(now)
                .setSubject(subject)
                .setIssuer(issuer)
                .signWith(signatureAlgorithm, signingKey);

        //if it has been specified, let's add the expiration
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            builder.setExpiration(exp);
        }

        //Builds the JWT and serializes it to a compact, URL-safe string
        return builder.compact();

    }

解析Token

//Sample method to validate and read the JWT
    public static Claims parseJWT(String jwt) {

        //This line will throw an exception if it is not a signed JWS (as expected)
        Claims claims = Jwts.parser()
                .setSigningKey(DatatypeConverter.parseBase64Binary(APP_ID + APP_SECRET))
                .parseClaimsJws(jwt).getBody();
//        System.out.println("ID: " + claims.getId());
//        System.out.println("Subject: " + claims.getSubject());
//        System.out.println("Issuer: " + claims.getIssuer());
//        System.out.println("Expiration: " + claims.getExpiration());
        return claims;
    }

其中APP_IDAPP_SECRET能够自定义为你想要的任何值,可是不能过于简单。

你会发现咱们再使用的时候没有设置Header的值,由于jjwt为了咱们使用方便会根据使用的签名算法或压缩算法自动设置它们。

使用

@Test
public void createJWT(){

    String jwt = JWTUtil.createJWT("1", "111", "admin", JWTUtil.DAY_TTL);
    System.out.println(jwt);
}

@Test
public void parseJWT(){
            Claims claims = JWTUtil.parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxIiwiaWF0IjoxNTQ4Mjk1NjQ0LCJzdWIiOiJhZG1pbiIsImlzcyI6IjExMSIsImV4cCI6MTU0ODMzODg0NH0.WRkyeG3MfVor02Ya4732fgGydXhtkkKSDwbxOIZ2i9Y");
    System.out.println(claims);
}

若是想使用jjwt更复杂的功能或者其余的Java实现能够去他们相应的Github上学习。

相关文章
相关标签/搜索