“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。
每个开发人员都希望能够更快,更有效地进行构建以支持规模。 使用Spring构建微服务架构可以为您的架构增加弹性和弹性,这将使其优雅地失效并无限扩展。
借助Spring Security及其OAuth 2.0支持,您还可以获得锁定API网关以及后端服务器所需的一切。 您可以将其设置为自动将访问令牌从一个应用程序传播到另一个应用程序,以确保在此过程中所有内容保持安全和加密。
本教程向您展示如何将Spring Security与OAuth 2.0和Okta结合使用来锁定您的微服务架构。
带有Spring Boot + Spring Cloud的微服务架构
本教程将向您展示如何为我之前写的教程“ 使用Spring Boot为Microbrews构建微服务体系结构”增加安全性。 带有Spring Boot和Spring Cloud的基本微服务架构如下图所示。
完成本教程后,您将获得Spring Security锁定的一切,Okta将为OAuth提供授权。 您的边缘服务(也称为API网关)将具有一个Feign客户端和一个处理正常故障转移的Hystrix,该客户端将随您的访问令牌一起传递。
首先,您需要克隆上述文章的已完成项目。
git clone https://github.com/oktadeveloper/spring-boot-microservices-example.git
在Okta中创建Web应用程序
如果您还没有,请创建一个永久免费的Okta Developer帐户 。 完成设置过程后,登录到您的帐户并导航至Applications > Add Application 。 单击Web , 然后单击下一步 。 在下一页上,输入以下值,然后单击完成 。
- 应用名称:
Spring OAuth
- 基本URI:
http://localhost:8081
- 登录重定向URI:
http://localhost:8081/login
记下clientId和client机密值,因为它们将用于配置Spring Boot应用程序。
您需要在ID令牌中添加一个roles
声明,以便将Okta中的组转换为Spring Security机构。 在Okta开发人员控制台中,导航到API > 授权服务器 ,单击授权服务器选项卡并编辑默认选项卡。 点击索赔标签,然后添加索赔 。 将其命名为“角色”,并将其包含在ID令牌中。 将值类型设置为“ Groups”,并将过滤器设置为.*
的正则表达式。
将Spring Security OAuth添加到边缘服务应用程序
边缘服务应用程序处理与beer-catalog-service
的通信,因此它是开始集成OAuth的最佳位置。 在edge-service/pom.xml
,添加Spring Security的依赖关系,其OAuth支持和JWT支持。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency><groupId>org.springframework.security.oauth.boot</groupId><artifactId>spring-security-oauth2-autoconfigure</artifactId><version>2.0.1.RELEASE</version>
</dependency>
将以下Zuul路由添加到edge-service/src/main/resources/application.properties
。
zuul.routes.beer-catalog-service.path=/beers
zuul.routes.beer-catalog-service.url=http://localhost:8080zuul.routes.home.path=/home
zuul.routes.home.url=http://localhost:8080
打开edge-service/src/main/java/com/example/edgeservice/EdgeServiceApplication.java
并添加@EnableOAuth2Sso
以启用OAuth身份验证。
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
...
@EnableOAuth2Sso
@SpringBootApplication
public class EdgeServiceApplication {
添加@EnableOAuth2Sso
导致Spring Security查找大量属性。 将以下属性添加到edge-service/src/main/resources/application.properties
。
security.oauth2.client.client-id={yourClientId}
security.oauth2.client.client-secret={yourClientSecret}
security.oauth2.client.access-token-uri=https://{yourOktaDomain}.com/oauth2/default/v1/token
security.oauth2.client.user-authorization-uri=https://{yourOktaDomain}.com/oauth2/default/v1/authorize
security.oauth2.client.scope=openid profile email
security.oauth2.resource.user-info-uri=https://{yourOktaDomain}.com/oauth2/default/v1/userinfo
security.oauth2.resource.token-info-uri=https://{yourOktaDomain}.com/oauth2/default/v1/introspect
security.oauth2.resource.prefer-token-info=false
提示:如果在上面的代码片段中看到{yourOktaDomain}
,请登录到Okta帐户并刷新此页面。 它将用您的域替换该值。
将ResourceServerConfig.java
类添加到与EdgeServiceApplication
相同的包中。
package com.example.edgeservice;import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {@Overridepublic void configure(HttpSecurity http) throws Exception {http.requestMatcher(new RequestHeaderRequestMatcher("Authorization")).authorizeRequests().antMatchers("/**").authenticated();}
}
至此,您已经完成了足以登录到Edge Service应用程序的配置,但是它无法与下游beer-catalog-service
进行通信。
将Spring Security OAuth添加到Beer Catalog Service
在beer-catalog-service/pom.xml
,添加与添加到Edge Service相同的依赖项,以及Thymeleaf的依赖项。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency><groupId>org.springframework.security.oauth.boot</groupId><artifactId>spring-security-oauth2-autoconfigure</artifactId><version>2.0.1.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
将相同的属性添加到beer-catalog-service/src/main/resources/application.properties
。
security.oauth2.client.client-id={yourClientId}
security.oauth2.client.client-secret={yourClientSecret}
security.oauth2.client.access-token-uri=https://{yourOktaDomain}.com/oauth2/default/v1/token
security.oauth2.client.user-authorization-uri=https://{yourOktaDomain}.com/oauth2/default/v1/authorize
security.oauth2.client.scope=openid profile email
security.oauth2.resource.user-info-uri=https://{yourOktaDomain}.com/oauth2/default/v1/userinfo
security.oauth2.resource.token-info-uri=https://{yourOktaDomain}.com/oauth2/default/v1/introspect
security.oauth2.resource.prefer-token-info=false
提示:添加这些属性的替代方法是使用环境变量。 例如, SECURITY_OAUTH2_CLIENT_CLIENT_ID
将是用于指定security.oauth2.client.client-id
的环境变量。 使用环境变量将允许您从一个位置更改两个应用程序的设置。
在beer-catalog-service/src/main/java/com/example/beercatalogservice/HomeController.java
创建一个HomeController
来呈现用户信息,以便您可以验证身份验证是否正常。
package com.example.beercatalogservice;import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;import java.security.Principal;
import java.util.Map;@Controller
public class HomeController {@GetMapping("/home")@SuppressWarnings("unchecked")public String howdy(Model model, Principal principal) {OAuth2Authentication authentication = (OAuth2Authentication) principal;Map<String, Object> user = (Map<String, Object>) authentication.getUserAuthentication().getDetails();model.addAttribute("user", user);return "home";}
}
在beer-catalog-service/src/main/resources/templates/home.html
创建一个home.html
模板,并使用以下代码填充它。
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head><style>th {text-align: left;}td {white-space: nowrap;}td:first-child {font-family: "Courier", monospace;font-size: 0.9em;color: #343434;}</style>
</head>
<body>
<h1>Hello<span th:if="${user}" th:text="' ' + ${user.name}"> Joe</span>!</h1>
<div th:unless="${user}"><a th:href="@{/login}">Login</a>
</div>
<div th:if="${user}"><form id="logoutForm" th:action="@{/logout}" method="post"><input type="submit" value="Logout"/></form>
</div><h2>User Properties</h2>
<table><thead><tr><th>Name</th><th>Value</th></tr></thead><tbody><tr><td>sub</td><td th:text="${user.sub}"></td></tr><tr><td>name</td><td th:text="${user.name}"></td></tr><tr><td>given_name</td><td th:text="${user.given_name}"></td></tr><tr><td>family_name</td><td th:text="${user.family_name}"></td></tr><tr><td>preferred_username</td><td th:text="${user.preferred_username}"></td></tr><tr><td>email</td><td th:text="${user.email}"></td></tr><tr><td>roles</td><td th:text="${user.roles}"></td></tr></tbody>
</table>
</body>
</html>
在与HomeController
相同的包中创建ResourceServerConfig.java
类。 此类配置Spring Security,因此可以保护所有端点,但那些通过Authorization
标头访问的端点除外。
package com.example.beercatalogservice;import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {@Overridepublic void configure(HttpSecurity http) throws Exception {http.requestMatcher(new RequestHeaderRequestMatcher("Authorization")).authorizeRequests().anyRequest().fullyAuthenticated();}
}
添加伪装的RequestInterceptor
用于与beer-catalog-service
对话的@FeignClient
Authorization
标头。 为了使其UserFeignClientInterceptor
,请在与EdgeServiceApplication
相同的目录中创建UserFeignClientInterceptor
类。
package com.example.edgeservice;import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Component;@Component
public class UserFeignClientInterceptor implements RequestInterceptor {private static final String AUTHORIZATION_HEADER = "Authorization";private static final String BEARER_TOKEN_TYPE = "Bearer";@Overridepublic void apply(RequestTemplate template) {SecurityContext securityContext = SecurityContextHolder.getContext();Authentication authentication = securityContext.getAuthentication();if (authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE, details.getTokenValue()));}}
}
注册为@Bean
里面EdgeServiceApplication
类。
import feign.RequestInterceptor;
...
public class EdgeServiceApplication {public static void main(String[] args) {SpringApplication.run(EdgeServiceApplication.class, args);}@Beanpublic RequestInterceptor getUserFeignClientInterceptor() {return new UserFeignClientInterceptor();}
}
为了使Hystrix了解安全上下文,您需要在edge-service/src/main/resources/application.properties
添加两个属性 :
feign.hystrix.enabled=true
hystrix.shareSecurityContext=true
验证安全通信
您可以通过启动所有Spring Boot应用程序来验证edge-service
和beer-catalog-service
之间的通信。 首先,启动eureka-service
:
cd eureka-service
./mvnw spring-boot:run
在新的终端窗口中,启动beer-catalog-service
:
cd beer-catalog-service
./mvnw spring-boot:run
在另一个终端窗口中,启动edge-service
:
cd edge-service
./mvnw spring-boot:run
打开浏览器并导航到http://localhost:8081/good-beers
。 您应该被重定向到Okta域,并看到一个登录页面,提示您输入凭据。
输入您用来创建帐户的凭据,结果将看到一列优质啤酒。
如果您尝试导航到http://localhost:8081/home
,它将无法正常工作。 这是因为您需要将Spring Cloud Security添加到edge-service/pom.xml
以中继Zuul代理的访问令牌。
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-security</artifactId>
</dependency>
没有这种依赖性,对/good-beers
请求将可以工作(因为已配置Feign),但对/home
请求将不会(因为Zuul需要Spring Cloud Security)。
重新启动边缘服务器应用程序,导航到http://localhost:8081/home
,您将在下一页看到您的用户详细信息。
在Spring Boot 2.0中保护下游服务
使用Spring Boot 1.5.x,将Actuator作为依赖项包括在内将触发Actuator Security并使其受到保护,从而保护了http://localhost:8080
。 在Spring Boot 2.x中,拥有WebSecurityConfigurerAdapter
会导致执行器安全性降低。 在Beer Catalog Service应用程序中, ResourceServerConfig
导致此行为。
要确保执行器端点安全并使其无法直接访问http://localhost:8080
,请在beer-catalog-service/src/main/resources/application.properties
添加要公开的端点:
management.endpoints.web.exposure.include=beans,mappings
然后创建一个SecurityConfig
类(与ResourceServerConfig
放在同一包中)。
package com.example.beercatalogservice;import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ADMIN").anyRequest().authenticated().and().httpBasic();}
}
进行这些更改后,重新启动beer-catalog-service
并见证其保护。
注意:由于出现403错误,我无法使注销按钮正常工作。 我尝试在边缘服务应用程序csrf().requireCsrfProtectionMatcher(r -> false)
到ResourceServerConfig
,但这没有帮助。 我给Spring Security团队发送了一封电子邮件,询问他们是否有任何建议。
将Okta的登录小部件添加到Angular客户端
要使用Okta的登录小部件,您需要在Okta中修改您的应用以启用“ 隐式”授予类型。 登录到您的帐户,导航至“ 应用程序” >“ Spring OAuth” >“ 常规”选项卡,然后单击“ 编辑” 。 在“ 允许的授予类型”下启用“ 隐式(混合)” ,并选中其下方的两个复选框。 在登录重定向URI下添加http://localhost:4200
,然后点击保存 。
为了使“登录小部件”向该应用程序发出请求,您还需要将客户端URL配置为可信来源。 单击API > 可信 来源 > 添加来源 。 输入http://localhost:4200
作为原始URL,并选中其下方的两个复选框。
打开一个终端,导航到spring-boot-microservices-example/client
,然后使用npm安装客户端的依赖项。
cd client
npm install
安装Okta的登录小部件 ,使其可以与受保护的服务器进行通信。
npm install @okta/okta-signin-widget --save
将小部件CSS添加到client/src/styles.css
:
@import '~@okta/okta-signin-widget/dist/css/okta-sign-in.min.css';
@import '~@okta/okta-signin-widget/dist/css/okta-theme.css';
创建client/src/app/shared/okta/okta.service.ts
并使用它来配置小部件以与您的Okta租户对话。 请确保在下面的代码中替换{yourOktaDomain}
和{clientId}
。
import { Injectable } from '@angular/core';
import * as OktaSignIn from '@okta/okta-signin-widget';@Injectable()
export class OktaService {widget;constructor() {this.widget = new OktaSignIn({baseUrl: 'https://{yourOktaDomain}.com',clientId: '{yourClientId}',authParams: {issuer: 'default',responseType: ['id_token', 'token'],scopes: ['openid', 'email', 'profile']}});}getWidget() {return this.widget;}getIdToken() {return this.widget.tokenManager.get('idToken');}getAccessToken() {return this.widget.tokenManager.get('accessToken');}
}
将OktaService
作为提供程序添加到client/src/app/app.module.ts
。
import { OktaService } from './shared/okta/okta.service';@NgModule({...providers: [OktaService],bootstrap: [AppComponent]
})
export class AppModule { }
修改client/src/app/shared/beer/beer.service.ts
以读取访问令牌,并将其设置在Authorization
标头中(如果存在)。
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { OktaService } from '../okta/okta.service';@Injectable()
export class BeerService {constructor(private http: HttpClient, private oktaService: OktaService) {}getAll(): Observable {let headers: HttpHeaders = new HttpHeaders();if (this.oktaService.getAccessToken()) {const accessToken = this.oktaService.getAccessToken();// headers is immutable, so re-assignheaders = headers.append('Authorization', accessToken.tokenType + ' ' + accessToken.accessToken);}return this.http.get('http://localhost:8081/good-beers', {headers: headers});}
}
修改app.component.html
,为小部件添加一个占位符,并显示一个部分,以显示用户名和注销按钮。
<mat-toolbar color="primary"><span>Welcome to {{title}}!</span>
</mat-toolbar><!-- Container to inject the Sign-In Widget -->
<div id="okta-signin-container"></div><div *ngIf="user"><h2>Welcome {{user?.name}}!</h2><button mat-raised-button (click)="logout()">Logout</button><app-beer-list></app-beer-list>
</div>
您会注意到HTML中的user
变量。 要解决此问题,您需要更改client/src/app/app.component.ts
以使其呈现登录小部件。 Angular的ChangeDetectorRef
用于在事物发生更改并且渲染需要处理更新的变量时通知Angular。
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { OktaService } from './shared/okta/okta.service';@Component({selector: 'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {title = 'app';user;signIn;constructor(private oktaService: OktaService, private changeDetectorRef: ChangeDetectorRef) {this.signIn = oktaService.getWidget();}showLogin() {this.signIn.renderEl({el: '#okta-signin-container'}, (response) => {if (response.status === 'SUCCESS') {response.forEach(token => {if (token.idToken) {this.signIn.tokenManager.add('idToken', token);this.user = this.getUser(token);}if (token.accessToken) {this.signIn.tokenManager.add('accessToken', token);}});this.signIn.remove();this.changeDetectorRef.detectChanges();}});}getUser(token) {return {name: token.claims.name,email: token.claims.email,username: token.claims.preferred_username};}ngOnInit() {this.signIn.session.get((response) => {if (response.status !== 'INACTIVE') {const token = this.oktaService.getIdToken();this.user = this.getUser(token);this.changeDetectorRef.detectChanges();} else {this.showLogin();}});}logout() {this.signIn.signOut(() => {this.user = undefined;this.changeDetectorRef.detectChanges();this.showLogin();});}
}
为了使BeerListComponent
(在src/app/beer-list/beer-list.component.ts
)来检测你已经登录,您需要使用添加在构造函数依赖ChangeDetectorRef
并调用它的detectChanges()
方法时您可以在每种beer
上设置giphyUrl
属性。
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { BeerService, GiphyService } from '../shared';@Component({selector: 'app-beer-list',templateUrl: './beer-list.component.html',styleUrls: ['./beer-list.component.css'],providers: [BeerService, GiphyService]
})
export class BeerListComponent implements OnInit {beers: Array<any>;constructor(private beerService: BeerService, private giphyService: GiphyService,private changeDetectorRef: ChangeDetectorRef) { }ngOnInit() {this.beerService.getAll().subscribe(data => {this.beers = data;for (const beer of this.beers) {this.giphyService.get(beer.name).subscribe(url => {beer.giphyUrl = url;this.changeDetectorRef.detectChanges();});}},error => console.log(error))}
}
验证身份验证作品
通过打开终端,导航到client
目录,然后运行npm start
client
。 将浏览器打开到http://localhost:4200
,您应该看到类似以下的登录表单。
如果要调整表单的样式,以免它与顶部工具栏不对,请在styles.css
添加以下内容。
#okta-signin-container {margin-top: 25px;
}
您应该能够登录,看到欢迎消息以及注销按钮。 但是,由于控制台中出现以下错误,您不会看到啤酒清单。
Failed to load http://localhost:8081/good-beers: Response for preflight is invalid (redirect)
发生这种情况是因为Spring Security无法识别/good-beers
端点上的@CrossOrigin
批注。 要解决此问题,请向EdgeServiceApplication
添加一个simpleCorsFilter
。
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;import java.util.Collections;
...
public class EdgeServiceApplication {public static void main(String[] args) {SpringApplication.run(EdgeServiceApplication.class, args);}@Beanpublic RequestInterceptor getUserFeignClientInterceptor() {return new UserFeignClientInterceptor();}@Beanpublic FilterRegistrationBean simpleCorsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();CorsConfiguration config = new CorsConfiguration();config.setAllowCredentials(true);config.setAllowedOrigins(Collections.singletonList("*"));config.setAllowedMethods(Collections.singletonList("*"));config.setAllowedHeaders(Collections.singletonList("*"));source.registerCorsConfiguration("/**", config);FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));bean.setOrder(Ordered.HIGHEST_PRECEDENCE);return bean;}
}
重新启动边缘服务应用程序,然后重试。 这次您应该取得巨大的成功!
注意:如果在生产中使用此配置,则应将允许的来源从*
更改为客户的URL。
部署到Cloud Foundry
要使用Pivotal Web Services在Cloud Foundry上部署所有内容,您需要创建一个帐户,下载/安装Cloud Foundry CLI并登录(使用cf login -a api.run.pivotal.io
)。
部署所有服务和Angular客户端要进行生产涉及很多步骤。 因此,我编写了一个deploy.sh
脚本来自动执行所有操作。
注意:该脚本完成后,您必须将客户端的URL作为登录重定向URI添加到Okta应用中。 您还需要在API > Trusted Origins下将其添加为源 。
提示:如果收到错误消息,说明您使用的内存过多,则可能必须升级Cloud Foundry订阅。
进一步了解Spring Boot,OAuth 2.0和微服务
本文向您展示了如何使用Spring Security,OAuth和Okta保护微服务架构。 借助Zuul,Feign和Spring Cloud Security,您可以确保后端服务安全地通信。
本教程的源代码位于GitHub的“ oauth”分支中 。
git clone https://github.com/oktadeveloper/spring-boot-microservices-example.git
git checkout oauth
本教程向您展示了如何在上一教程“ 使用Spring Boot为Microbrews构建微服务架构 ”中增加安全性。
如果您有兴趣了解Spring Security和OAuth 2.0的未来,请参阅我们的Spring Security团队的好朋友Joe Grandja提供的有关Spring Security的下一代OAuth 2.0支持 。
此外,JHipster对其OAuth支持使用相同的设置。 如果您对将Okta与JHipster结合使用感兴趣,建议您阅读以下博客文章:
- 使用OAuth 2.0和JHipster开发微服务架构
- 使用Ionic for JHipster创建具有OIDC身份验证的移动应用程序
在developer.okta.com/product上了解有关Okta及其API的更多信息。 如果您对本教程有疑问,请在下面发表评论或在Twitter @mraible上打我。
变更日志:
- 2018年5月11日:更新为使用Spring Boot 2.0和Okta登录小部件2.0.8。 请参阅spring-boot-microservices-example#17中的示例应用程序更改; 可以在okta.github.io#2049中查看对此帖子的更改。
“我喜欢编写身份验证和授权代码。” 〜从来没有Java开发人员。 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证。
使用Spring Security和OAuth 2.0保护Spring微服务体系结构最初于2018年2月13日发布在Okta开发者博客上。
翻译自: https://www.javacodegeeks.com/2018/05/secure-a-spring-microservices-architecture-with-spring-security-and-oauth-2-0.html