利用OAuth2.0来构建一个基于Spring Boot的安全的端到端通信应用
Posted SpringForAll社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用OAuth2.0来构建一个基于Spring Boot的安全的端到端通信应用相关的知识,希望对你有一定的参考价值。
原文:https://dzone.com/articles/build-a-spring-boot-app-with-secure-server-to-serv
译者:anLA7856
如何利用OAuth2.0客户端授权方式来构建一个端到端的Spring Boot应用呢?
前言: 大多数OAuth2.0的指引都以用户为出发点,例如,通过Google,Github,Okta等等方式登录。虽然这可能会很有用,但是它忽略了服务器到服务器的通信(这里没有用户),并且你只有一个服务连接到另一个服务器,Okta在这里帮助你实现有关这一块的应用安全。
OAuth2.0客户端授权方式专门应用于无用户场景的认证方式,例如CRON任务,计划任务,其他的数据工作负载等。由于没有终端用户或者浏览器介入,这种方式授权比其他的OAuth授权方式更加少见。但是,相比于复杂的以用户为中心的OAuth2.0授权模式,这种方式更加容易理解。在这片文章中,我将介绍OAuth2.0客户端授权协议,并且讲授如何在Spring Boot中应用它实现一个安全的服务端到服务端的通信。
OAuth 2.0客户端授权
客户端授权模式的目的是允许两个机器中间安全的通信。在这个模式中,你有一个端(可以认为是你的应用)向另一个应用发起API请求(可以认为是你的资源服务器)。 为了帮助说明这样模式的重要性,让我们先穿越一下,看看在AAuth2.0以前我们是如何实现上述通信的。
Note:如果你已经对OAuth 2.0有了一定了解,你可以跳过这一节,直接进入到下一段代码示例部分,并且从GitHub中check out。
在OAuth 2.0之前,开发人员是利用基于HTTP的认证方式解决端到端认证。本质上来说,就是把用户名密码作为请求的一部分发送给另一个API应用,这个API应用然后会校验每一次请求的用户名密码来判定是否授权
这样的方式有以下的缺点和暴露点:
以上图表中的每一个应用都处理用户名密码
二次用户名密码可能要连接到存储用户信息的地方
相同的用户名密码在每次请求中都用到
有众多不同的方式来减轻这样的风险,但是这超出了这片文章的范围。
OAuth 2.0客户端授权的出现,是为了解决基于HTTP的基本授权模式,尽管我们仍然使用用户名和密码(称为clientid和clientsecret),通过传输由中间认证服务器产生的token,而不是每次都传输用户名密码。
认证服务器返回一个临时性的token(过期时间内有效)。当客户端与资源服务器交互时利用这个token,这意味着最敏感的数据(ID和secret)仅仅在过期时间内才在互联网上共享一次,这极大的降低了损害的可能性。
一旦资源服务器收到了带有token的来访请求,它会通过和认证服务器交互来验证这个token。 我会在文章的结尾讨论一些减少网络调用数量的方法,但首先,要介绍一个例子。
构建一个OAuth 2.0客户端授权方式应用
够了,让我们做点什么吧!我将像你们展示如何实现基于Spring的客户端授权类型的例子,它包括client和server。server端将只包含一个端点返回字符串“message of the day.”。client端将会是一个命令行应用,你可以用后端的Web应用程序,CRON作业或任何其他后端脚本轻松替代它。
建立你的认证服务器
为了让事情变得更简单,你可以使用Okta作为你的OAuth 2.0 认证服务器。这样将会解决所有以上提到的客户端授权方式所需要的工作。你需要用到Okta吗?一点也不!你可以使用任何你想要的OAuth 2.0的服务器。因为我们所要实现的服务是免费并且易于使用的,这样的方式加快了我们的进度。
如果你还没有一个免费的开发者帐号,去https://developer.okta.com/ 申请一个吧,为了完成之后,你需要两个信息:
基于Okta的URL,例如dev-123456.oktapreview.com
当激活了你的Okta帐号之后,你会处于开发者控制台界面,此时你需要创建一个应用以及一个用户OAuth范围(scope)。Okta将会给你应用一个client_id和secret,然而scope将会限制你的token所能获取到应用的资源。
点击Applications ,然后依次选择Service -> Next ,然后你可以讲名字改成你想要的(我打算改成“My MOD App”),然后,点击Done 。
下一步,你将会用到client_id和secret。 然后,为你的应用创建一个custom scope (权限范围)。
从菜单开始,依次选择 API -> Authorization Servers 。记住Issuer URI 的值,你需要这个值来完成下一步的操作。
通过单击铅笔来编辑认证服务器,然后依次点击Scopes -> Add Scope ,将name框填为custom_mod,然后点击create 。
现在,到有趣的东西了!
创建一个资源服务器
资源服务器(即API服务)将会十分简单,并且只有一个端点 /mod
组成,现在让我们用 Spring Initializer 来创建一个项目吧!
curl https://start.spring.io/starter.tgz \
-d artifactId=creds-example-server \
-d dependencies=security,web \
-d language=java \
-d type=maven-project \
-d baseDir=creds-example-server \
| tar -xzvf -
# change into the new directory
cd creds-example-server
另外,你需要手工的加入以下的依赖到pom.xml中:
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
NOTE: 从Spring Initializer上面获取的项目,我把名字由 DemoApplication
改为了 ServerApplication
,因为我们将要立刻创建另外一个项目。 下一步,给 ServerApplication
加上 @EnableResourceServer
注解。并且实现一个简单的REST控制器。
@EnableResourceServer
@SpringBootApplication
public class ServerApplication {
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
/**
* Allows for @PreAuthorize annotation processing.
*/
@EnableGlobalMethodSecurity(prePostEnabled = true)
protected static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
@RestController
public class MessageOfTheDayController {
@GetMapping("/mod")
@PreAuthorize("#oauth2.hasScope('custom_mod')")
public String getMessageOfTheDay(Principal principal) {
return "The message of the day is boring for user: " + principal.getName();
}
}
}
现在,该配置文件了!我把application.propertis改为了application.yml 。并且做了如下更新:
security:
oauth2:
client:
clientId: {client-id-from-above}
clientSecret: {client-secret-from-above}
resource:
tokenInfoUri: {issuer-uri-from-above}/v1/introspect
就是这样,只需要几行代码的配置,Spring Boot自动的完成对token的校验,你所需要做的就是专注于你的代码。
下一步,启动并运行它。 你可以访问http://localhost:8080/mod 。但是它会返回401UNAUTHORIZED
创建OAuth 2.0客户端
下一步,将创建一个简单的命令行应用,你能够轻易的在任何应用中使用它。 现在,打开一个命令终端,并利用Spring Initializer 创建第二个应用:
curl https://start.spring.io/starter.tgz \
-d artifactId=creds-example-client \
-d dependencies=security \
-d language=java \
-d type=maven-project \
-d baseDir=creds-example-client \
| tar -xzvf -
# change into the new directory
cd creds-example-client
然后,和之前一样,往pom文件里面加入Spring OAuth 2.0 依赖:
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
此时,在如下配置文件下,我启动了应用(我同样把 application.properties
改为了 application.yml
):
example:
baseUrl: http://localhost:8080
oauth2:
client:
grantType: client_credentials
clientId: {client-id-from-above}
clientSecret: {client-secret-from-above}
accessTokenUri: {issuer-uri-from-above}/v1/token
scope: custom_mod
我设置配置文件的命名空间为 example
,因为你可能会去连接不同的服务端。 我同时还定义了以下的属性:
baseUrl
:我们示例服务端的主页grantType
:连接通信的授权模式clientId
和clientSecret
:和上面授权服务器一致的账号密码accessTokenUri
:定义用来获取授权的URIscope
:我们在上面创建的用户权限范围
最后是我们的 ClientApplication
(原先为 DemoApplication
,更改为 ClientApplication
)
@SpringBootApplication
public class ClientApplication implements CommandLineRunner {
private final Logger logger = LoggerFactory.getLogger(ClientApplication.class);
@Value("#{ @environment['example.baseUrl'] }")
private String serverBaseUrl;
public static void main(String[] args) {
SpringApplication.run(ClientApplication.class, args);
}
@Bean
@ConfigurationProperties("example.oauth2.client")
protected ClientCredentialsResourceDetails oAuthDetails() {
return new ClientCredentialsResourceDetails();
}
@Bean
protected RestTemplate restTemplate() {
return new OAuth2RestTemplate(oAuthDetails());
}
@Override
public void run(String... args) {
logger.info("MOD: {}", restTemplate().getForObject(serverBaseUrl + "/mod", String.class));
}
}
以上代码我有以下几点说明以下:
CommandLineRunner
接口提供了一个run
方法,当初始化后,它将自动执行,执行完这个方法,应用程序退出。我创建了一个
ClientCredentialsResourceDetails
的bean,它绑定了我的配置文件中的前缀:example.oauth2.client
我使用OAuth2RestTemplate替代标准的RestTemplate。它将自动管理所有的OAuth2.0的token交换,并设置身份认证:设置请求头的值。总的来说,它处理了所有的OAuth认证的细节,所以你不用担心里面任意一点。
下一步,执行以下命令 ./mvnw spring-boot:run
启动这个应用,你应该可以看到相似的一条输出:
2018-03-20 12:56:10.058 INFO 15833 --- [main] c.e.c.ClientApplication: MOD: The message of the day is boring for user: 0oabcd12yz2EpHuis75s3
客户端成功的与服务端通信了!不算差是吧?仅仅几行代码,你就能够配置和运行一个OAuth2.0认证服务器,同样也创建了两个可以基于OAuth2.0客户端授权模式的Spring 应用(一个客户端一个服务端),它们可以安全的通信。
NOTE:如果你遇到了 401
或者 500
错误,再一次检查 application.yml
是否配置正确。
额外知识-减少对授权服务器调用次数
第二个序列图看起来似乎比第一个更复杂点,即使考虑访问令牌的重试情况。token格式是不确定的,没有具体定义,这个格式由特定的授权服务器自己去实现。 在Okta中,我们使用签名JWTs,这意味着你能够在本地验证这些token,而不是每次收到请求后都要再一次请求授权服务器去验证。
我们已经实现了一些语言的库,以及一个Spring Boot starter ,用它们来帮助你解决token本地认证。
NOTE:在本文创作时, okta-spring-boot
仅仅基于 SpringBoot1.5.x
。你可以从GitHub中获得一个例子。
学习更多OAuth 2.0和Okta
这篇文章中,我结识了OAuth2.0客户端授权方式,并且创建了一个小的demo来实践这个流程(有了Spring Boot的帮助,几行代码就解决了!)。如果你有任何问题,直接在下方(原文)评论或者在Twitter上@我(@briandemers)或者@OktaDev 。
获取更多关于OAuth2.0和Okta的信息,请访问以下资源:
What the Heck is OAuth?
OAuth.com
Secure your SPA with Spring Boot and OAuth
本文原文同样也已经发布在Okta开发者博客2018年4月2日:https://developer.okta.com/blog/2017/10/27/secure-spa-spring-boot-oauth
推荐:
上一篇:
关注公众号
以上是关于利用OAuth2.0来构建一个基于Spring Boot的安全的端到端通信应用的主要内容,如果未能解决你的问题,请参考以下文章
Spring Security 实战干货: 简单的认识 OAuth2.0 协议
基于Spring oauth2.0统一认证登录,返回自定义用户信息