认证令牌
建筑物身份管理,包括身份验证和授权? 尝试Stormpath! 我们的REST API和强大的Java SDK支持可以消除您的安全风险,并且可以在几分钟内实现。 注册 ,再也不会建立auth了!
2016年5月12日更新:构建Java应用程序? JJWT是由我们自己的Les Hazlewood开发的Java库,提供端到端JWT的创建和验证。 JJWT永久免费且开源(Apache许可证,版本2.0),易于使用和理解。 它的设计采用了以构建者为中心的流畅界面,从而掩盖了其大部分复杂性。 我们希望您能尝试一下 ,并告诉我们您的想法! (而且,如果您是Node开发人员,请查看NJWT !)
在我的上一篇文章中 ,我们涵盖了很多基础,包括我们传统上如何保护网站安全,使用cookie和会话的一些陷阱以及如何通过传统方式解决这些陷阱。
在本文中,我们将超越传统,深入探讨如何使用JWT(JSON Web令牌)进行令牌身份验证,不仅解决了这些问题,而且还使我们受益于可检查的元数据和强大的加密签名。
救援的令牌认证!
首先让我们检查一下在这种情况下authentication
和token
含义。
身份验证证明用户就是他们所说的真实身份。
令牌是一个独立的信息块。 它可能具有内在价值,也可能没有。 我们将研究一种特定类型的令牌, 它确实具有内在价值,并通过会话ID解决了许多问题。
JSON Web令牌(JWT)
JWT是URL安全,紧凑,自包含的字符串,其中包含有意义的信息,这些信息通常经过数字签名或加密。 它们正Swift成为网络上令牌实施的实际标准。
URL安全是一种说法,它表示整个字符串都已编码,因此没有特殊字符,并且令牌可以放入URL中。
该字符串是不透明的,可以与使用会话ID几乎相同的方式独立使用。 不透明是指查看字符串本身没有提供任何其他信息。
但是,还可以对字符串进行解码以提取元数据,并且可以对签名进行加密验证,以使您的应用程序知道令牌未被篡改。
JWT和OAuth2访问令牌
许多OAuth2实现都将JWT用于其访问令牌。 应该指出,OAuth2和JWT规范是彼此完全独立的,彼此之间没有任何依赖关系。 将JWT用作OAuth2的令牌机制会带来很多好处,我们将在下面看到。
JWT可以存储在cookie中,但是我们之前讨论的cookie的所有规则仍然适用。 您可以将会话ID完全替换为JWT。 然后,您可以获得直接从该会话ID访问元信息的其他好处。
在野外,它们看起来就像是另一个丑陋的弦:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vdHJ1c3R5YXBwLmNvbS8iLCJleHAiOjEzMDA4MTkzODAsInN1YiI6InVzZXJzLzg5ODM0NjIiLCJzY29wZSI6InNlbGYgYXBpL2J1eSJ9.43DXvhrwMGeLLlP4P4izjgsBB2yrpo82oiUPhADakLs
如果仔细看,您会看到字符串中有两个句点。 这些意义重大,因为它们界定了JWT的不同部分。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
.
eyJpc3MiOiJodHRwOi8vdHJ1c3R5YXBwLmNvbS8iLCJleHAiOjEzMDA4MTkzODAsInN1YiI6InVzZXJzLzg5ODM0NjIiLCJzY29wZSI6InNlbGYgYXBpL2J1eSJ9
.
43DXvhrwMGeLLlP4P4izjgsBB2yrpo82oiUPhADakLs
JWT结构
JWT具有三部分结构,每个部分都是base64编码的:
以下是解码的部分:
标头
{"typ": "JWT","alg": "HS256"
}
索偿
{"iss":"http://trustyapp.com/","exp": 1300819380,"sub": "users/8983462","scope": "self api/buy"
}
密码签名
tß´—™à%O˜v+nî…SZu¯µ€U…8H×
智威汤逊索赔
让我们检查索赔部分。 可以在此处找到属于JWT规范的每种声明。
iss
是发行令牌的人。
exp
是令牌过期的时间。
sub
是令牌的主题。 这通常是某种用户标识符。
权利要求的以上部分全部包含在JWT规范中。 scope
未包含在规范中,但通常用于提供授权信息。 也就是说,用户可以访问应用程序的哪些部分。
JWT的一个优点是,可以将任意数据编码到上述scope
的权利要求中。 另一个优点是,客户端现在可以对该信息做出React,而无需与服务器进行任何进一步的交互。 例如,可以基于在scope
权利要求中找到的数据来隐藏页面的一部分。
注意 :服务器始终验证客户机采取的操作仍然很关键,也是最佳实践。 例如,如果正在对客户端执行某些管理操作,则您仍将需要在应用程序服务器上验证当前用户是否有权执行该操作。 您永远不会仅依赖客户端授权信息。
您可能已经获得了另一个好处:加密签名。 签名可以被验证,证明JWT未被篡改。 请注意,密码签名的存在不能保证机密性。 仅当对JWT进行加密和签名时,才能确保机密性。
现在,最重要的是: 无国籍 。 尽管服务器将需要生成JWT,但由于所有用户元数据都已直接编码到JWT中,因此它不需要将其存储在任何地方。 服务器和客户端可以来回传递JWT,而从不存储它。 这样可以很好地扩展。
管理承载令牌安全性
隐式信任是一种折衷。 这些类型的令牌通常被称为承载令牌,因为获得对应用程序受保护部分的访问所需的全部就是有效的未过期令牌的表示。
您必须解决以下问题:令牌应使用多长时间? 您将如何撤销它? (还有其他整篇文章可以针对刷新令牌进行 。)
如果未加密,则必须注意存储在JWT中的内容。 不要存储任何敏感信息。 以sub
权利要求的形式存储用户标识符是公认的惯例。 当JWT签名时,它称为JWS。 加密后,称为JWE。
Java,JWT和您!
我们为Github上的JJWT项目感到自豪。 它是由Stormpath的首席技术官Les Hazlewood最初撰写的,它是针对Java的完全开源的JWT解决方案。 它是最易于使用和理解的库,用于在JVM上创建和验证JSON Web令牌。
您如何创建JWT? 十分简单!
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;byte[] key = getSignatureKey();String jwt = Jwts.builder().setIssuer("http://trustyapp.com/").setSubject("users/1300819380").setExpiration(expirationDate).put("scope", "self api/buy") .signWith(SignatureAlgorithm.HS256,key).compact();
首先要注意的是用于创建JWT的流畅的 Builder API。 方法调用被链接在一起,最终以compact
调用返回最终的JWT字符串。
还要注意,当我们设置规范中的一项权利要求时,我们使用了二传手。 例如: .setSubject("users/1300819380")
。 设置自定义声明后,我们会使用调用来放置并指定键和值。 例如: .put("scope", "self api/buy")
验证JWT一样容易。
String subject = "HACKER";
try {Jws jwtClaims = Jwts.parser().setSigningKey(key).parseClaimsJws(jwt);subject = claims.getBody().getSubject();//OK, we can trust this JWT} catch (SignatureException e) {//don't trust the JWT!
}
如果对JWT进行了任何篡改,则解析声明将引发SignatureException
并且subject
变量的值将保持HACKER
。 如果它是有效的JWT,则将从中提取subject
: claims.getBody().getSubject()
什么是OAuth?
在下一部分中,我们将看一个使用Stormpath的OAuth2实现的示例,该实现利用了JWT。
OAuth2规范周围有很多困惑。 那部分是因为它确实是über规范–它具有很多复杂性。 这也是因为OAuth1.a和OAuth2是非常不同的野兽。 我们将看一看OAuth2规范的一个非常具体,易于使用的子集。 我们有一篇很棒的文章,它详细介绍了什么是OAuth 。 在这里,我们将提供一些简短的背景知识,然后直接进入示例。
OAuth2基本上是一种支持授权工作流程的协议。 这意味着它为您提供了一种确保特定用户有权执行某项操作的方法。
而已。
OAuth2 并不旨在执行诸如验证用户身份之类的工作, 而是由身份验证服务负责。 身份验证是在验证用户身份( 例如要求输入用户名/密码 )时进行的验证,而授权是在检查现有用户已拥有的权限时进行的验证。
请记住,OAuth2是授权协议。
使用OAuth授权类型进行授权
让我们看一下典型的OAuth2交互。
POST /oauth/token HTTP/1.1
Origin: https://foo.com
Content-Type: application/x-www-form-urlencodedgrant_type=password&username=username&password=password
grant_type
是grant_type
。 这种交互类型也需要application/x-www-form-urlencoded
内容类型。 假设您通过网络传递用户名和密码,则始终希望连接是安全的。 不过,好处是响应将具有OAuth2承载令牌。 然后,此令牌将用于以后浏览器与服务器之间的每次交互。 这里有一个非常简短的介绍,其中用户名和密码是通过网络传递的。 假设服务器上的身份验证服务验证了用户名和密码,则响应如下:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache{"access_token":"2YotnFZFEjr1zCsicMWpAA...","token_type":"example","expires_in":3600,"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA...","example_parameter":"example_value"
}
注意Cache-Control
和Pragma
标头。 我们不希望此响应被缓存在任何地方。 access_token
是浏览器在后续请求中将使用的内容。 同样,OAuth2和JWT之间没有直接关系。 但是, access_token
可以是JWT。 这就是编码的元数据的额外好处所在。这是在以后的请求中利用访问令牌的方式:
GET /admin HTTP/1.1
Authorization: Bearer 2YotnFZFEjr1zCsicMW...
Authorization
标头是标准标头。 使用OAuth2不需要自定义标头。 在这种情况下,类型是Bearer
而不是Basic
类型。 访问令牌直接包含在Bearer
关键字之后。 这样就完成了密码授予类型的OAuth2交互。 浏览器的每个后续请求都可以将Authorizaion: Bearer
标头与访问令牌一起使用。
还有一种称为client_credentials
授予类型,它使用client_id
和client_secret
而不是username
和password
。 此授予类型通常用于API交互。 尽管客户端ID和秘密密钥的功能类似于用户名和密码,但它们通常具有更高的安全性,并且不一定是人类可读的。
带我们回家:OAuth2 Java示例
我们到了! 现在该深入研究一些演示JWT的特定代码了。
Spring Boot Web MVC
Stormpath Java SDK中有许多示例。 在这里,我们将看一个Spring Boot Web MVC示例。 这是示例中的HelloController :
@RestController
public class HelloController {@RequestMapping("/")String home(HttpServletRequest request) {String name = "World";Account account = AccountResolver.INSTANCE.getAccount(request);if (account != null) {name = account.getGivenName();}return "Hello " + name + "!";}}
对于此演示而言,关键是:
Account account = AccountResolver.INSTANCE.getAccount(request);
在幕后,仅当存在经过身份验证的会话时, account
才会解析为Account
对象(而不是null
)。
生成并运行示例代码
要构建并运行此示例,请执行以下操作:
☺ dogeared jobs:0 ~/Projects/StormPath/stormpath-sdk-java (master|8100m)
➥ cd examples/spring-boot-webmvc/
☺ dogeared jobs:0 ~/Projects/StormPath/stormpath-sdk-java/examples/spring-boot-webmvc (master|8100m)
➥ mvn clean package
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Stormpath Java SDK :: Examples :: Spring Boot Webapp 1.0.RC4.6-SNAPSHOT
[INFO] ------------------------------------------------------------------------... skipped output ...[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.865 s
[INFO] Finished at: 2015-08-04T11:46:05-04:00
[INFO] Final Memory: 31M/224M
[INFO] ------------------------------------------------------------------------
☺ dogeared jobs:0 ~/Projects/StormPath/stormpath-sdk-java/examples/spring-boot-webmvc (master|8100m
启动Spring Boot示例
然后,您可以像这样启动Spring Boot示例:
☺ dogeared jobs:0 ~/Projects/StormPath/stormpath-sdk-java/examples/spring-boot-webmvc (master|8104m)
➥ java -jar target/stormpath-sdk-examples-spring-boot-web-1.0.RC4.6-SNAPSHOT.jar. ____ _ __ _ _/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \\\/ ___)| |_)| | | | | || (_| | ) ) ) )' |____| .__|_| |_|_| |_\__, | / / / /=========|_|==============|___/=/_/_/_/:: Spring Boot :: (v1.2.1.RELEASE)2015-08-04 11:51:00.127 INFO 17973 --- [ main] tutorial.Application : Starting Application v1.0.RC4.6-SNAPSHOT on MacBook-Pro.local with PID 17973 ... skipped output ...2015-08-04 11:51:04.558 INFO 17973 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-08-04 11:51:04.559 INFO 17973 --- [ main] tutorial.Application : Started Application in 4.599 seconds (JVM running for 5.103)
注意 :假设您已经设置了一个Stormpath帐户,并且您的api密钥位于~/.stormpath/apiKey.properties
。 在此处查找有关使用Spring Boot快速设置Stormpath的更多信息。
使用JSON Web令牌进行身份验证(或不进行身份验证)
现在,我们可以练习该示例,并展示一些实际应用中的JWT! 首先,无需任何身份验证即可命中端点。 我喜欢使用httpie ,但是任何命令行http客户端都可以。
➥ http -v localhost:8080
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8080
User-Agent: HTTPie/0.9.2HTTP/1.1 200 OK
Accept-Charset: big5, big5-hkscs, cesu-8, euc-jp, euc-kr, gb18030, ...
Content-Length: 12
Content-Type: text/plain;charset=UTF-8
Date: Tue, 04 Aug 2015 15:56:41 GMT
Server: Apache-Coyote/1.1Hello World!
-v
参数产生详细的输出,并显示请求和响应的所有标头。 在这种情况下,输出消息就是: Hello World!
。 这是因为没有建立的会话。
使用Stormpath OAuth端点进行身份验证
现在,让我们点击oauth
端点,以便我们的服务器可以使用Stormpath进行身份验证。 您可能会问,“什么是oauth
端点?” 上面的控制器未指示任何此类端点。 示例中是否还有其他具有其他端点的控制器? 不是,没有! Stormpath提供了开箱即用的oauth(和许多其他)端点。 看看这个:
➥ http -v --form POST http://localhost:8080/oauth/token \
> 'Origin:http://localhost:8080' \
> grant_type=password username=micah+demo.jsmith@stormpath.com password=
POST /oauth/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Host: localhost:8080
Origin: http://localhost:8080
User-Agent: HTTPie/0.9.2grant_type=password&username=micah%2Bdemo.jsmith%40stormpath.com&password=HTTP/1.1 200 OK
Cache-Control: no-store
Content-Length: 325
Content-Type: application/json;charset=UTF-8
Date: Tue, 04 Aug 2015 16:02:08 GMT
Pragma: no-cache
Server: Apache-Coyote/1.1
Set-Cookie: account=eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxNDQyNmQxMy1mNThiLTRhNDEtYmVkZS0wYjM0M2ZjZDFhYzAiLCJpYXQiOjE0Mzg3MDQxMjgsInN1YiI6Imh0dHBzOi8vYXBpLnN0b3JtcGF0aC5jb20vdjEvYWNjb3VudHMvNW9NNFdJM1A0eEl3cDRXaURiUmo4MCIsImV4cCI6MTQzODk2MzMyOH0.wcXrS5yGtUoewAKqoqL5JhIQ109s1FMNopL_50HR_t4; Expires=Wed, 05-Aug-2015 16:02:08 GMT; Path=/; HttpOnly{"access_token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxNDQyNmQxMy1mNThiLTRhNDEtYmVkZS0wYjM0M2ZjZDFhYzAiLCJpYXQiOjE0Mzg3MDQxMjgsInN1YiI6Imh0dHBzOi8vYXBpLnN0b3JtcGF0aC5jb20vdjEvYWNjb3VudHMvNW9NNFdJM1A0eEl3cDRXaURiUmo4MCIsImV4cCI6MTQzODk2MzMyOH0.wcXrS5yGtUoewAKqoqL5JhIQ109s1FMNopL_50HR_t4","expires_in": 259200,"token_type": "Bearer"
}
这里有很多事情,所以让我们分解一下。
在第一行中,我告诉httpie
我想创建一个表单url编码的--form
这就是--form
和POST
参数的作用。 我正在命中本地运行的服务器的/oauth/token
端点。 我指定一个Origin
标头。 出于我们先前提到的安全原因,与Stormpath进行交互是必需的。 按照OAuth2规范,我要传递grant_type=password
以及username
和password
。
响应具有Set-Cookie
标头以及包含OAuth2访问令牌的JSON正文。 你猜怎么着? 该访问令牌也是JWT。 以下是已解码的声明:
{"jti": "14426d13-f58b-4a41-bede-0b343fcd1ac0","iat": 1438704128,"sub": "https://api.stormpath.com/v1/accounts/5oM4WI3P4xIwp4WiDbRj80","exp": 1438963328
}
注意sub
键。 这就是我验证为的帐户的完整Stormpath URL。 现在,让我们再次点击基本的Hello World端点,仅这次,我们将使用OAuth2访问令牌:
➥ http -v localhost:8080 \
> 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxNDQyNmQxMy1mNThiLTRhNDEtYmVkZS0wYjM0M2ZjZDFhYzAiLCJpYXQiOjE0Mzg3MDQxMjgsInN1YiI6Imh0dHBzOi8vYXBpLnN0b3JtcGF0aC5jb20vdjEvYWNjb3VudHMvNW9NNFdJM1A0eEl3cDRXaURiUmo4MCIsImV4cCI6MTQzODk2MzMyOH0.wcXrS5yGtUoewAKqoqL5JhIQ109s1FMNopL_50HR_t4'
GET / HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxNDQyNmQxMy1mNThiLTRhNDEtYmVkZS0wYjM0M2ZjZDFhYzAiLCJpYXQiOjE0Mzg3MDQxMjgsInN1YiI6Imh0dHBzOi8vYXBpLnN0b3JtcGF0aC5jb20vdjEvYWNjb3VudHMvNW9NNFdJM1A0eEl3cDRXaURiUmo4MCIsImV4cCI6MTQzODk2MzMyOH0.wcXrS5yGtUoewAKqoqL5JhIQ109s1FMNopL_50HR_t4
Connection: keep-alive
Host: localhost:8080
User-Agent: HTTPie/0.9.2HTTP/1.1 200 OK
Content-Length: 11
Content-Type: text/plain;charset=UTF-8
Date: Tue, 04 Aug 2015 16:44:28 GMT
Server: Apache-Coyote/1.1Hello John!
请注意,在输出的最后一行中,消息是通过名称寻址我们的。 现在,我们已经使用OAuth2与Stormpath建立了经过身份验证的会话,控制器中的这些行将检索名字:
Account account = AccountResolver.INSTANCE.getAccount(request);
if (account != null) {name = account.getGivenName();
}
摘要:Java应用程序的令牌认证
在本文中,我们研究了如何使用JWT进行令牌身份验证,不仅解决了传统方法的问题,而且还为我们提供了可检查的元数据和强大的密码签名的好处。
我们概述了OAuth2协议,并详细介绍了Stormpath的OAuth2实现如何使用JWT。
以下是指向基于令牌的身份验证,JWT和Spring Boot的帖子的其他链接:
- Angular.js的基于令牌的身份验证
- JJWT –适用于Java和Android的JSON Web令牌
- Spring Boot Webapp示例快速入门
- JWT规范
建筑物身份管理,包括身份验证和授权? 尝试Stormpath! 我们的REST API和强大的Java SDK支持可以消除您的安全风险,并且可以在几分钟内实现。 注册 ,再也不会建立auth了!
翻译自: https://www.javacodegeeks.com/2016/07/token-authentication-java-applications.html
认证令牌