您当前的位置: 首页 >  运维

科技D人生

暂无认证

  • 0浏览

    0关注

    1550博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

App后台开发运维和架构实践学习总结(10)——基于Java-JWT前后端token认证实战使用详解

科技D人生 发布时间:2018-04-14 17:14:21 ,浏览量:0

一、什么是JWT?了解JWT,认知JWT首先jwt其实是三个英语单词JSON Web Token的缩写。通过全名你可能就有一个基本的认知了。token一般都是用来认证的,比如我们系统中常用的用户登录token可以用来认证该用户是否登录。jwt也是经常作为一种安全的token使用。JWT的定义:JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。JWT特点:简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快。自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库二、JWT构成或者说JWT是什么样的?2.1.JWT结构JWT主要包含三个部分之间用英语句号'.'隔开Header 头部Payload 负载Signature 签名注意,顺序是 header.payload.signature最终的结构有点像这样:leftso.com.blog当然真实的jwt不可能是这么简单的明文2.2.JWT的头部(Header)在header中通常包含了两部分:token类型和采用的加密算法。如下:{  "alg": "HS256",  "typ": "JWT"}  上面的JSON内容指定了当前采用的加密方式为HS256,token的类型为jwt将上面的内容进行base64编码,可以得到我们JWT的头部,编码后如下:(本站提供了在线的base64编码/解码的工具,可供读者测试)ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9ICA=2.3.JWT的负载(Payload)负载(Payload)为JWT的第二部分。JWT的标准所定义了一下几个基本字段iss: 该JWT的签发者sub: 该JWT所面向的用户aud: 接收该JWT的一方exp(expires): 什么时候过期,这里是一个Unix时间戳iat(issued at): 在什么时候签发的除了标准定义的字段外,我们还要定义一些我们在业务处理中需要用到的字段,例如用户token一般可以包含用户登录的token或者用户的id,一个简单的例子如下:{    "iss": "Lefto.com",    "iat": 1500218077,    "exp": 1500218077,    "aud": "www.leftso.com",    "sub": "leftso@qq.com",    "user_id": "dc2c4eefe2d141490b6ca612e252f92e",    "user_token": "09f7f25cdb003699cee05759e7934fb2"}上面的user_id、user_token都是我们自己定义的字段现在我们需要将负载这整个部分进行base64编码,编码后结果如下:ewogICAgImlzcyI6ICJMZWZ0by5jb20iLAogICAgImlhdCI6IDE1MDAyMTgwNzcsCiAgICAiZXhwIjogMTUwMDIxODA3NywKICAgICJhdWQiOiAid3d3LmxlZnRzby5jb20iLAogICAgInN1YiI6ICJsZWZ0c29AcXEuY29tIiwKICAgICJ1c2VyX2lkIjogImRjMmM0ZWVmZTJkMTQxNDkwYjZjYTYxMmUyNTJmOTJlIiwKICAgICJ1c2VyX3Rva2VuIjogIjA5ZjdmMjVjZGIwMDM2OTljZWUwNTc1OWU3OTM0ZmIyIgp92.4.Signature(签名)签名其实是对JWT的头部和负载整合的一个签名验证首先需要将头部和负载通过.链接起来就像这样:header.Payload,上述的例子链接起来之后就是这样的:ewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9ICA=.ewogICAgImlzcyI6ICJMZWZ0by5jb20iLAogICAgImlhdCI6IDE1MDAyMTgwNzcsCiAgICAiZXhwIjogMTUwMDIxODA3NywKICAgICJhdWQiOiAid3d3LmxlZnRzby5jb20iLAogICAgInN1YiI6ICJsZWZ0c29AcXEuY29tIiwKICAgICJ1c2VyX2lkIjogImRjMmM0ZWVmZTJkMTQxNDkwYjZjYTYxMmUyNTJmOTJlIiwKICAgICJ1c2VyX3Rva2VuIjogIjA5ZjdmMjVjZGIwMDM2OTljZWUwNTc1OWU3OTM0ZmIyIgp9由于HMacSHA256加密算法需要一个key,我们这里key暂时用leftso吧加密后的内容为:686855c578362e762248f22e2cc1213dc7a6aff8ebda52247780eb6b5ae91877其实加密的内容也就是JWT的签名,类似我们对某个文件进行MD5加密然后接收到文件进行md5对比一样.只是这里的HMacSHA256算法需要一个key,当然这个key应该是使用者和接收者都知道的。对上面的签名内容进行base64编码得到最终的签名Njg2ODU1YzU3ODM2MmU3NjIyNDhmMjJlMmNjMTIxM2RjN2E2YWZmOGViZGE1MjI0Nzc4MGViNmI1YWU5MTg3Nw==2.5最终的JWTewogICJhbGciOiAiSFMyNTYiLAogICJ0eXAiOiAiSldUIgp9ICA=.ewogICAgImlzcyI6ICJMZWZ0by5jb20iLAogICAgImlhdCI6IDE1MDAyMTgwNzcsCiAgICAiZXhwIjogMTUwMDIxODA3NywKICAgICJhdWQiOiAid3d3LmxlZnRzby5jb20iLAogICAgInN1YiI6ICJsZWZ0c29AcXEuY29tIiwKICAgICJ1c2VyX2lkIjogImRjMmM0ZWVmZTJkMTQxNDkwYjZjYTYxMmUyNTJmOTJlIiwKICAgICJ1c2VyX3Rva2VuIjogIjA5ZjdmMjVjZGIwMDM2OTljZWUwNTc1OWU3OTM0ZmIyIgp9.Njg2ODU1YzU3ODM2MmU3NjIyNDhmMjJlMmNjMTIxM2RjN2E2YWZmOGViZGE1MjI0Nzc4MGViNmI1YWU5MTg3Nw==通过上面的一个简单的说明您是否对JWT有一个简单额认知了呢?接下来我将讲解JWT在Java编程中的使用三、Java编程中jwt框架选择在Java编程中,实现jwt标准的有很多框架,本博客采用的框架是auth0的java-jwt版本为3.2.0四、什么是Java-JWTauth0的java-jwt是一个JSON WEB TOKEN(JWT)的一个实现。五、安装下载相关依赖如果你是采用maven的方式,在你的项目pom.xml文件中添加以下java-jwt的依赖片段:    com.auth0    java-jwt    3.2.0如果你是采用Gradle的方式,则添加以下内容:compile 'com.auth0:java-jwt:3.2.0'5.1、java-jwt已经实现的算法该库使用以下算法实现JWT验证和签名:JWS 算法 介绍HS256 HMAC256 HMAC with SHA-256HS384 HMAC384 HMAC with SHA-384HS512 HMAC512 HMAC with SHA-512RS256 RSA256 RSASSA-PKCS1-v1_5 with SHA-256RS384 RSA384 RSASSA-PKCS1-v1_5 with SHA-384RS512 RSA512 RSASSA-PKCS1-v1_5 with SHA-512ES256 ECDSA256 ECDSA with curve P-256 and SHA-256ES384 ECDSA384 ECDSA with curve P-384 and SHA-384ES512 ECDSA512 ECDSA with curve P-521 and SHA-512六、如何使用java-jwt6.1.选择一种算法  算法定义了一个令牌是如何被签名和验证的。它可以用HMAC算法的原始值来实例化,也可以在RSA和ECDSA算法的情况下对密钥对或密钥提供程序进行实例化。创建后,该实例可用于令牌签名和验证操作。在使用RSA或ECDSA算法时,只需要签署JWTs,就可以通过传递null值来避免指定公钥。当您需要验证JWTs时,也可以使用私钥进行操作使用静态的字符密文或者key来获取算法器: //HMACAlgorithm algorithmHS = Algorithm.HMAC256("secret");//RSARSAPublicKey publicKey = //Get the key instanceRSAPrivateKey privateKey = //Get the key instanceAlgorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);使用一个key提供者来获取算法:  通过使用KeyProvider,您可以在运行时更改密钥,用于验证令牌签名或为RSA或ECDSA算法签署一个新的令牌。这是通过实现RSAKeyProvider或ECDSAKeyProvider方法实现的:getPublicKeyById(String kid): 它在令牌签名验证中调用,它应该返回用于验证令牌的密钥。如果使用了关键的轮换,例如JWK,它可以使用id来获取正确的轮换键(或者只是一直返回相同的键)。getPrivateKey(): 在令牌签名期间调用它,它应该返回用于签署JWT的密钥。getPrivateKeyId():在令牌签名期间调用它,它应该返回标识由getPrivateKey()返回的键的id的id。这个值比JWTCreator.Builder和keyid(String)方法中的值更受欢迎。如果您不需要设置孩子的值,就避免使用KeyProvider实例化算法。下面的代码片段将展示如何使用:final JwkStore jwkStore = new JwkStore("{JWKS_FILE_HOST}");final RSAPrivateKey privateKey = //Get the key instancefinal String privateKeyId = //Create an Id for the above keyRSAKeyProvider keyProvider = new RSAKeyProvider() {    @Override    public RSAPublicKey getPublicKeyById(String kid) {        //Received 'kid' value might be null if it wasn't defined in the Token's header        RSAPublicKey publicKey = jwkStore.get(kid);        return (RSAPublicKey) publicKey;    }    @Override    public RSAPrivateKey getPrivateKey() {        return privateKey;    }    @Override    public String getPrivateKeyId() {        return privateKeyId;    }};Algorithm algorithm = Algorithm.RSA256(keyProvider);//Use the Algorithm to create and verify JWTs.提示:对于使用JWKs的简单的键轮换,可以尝试JWKs-rsa-java库。6.2.创建一个签名的JWT token首先需要通过调用jwt.create()创建一个JWTCreator实例例如使用 HS256算法:try {    Algorithm algorithm = Algorithm.HMAC256("secret");    String token = JWT.create()        .withIssuer("auth0")        .sign(algorithm);} catch (UnsupportedEncodingException exception){    //UTF-8 encoding not supported} catch (JWTCreationException exception){    //Invalid Signing configuration / Couldn't convert Claims.}例如使用RS256算法:RSAPublicKey publicKey = //Get the key instanceRSAPrivateKey privateKey = //Get the key instancetry {    Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);    String token = JWT.create()        .withIssuer("auth0")        .sign(algorithm);} catch (JWTCreationException exception){    //Invalid Signing configuration / Couldn't convert Claims.}如果Claim不能转换为JSON,或者在签名过程中使用的密钥无效,那么将会抛出JWTCreationException异常。6.3.验证令牌  首先需要通过调用jwt.require()和传递算法实例来创建一个JWTVerifier实例。如果您要求令牌具有特定的Claim值,请使用构建器来定义它们。方法build()返回的实例是可重用的,因此您可以定义一次,并使用它来验证不同的标记。最后调用verifier.verify()来验证token例如使用 HS256算法的时候:String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";try {    Algorithm algorithm = Algorithm.HMAC256("secret");    JWTVerifier verifier = JWT.require(algorithm)        .withIssuer("auth0")        .build(); //Reusable verifier instance    DecodedJWT jwt = verifier.verify(token);} catch (UnsupportedEncodingException exception){    //UTF-8 encoding not supported} catch (JWTVerificationException exception){    //Invalid signature/claims}例如使用 RS256算法的时候:String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";RSAPublicKey publicKey = //Get the key instanceRSAPrivateKey privateKey = //Get the key instancetry {    Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);    JWTVerifier verifier = JWT.require(algorithm)        .withIssuer("auth0")        .build(); //Reusable verifier instance    DecodedJWT jwt = verifier.verify(token);} catch (JWTVerificationException exception){    //Invalid signature/claims}如果令牌有一个无效的签名,或者没有满足Claim要求,那么将会抛出JWTVerificationException异常6.4.jwt时间的验证JWT令牌可能包括可用于验证的DateNumber字段:这个令牌发布了一个过期的时间 "iat" < TODAY这个令牌还没过期 "exp" > TODAY and这个令牌已经被使用了. "nbf" > TODAY当验证一个令牌时,时间验证会自动发生,导致在值无效时抛出一个JWTVerificationException。如果前面的任何一个字段都丢失了,那么在这个验证中就不会考虑这些字段。要指定令牌仍然被认为有效的余地窗口,在JWTVerifier builder中使用accept回旋()方法,并传递一个正值的秒值。这适用于上面列出的每一项。JWTVerifier verifier = JWT.require(algorithm)    .acceptLeeway(1) // 1 sec for nbf, iat and exp    .build();您还可以为给定的日期声明指定一个自定义值,并为该声明覆盖缺省值。JWTVerifier verifier = JWT.require(algorithm)    .acceptLeeway(1)   //1 sec for nbf and iat    .acceptExpiresAt(5)   //5 secs for exp    .build();如果您需要在您的lib/app中测试此行为,将验证实例转换为basever可视化,以获得verific.build()方法的可见性,该方法可以接受定制的时钟。例如:BaseVerification verification = (BaseVerification) JWT.require(algorithm)    .acceptLeeway(1)    .acceptExpiresAt(5);Clock clock = new CustomClock(); //Must implement Clock interfaceJWTVerifier verifier = verification.build(clock);6.5解码一个jwt令牌String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";try {    DecodedJWT jwt = JWT.decode(token);} catch (JWTDecodeException exception){    //Invalid token}如果令牌有无效的语法,或者消息头或有效负载不是JSONs,那么将会抛出JWTDecodeException异常。6.6JWT头部解析Algorithm ("alg")返回jwt的算法值或,如果没有定义则返回nullString algorithm = jwt.getAlgorithm();Type ("typ")返回jwt的类型值,如果没有定义则返回null(多数情况类型值为jwt)String type = jwt.getType();Content Type ("cty")返回内容的类型,如果没有定义则返回nullString contentType = jwt.getContentType();Key Id ("kid")返回key的id值,如果没有定义则返回nullString keyId = jwt.getKeyId();私有的Claims,即自定义字段在令牌的头部中定义的附加声明可以通过调用getHeaderClaim() 获取,即使无法找到,也会返回。您可以通过调用claim.isNull()来检查声明的值是否为null。Claim claim = jwt.getHeaderClaim("owner");当使用jwt.create()创建一个令牌时,您可以通过调用withHeader()来指定头声明,并同时传递声明的映射。Map headerClaims = new HashMap();headerClaims.put("owner", "auth0");String token = JWT.create()        .withHeader(headerClaims)        .sign(algorithm);提示:在签名过程之后,alg和typ值将始终包含在Header中。6.7JWT的负载(Payload)声明Issuer ("iss")返回签发者的名称值,如果没有在负载中定义则返回nullString issuer = jwt.getIssuer();Subject ("sub")返回jwt所面向的用户的值,如果没有在负载中定义则返回nullString subject = jwt.getSubject();Audience ("aud")返回该jwt由谁接收,如果没有在负载中定义则返回nullList audience = jwt.getAudience();Expiration Time ("exp")返回该jwt的过期时间,如果在负载中没有定义则返回nullDate expiresAt = jwt.getExpiresAt();Not Before ("nbf")Returns the Not Before value or null if it's not defined in the Payload.Date notBefore = jwt.getNotBefore();Issued At ("iat")返回在什么时候签发的,如果在负载中没有定义则返回nullDate issuedAt = jwt.getIssuedAt();JWT ID ("jti")返回该jwt的唯一标志,如果在负载中没有定义则返回nullString id = jwt.getId();自定义声明在令牌有效负载中定义的附加声明可以通过调用getClaims()或 getClaim()和传递声明名来获得。即使无法找到声明,也将会有返回值。您可以通过调用claim.isNull()来检查声明的值是否为null。Map claims = jwt.getClaims();    //Key is the Claim nameClaim claim = claims.get("isAdmin");或者:Claim claim = jwt.getClaim("isAdmin");当使用jwt.create()创建一个令牌时,您可以通过调用withClaim()来指定自定义声明,并同时传递名称和值。String token = JWT.create()        .withClaim("name", 123)        .withArrayClaim("array", new Integer[]{1, 2, 3})        .sign(algorithm);您还可以通过调用withClaim()来验证jwt.require()的自定义声明,并传递该名称和所需的值。JWTVerifier verifier = JWT.require(algorithm)    .withClaim("name", 123)    .withArrayClaim("array", 1, 2, 3)    .build();DecodedJWT jwt = verifier.verify("my.jwt.token");提示:当前支持的自定义JWT声明创建和验证的类型是:Boolean, Integer, Double, String, Date 和Arrays。6.8Claim Class索赔类是索赔值的包装器。它允许您将索赔作为不同的类类型。可用的工具方法:原始的:asBoolean(): 返回布尔值,如果不能转换返回null。asInt(): 返回整数值,如果不能转换返回null。asDouble(): 返回 Double 值,如果不能转换则返回null。asLong(): 返回Long 值,如果不能转换则返回null。asString(): 返回String值,如果不能转换则返回null。asDate(): 返回 Date值,如果不能转换则返回null。 必须是一个数字日期 (Unix 系统时间戳). 注意,JWT标准指定所有的数字日期值必须以秒为单位。自定义类型和集合:要获得作为集合的声明,您需要提供要转换的内容的类类型as(class): 返回 Class Type 的解析值. 对于集合,您应该使用asArray和asList方法。asMap(): 返回被转换为 Map的值asArray(class): 返回被转换成元素类型的 Class Type, or null if the value isn't a JSON Array.asList(class): 返回集合元素的 Class Type, or null if the value isn't a JSON Array.如果不能将值转换为给定的类类型,则会抛出JWTDecodeException异常
关注
打赏
1662604032
查看更多评论
立即登录/注册

微信扫码登录

0.0508s