javascript
构建Spring微服务并对其进行Dockerize生产
“我喜歡編寫(xiě)身份驗(yàn)證和授權(quán)代碼。” ?從來(lái)沒(méi)有Java開(kāi)發(fā)人員。 厭倦了一次又一次地建立相同的登錄屏幕? 嘗試使用Okta API進(jìn)行托管身份驗(yàn)證,授權(quán)和多因素身份驗(yàn)證。
在這篇文章中,您將學(xué)習(xí)微服務(wù)架構(gòu)以及如何使用Spring Boot來(lái)實(shí)現(xiàn)它。 在使用該技術(shù)創(chuàng)建了一些項(xiàng)目之后,您將把工件部署為Docker容器,并使用Docker Compose進(jìn)行簡(jiǎn)化以模擬容器編排器 (例如Kubernetes)。 錦上添花的是使用Spring Profiles進(jìn)行身份驗(yàn)證集成。 您將了解如何通過(guò)生產(chǎn)資料啟用它。
但是首先,讓我們談?wù)勎⒎?wù)。
了解現(xiàn)代微服務(wù)架構(gòu)
與整體架構(gòu)相反,微服務(wù)要求您將應(yīng)用程序分成邏輯上相關(guān)的小塊。 這些片段是獨(dú)立的軟件,例如,可以使用HTTP或消息與其他片段進(jìn)行通信。
有一些關(guān)于微型尺寸的討論。 有人說(shuō)微服務(wù)是可以在單個(gè)沖刺中創(chuàng)建的軟件。 其他人則說(shuō),如果微服務(wù)在邏輯上相關(guān)(例如,您不能混合使用蘋(píng)果和橙子),則微服務(wù)的規(guī)模可能會(huì)更大。 我同意馬丁·福勒 ( Martin Fowler)的觀點(diǎn),認(rèn)為尺寸并沒(méi)有多大關(guān)系,它與款式息息相關(guān)。
微服務(wù)有許多優(yōu)點(diǎn):
- 耦合風(fēng)險(xiǎn)不高 –由于每個(gè)應(yīng)用程序都處于不同的進(jìn)程中,因此無(wú)法創(chuàng)建相互對(duì)話(huà)的類(lèi)。
- 輕松擴(kuò)展 –如您所知,每項(xiàng)服務(wù)都是獨(dú)立的軟件。 因此,它可以按需擴(kuò)展或縮小。 此外,由于代碼比整體代碼小 ,因此啟動(dòng)速度可能更快。
- 多個(gè)堆棧 –您可以為每個(gè)服務(wù)使用最佳的軟件堆棧。 例如,當(dāng)Python對(duì)您正在構(gòu)建的東西更好時(shí),就不再需要使用Java。
- 更少的合并和代碼沖突 –由于每個(gè)服務(wù)都是一個(gè)不同的存儲(chǔ)庫(kù),因此更易于處理和檢查提交。
但是,有一些缺點(diǎn):
- 您有一個(gè)新的敵人- 網(wǎng)絡(luò)問(wèn)題 。 服務(wù)啟動(dòng)了嗎? 如果服務(wù)中斷,該怎么辦?
- 復(fù)雜的部署過(guò)程 – OK CI / CD在這里,但是您現(xiàn)在為每個(gè)服務(wù)只有一個(gè)工作流程。 如果他們使用不同的堆棧,則可能甚至無(wú)法為每個(gè)堆棧復(fù)制工作流程。
- 更復(fù)雜且難以理解的體系結(jié)構(gòu) –它取決于您的設(shè)計(jì)方式,但請(qǐng)考慮以下問(wèn)題:如果您不知道方法的作用,則可以閱讀其代碼。 在微服務(wù)體系結(jié)構(gòu)中,此方法可能在另一個(gè)項(xiàng)目中,甚至可能沒(méi)有代碼。
如今,通常應(yīng)該首先避免使用微服務(wù)架構(gòu) 。 經(jīng)過(guò)一些迭代后,代碼劃分將變得更加清晰,項(xiàng)目的需求也將變得更加清晰。 在您的開(kāi)發(fā)團(tuán)隊(duì)開(kāi)始進(jìn)行小型項(xiàng)目之前,處理微服務(wù)通常過(guò)于昂貴。
在Spring使用Docker構(gòu)建微服務(wù)
在本教程中,您將構(gòu)建兩個(gè)項(xiàng)目:一個(gè)服務(wù)(school-service)和一個(gè)UI(school_ui)。 該服務(wù)提供持久層和業(yè)務(wù)邏輯,而UI提供圖形用戶(hù)界面。 只需最少的配置即可連接它們。
初始設(shè)置后,我將討論發(fā)現(xiàn)和配置服務(wù)。 兩種服務(wù)都是任何大規(guī)模分布式體系結(jié)構(gòu)的重要組成部分。 為了證明這一點(diǎn),您將其與OAuth 2.0集成在一起,并使用配置項(xiàng)目來(lái)設(shè)置OAuth 2.0密鑰。
最后,每個(gè)項(xiàng)目都將轉(zhuǎn)換為Docker映像。 Docker Compose將用于模擬容器協(xié)調(diào)器,因?yàn)镃ompose將使用服務(wù)之間的內(nèi)部網(wǎng)絡(luò)來(lái)管理每個(gè)容器。
最后,將介紹Spring配置文件以根據(jù)當(dāng)前適當(dāng)分配的環(huán)境來(lái)更改配置。 這樣,您將擁有兩個(gè)OAuth 2.0環(huán)境:一個(gè)用于開(kāi)發(fā),另一個(gè)用于生產(chǎn)。
更少的單詞,更多的代碼! 克隆本教程的資源庫(kù),并檢出start分支。
git clone -b start https://github.com/oktadeveloper/okta-spring-microservices-docker-example.git根pom.xml文件不是pom.xml 。 但是,一次管理多個(gè)項(xiàng)目可能會(huì)有所幫助。 讓我們看看里面:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.okta.developer.docker_microservices</groupId><artifactId>parent-pom</artifactId><version>0.0.1-SNAPSHOT</version><packaging>pom</packaging><name>parent-project</name><modules><module>school-service</module><module>school-ui</module> </modules> </project>這稱(chēng)為聚合項(xiàng)目,因?yàn)樗酆献禹?xiàng)目。 這對(duì)于在所有聲明的模塊上運(yùn)行相同的Maven任務(wù)很有用。 這些模塊無(wú)需將根模塊用作父模塊。
有兩個(gè)模塊可用:學(xué)校服務(wù)和學(xué)校UI。
學(xué)校服務(wù)微服務(wù)
school-service目錄包含一個(gè)Spring Boot項(xiàng)目,該項(xiàng)目充當(dāng)項(xiàng)目的持久層和業(yè)務(wù)規(guī)則。 在更復(fù)雜的情況下,您將擁有更多這樣的服務(wù)。 該項(xiàng)目是使用始終出色的Spring Initializr創(chuàng)建的,并具有以下配置:
- 組– com.okta.developer.docker_microservices
- 神器– school-service
- 依賴(lài)關(guān)系– JPA,Web,Lombok,H2
您可以閱讀PostgreSQL,Flyway和JSONB的Spring Boot以獲得有關(guān)此項(xiàng)目的更多詳細(xì)信息。 總而言之,它具有實(shí)體TeachingClass , Course, Student并使用TeachingClassServiceDB和TeachingClassController通過(guò)REST API公開(kāi)一些數(shù)據(jù)。 要測(cè)試它,請(qǐng)打開(kāi)一個(gè)終端,導(dǎo)航到school-service目錄,然后運(yùn)行以下命令:
./mvnw spring-boot:run該應(yīng)用程序?qū)亩丝?081 (在文件school-service/src/main/resources/application.properties )啟動(dòng),因此您應(yīng)該能夠?qū)Ш降絟ttp://localhost:8081并查看返回的數(shù)據(jù)。
> curl http://localhost:8081 [{"classId":13,"teacherName":"Profesor Jirafales","teacherId":1,"courseName":"Mathematics","courseId":3,"numberOfStudents":2,"year":1988},{"classId":14,"teacherName":"Profesor Jirafales","teacherId":1,"courseName":"Spanish","courseId":4,"numberOfStudents":2,"year":1988},{"classId":15,"teacherName":"Professor X","teacherId":2,"courseName":"Dealing with unknown","courseId":5,"numberOfStudents":2,"year":1995},{"classId":16,"teacherName":"Professor X","teacherId":2,"courseName":"Dealing with unknown","courseId":5,"numberOfStudents":1,"year":1996} ]基于Spring的School UI微服務(wù)
顧名思義,學(xué)校UI是利用學(xué)校服務(wù)的用戶(hù)界面。 它是使用Spring Initializr使用以下選項(xiàng)創(chuàng)建的:
- 組– com.okta.developer.docker_microservices
- 神器– school-ui
- 依存關(guān)系-網(wǎng)絡(luò),仇恨,胸腺,Lombok
UI是一個(gè)單獨(dú)的網(wǎng)頁(yè),列出了數(shù)據(jù)庫(kù)上可用的類(lèi)。 為了獲取信息,它通過(guò)文件school-ui/src/main/resources/application.properties的配置與school-service連接。
service.host=localhost:8081SchoolController類(lèi)具有查詢(xún)服務(wù)的所有邏輯:
package com.okta.developer.docker_microservices.ui.controller;import com.okta.developer.docker_microservices.ui.dto.TeachingClassDto; import org.springframework.beans.factory.annotation.*; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.*; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.ModelAndView; import java.util.List;@Controller @RequestMapping("/") public class SchoolController {private final RestTemplate restTemplate;private final String serviceHost;public SchoolController(RestTemplate restTemplate, @Value("${service.host}") String serviceHost) {this.restTemplate = restTemplate;this.serviceHost = serviceHost;}@RequestMapping("")public ModelAndView index() {return new ModelAndView("index");}@GetMapping("/classes")public ResponseEntity<List<TeachingClassDto>> listClasses(){return restTemplate.exchange("http://"+ serviceHost +"/class", HttpMethod.GET, null,new ParameterizedTypeReference<List<TeachingClassDto>>() {});} }如您所見(jiàn),該服務(wù)有一個(gè)硬編碼的位置。 您可以使用-Dservice.host=localhost:9090這樣的環(huán)境變量來(lái)更改屬性設(shè)置。 盡管如此,它仍必須手動(dòng)定義。 如何擁有許多學(xué)校服務(wù)申請(qǐng)實(shí)例? 在當(dāng)前階段不可能。
啟用school-service后 ,啟動(dòng)school-ui ,并在瀏覽器中瀏覽至http://localhost:8080 :
./mvnw spring-boot:run您應(yīng)該看到如下頁(yè)面:
使用Spring Cloud和Eureka構(gòu)建發(fā)現(xiàn)服務(wù)器
現(xiàn)在,您有了一個(gè)可以使用的服務(wù),該應(yīng)用程序使用兩種服務(wù)將信息提供給最終用戶(hù)。 怎么了 在現(xiàn)代應(yīng)用程序中,開(kāi)發(fā)人員(或操作)通常不知道應(yīng)用程序可能部署在何處或在哪個(gè)端口上。 部署應(yīng)該是自動(dòng)化的,以便沒(méi)有人關(guān)心服務(wù)器名稱(chēng)和物理位置。 (除非您在數(shù)據(jù)中心內(nèi)工作。否則,希望您在意!)
但是,必須有一個(gè)工具來(lái)幫助服務(wù)發(fā)現(xiàn)其對(duì)應(yīng)對(duì)象。 有許多可用的解決方案,對(duì)于本教程,我們將使用Netflix的Eureka ,因?yàn)樗哂谐錾腟pring支持。
返回start.spring.io并創(chuàng)建一個(gè)新項(xiàng)目,如下所示:
- 組: com.okta.developer.docker_microservices
- 神器: discovery
- 依賴(lài)項(xiàng):Eureka Server
編輯主DiscoveryApplication.java類(lèi),以添加@EnableEurekaServer批注:
package com.okta.developer.docker_microservices.discovery;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@SpringBootApplication @EnableEurekaServer public class DiscoveryApplication {public static void main(String[] args) {SpringApplication.run(DiscoveryApplication.class, args);} }并且,您需要更新其application.properties文件,使其在端口8761上運(yùn)行,并且不會(huì)嘗試向其自身注冊(cè)。
spring.application.name=discovery-server server.port=8761 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false讓我們定義每個(gè)屬性:
- spring.application.name –應(yīng)用程序的名稱(chēng),發(fā)現(xiàn)服務(wù)也使用它來(lái)發(fā)現(xiàn)服務(wù)。 您會(huì)看到其他所有應(yīng)用程序也都有一個(gè)應(yīng)用程序名稱(chēng)。
- server.port –服務(wù)器正在運(yùn)行的端口。 Eureka服務(wù)器的默認(rèn)端口8761 。
- eureka.client.register-with-eureka –告訴Spring不要將自己注冊(cè)到發(fā)現(xiàn)服務(wù)中。
- eureka.client .fetch-registry –指示該實(shí)例不應(yīng)從服務(wù)器獲取發(fā)現(xiàn)信息。
現(xiàn)在,運(yùn)行并訪問(wèn)http://localhost:8761 。
./mvnw spring-boot:run上面的屏幕顯示了準(zhǔn)備注冊(cè)新服務(wù)的Eureka服務(wù)器。 現(xiàn)在,該更改學(xué)校服務(wù)和學(xué)校用戶(hù)界面以使用它了。
注意:如果在啟動(dòng)時(shí)收到ClassNotFoundException: javax.xml.bind.JAXBContext錯(cuò)誤,那是因?yàn)槟贘ava 11上運(yùn)行。您可以將JAXB依賴(lài)項(xiàng)添加到pom.xml以解決此問(wèn)題。
<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version> </dependency> <dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>2.3.2</version> </dependency>使用服務(wù)發(fā)現(xiàn)在微服務(wù)之間進(jìn)行通信
首先,添加所需的依賴(lài)關(guān)系很重要。 將以下內(nèi)容添加到pom.xml文件中(在school-service和school-ui項(xiàng)目中):
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>該模塊是Spring Cloud計(jì)劃的一部分,因此,需要一個(gè)新的依賴(lài)關(guān)系管理節(jié)點(diǎn),如下所示(不要忘記將其添加到兩個(gè)項(xiàng)目中):
<dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies> </dependencyManagement>現(xiàn)在,您需要配置兩個(gè)應(yīng)用程序以向Eureka注冊(cè)。
在兩個(gè)項(xiàng)目的application.properties文件中,添加以下行:
eureka.client.serviceUrl.defaultZone=${EUREKA_SERVER:http://localhost:8761/eureka} spring.application.name=school-service不要忘了應(yīng)用程序的名稱(chēng)從改變school-service于school-ui在學(xué)校的UI項(xiàng)目。 注意第一行中有一種新的參數(shù): {EUREKA_SERVER:http://localhost:8761/eureka} 。 這意味著“如果環(huán)境變量EUREKA_SERVER存在,請(qǐng)使用其值,否則請(qǐng)使用默認(rèn)值。” 這在以后的步驟中將很有用。 ;)
你知道嗎? 兩個(gè)應(yīng)用程序都準(zhǔn)備好將自己注冊(cè)到發(fā)現(xiàn)服務(wù)中。 您無(wú)需執(zhí)行任何其他操作。 我們的主要目標(biāo)是學(xué)校用戶(hù)界面項(xiàng)目不需要知道學(xué)校服務(wù)在哪里 。 因此,您需要更改SchoolController (在school-ui項(xiàng)目中)以在其REST端點(diǎn)中使用school-service 。 您也可以在此類(lèi)中刪除serviceHost變量。
package com.okta.developer.docker_microservices.ui.controller;import com.okta.developer.docker_microservices.ui.dto.TeachingClassDto; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.ModelAndView;import java.util.List;@Controller @RequestMapping("/") public class SchoolController {private final RestTemplate restTemplate;public SchoolController(RestTemplate restTemplate) {this.restTemplate = restTemplate;}@RequestMapping("")public ModelAndView index() {return new ModelAndView("index");}@GetMapping("/classes")public ResponseEntity<List<TeachingClassDto>> listClasses() {return restTemplate.exchange("http://school-service/classes", HttpMethod.GET, null,new ParameterizedTypeReference<List<TeachingClassDto>>() {});} }在集成Eureka之前,您已經(jīng)進(jìn)行了配置,指出了學(xué)校服務(wù)的位置。 現(xiàn)在,您已將服務(wù)調(diào)用更改為使用其他服務(wù)使用的名稱(chēng):無(wú)端口,無(wú)主機(jī)名。 您需要的服務(wù)就在某處,您無(wú)需知道在哪里。
學(xué)校服務(wù)可能具有的多個(gè)實(shí)例,并且最好在這些實(shí)例之間進(jìn)行負(fù)載均衡負(fù)載。 幸運(yùn)的是,Spring有一個(gè)簡(jiǎn)單的解決方案:在創(chuàng)建RestTemplate bean時(shí),如下所示添加@LoadBalanced批注。 每當(dāng)您向服務(wù)器提出問(wèn)題時(shí),Spring都會(huì)管理多個(gè)實(shí)例調(diào)用。
package com.okta.developer.docker_microservices.ui;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.config.annotation.*;@SpringBootApplication public class UIWebApplication implements WebMvcConfigurer {public static void main(String[] args) {SpringApplication.run(UIWebApplication.class, args);}@Bean@LoadBalancedpublic RestTemplate restTemplate() {return new RestTemplate();}@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {if(!registry.hasMappingForPattern("/static/**")) {registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/", "classpath:/static/js/");}} }現(xiàn)在,開(kāi)始重新啟動(dòng)school-service和school-ui (并保持Discovery服務(wù)啟動(dòng))。 再次快速瀏覽一下http://localhost:8761 :
現(xiàn)在,您的服務(wù)正在與Discovery服務(wù)器共享信息。 您可以再次測(cè)試該應(yīng)用程序,然后查看它是否可以正常運(yùn)行。 只需在您喜歡的瀏覽器中轉(zhuǎn)到http://localhost:8080 。
將配置服務(wù)器添加到您的微服務(wù)架構(gòu)
盡管此配置有效,但最好刪除項(xiàng)目源代碼中任何配置值的痕跡。 首先,配置URL已從項(xiàng)目中刪除,并由服務(wù)進(jìn)行管理。 現(xiàn)在,您可以使用Spring Cloud Config對(duì)項(xiàng)目中的每個(gè)配置執(zhí)行類(lèi)似的操作。
首先,使用Spring Initializr和以下參數(shù)創(chuàng)建配置項(xiàng)目:
- 組: com.okta.developer.docker_microservices
- 工件: config
- 依賴(lài)項(xiàng):配置服務(wù)器,Eureka發(fā)現(xiàn)
在主類(lèi)中,添加@EnableConfigServer :
package com.okta.developer.docker_microservices.config;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer;@SpringBootApplication @EnableConfigServer public class ConfigApplication {... }在項(xiàng)目的application.properties添加以下屬性和值:
spring.application.name=CONFIGSERVER server.port=8888 spring.profiles.active=native spring.cloud.config.server.native.searchLocations=. eureka.client.serviceUrl.defaultZone=${EUREKA_SERVER:http://localhost:8761/eureka}有關(guān)屬性的一些解釋:
- spring.profiles.active=native表示Spring Cloud Config必須使用本機(jī)文件系統(tǒng)來(lái)獲取配置。 通常使用Git存儲(chǔ)庫(kù),但是為了簡(jiǎn)單起見(jiàn),我們將堅(jiān)持使用本機(jī)文件系統(tǒng)。
- spring.cloud.config.server.native.searchLocations –包含配置文件的路徑。 如果將其更改為硬盤(pán)驅(qū)動(dòng)器上的特定文件夾,請(qǐng)確保并在其中創(chuàng)建school-ui.properties文件。
現(xiàn)在,您需要一些配置和適用于此示例。 Okta的配置如何? 讓我們將school-ui放在授權(quán)層后面,并使用配置項(xiàng)目提供的屬性值。
您可以注冊(cè)一個(gè)永久免費(fèi)的開(kāi)發(fā)人員帳戶(hù) ,該帳戶(hù)使您可以創(chuàng)建所需使用的盡可能多的用戶(hù)和應(yīng)用程序! 創(chuàng)建帳戶(hù)后,在Okta的信息中心中創(chuàng)建一個(gè)新的Web應(yīng)用程序(“ 應(yīng)用程序” >“ 添加應(yīng)用程序” ):
并用以下值填寫(xiě)下一個(gè)表格:
該頁(yè)面將為您返回一個(gè)應(yīng)用程序ID和一個(gè)密鑰。 確保安全,然后在config項(xiàng)目的根文件夾中創(chuàng)建一個(gè)名為school-ui.properties的文件,內(nèi)容如下。 不要忘記填充變量值:
okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default okta.oauth2.clientId={yourClientId} okta.oauth2.clientSecret={yourClientSecret}現(xiàn)在,運(yùn)行config項(xiàng)目并檢查其是否正確獲取了配置數(shù)據(jù):
./mvnw spring-boot:run > curl http://localhost:8888/school-ui.properties okta.oauth2.clientId: YOUR_CLIENT_ID okta.oauth2.clientSecret: YOUR_CLIENT_SECRET okta.oauth2.issuer: https://YOUR_DOMAIN/oauth2/default更改School UI以使用Spring Cloud Config和OAuth 2.0
現(xiàn)在,您需要對(duì)Spring UI項(xiàng)目進(jìn)行一些更改。
首先,您需要更改school-ui/pom.xml并添加一些新的依賴(lài)項(xiàng):
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency><groupId>com.okta.spring</groupId><artifactId>okta-spring-boot-starter</artifactId><version>1.1.0</version> </dependency> <dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency>在com.okta...ui.config包中創(chuàng)建一個(gè)新的SecurityConfiguration類(lèi):
package com.okta.developer.docker_microservices.ui;import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/").permitAll().anyRequest().authenticated().and().logout().logoutSuccessUrl("/").and().oauth2Login();} }更改您的SchoolController以便僅允許具有范圍profile用戶(hù)使用(每位經(jīng)過(guò)身份驗(yàn)證的用戶(hù)都擁有)。
import org.springframework.security.access.prepost.PreAuthorize;....@GetMapping("/classes") @PreAuthorize("hasAuthority('SCOPE_profile')") public ResponseEntity<List<TeachingClassDto>> listClasses(){return restTemplate.exchange("http://school-service/class", HttpMethod.GET, null,new ParameterizedTypeReference<List<TeachingClassDto>>() {}); }一些配置需要在項(xiàng)目啟動(dòng)時(shí)定義。 Spring有一個(gè)聰明的解決方案,可以在上下文啟動(dòng)之前正確定位并提取配置數(shù)據(jù)。 您需要?jiǎng)?chuàng)建一個(gè)文件src/main/resources/bootstrap.yml如下所示:
eureka:client:serviceUrl:defaultZone: ${EUREKA_SERVER:http://localhost:8761/eureka} spring:application:name: school-uicloud:config:discovery:enabled: trueservice-id: CONFIGSERVER引導(dǎo)文件會(huì)創(chuàng)建一個(gè)預(yù)啟動(dòng)的Spring Application Context,用于在實(shí)際應(yīng)用程序啟動(dòng)之前提取配置。 您需要將所有屬性從application.properties移到該文件,因?yàn)镾pring需要知道Eureka Server的位置以及如何搜索配置。 在上面的示例中,您啟用了通過(guò)發(fā)現(xiàn)服務(wù)進(jìn)行配置( spring.cloud.config.discovery.enabled )并指定了配置service-id 。
更改application.properties文件,使其僅具有一個(gè)OAuth 2.0屬性:
okta.oauth2.redirect-uri=/authorization-code/callback最后一個(gè)要修改的文件是src/main/resources/templates/index.hml 。 對(duì)其進(jìn)行調(diào)整,以在用戶(hù)未通過(guò)身份驗(yàn)證時(shí)顯示登錄按鈕,在用戶(hù)登錄時(shí)顯示注銷(xiāo)按鈕。
<!doctype html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head><!-- Required meta tags --><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><!-- Bootstrap CSS --><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"><title>Hello, world!</title> </head> <body> <nav class="navbar navbar-default"><form method="post" th:action="@{/logout}" th:if="${#authorization.expression('isAuthenticated()')}" class="navbar-form navbar-right"><input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /><button id="logout-button" type="submit" class="btn btn-danger">Logout</button></form><form method="get" th:action="@{/oauth2/authorization/okta}" th:unless="${#authorization.expression('isAuthenticated()')}"><button id="login-button" class="btn btn-primary" type="submit">Login</button></form> </nav><div id="content" th:if="${#authorization.expression('isAuthenticated()')}"><h1>School classes</h1><table id="classes"><thead><tr><th>Course</th><th>Teacher</th><th>Year</th><th>Number of students</th></tr></thead><tbody></tbody></table><!-- Optional JavaScript --><!-- jQuery first, then Popper.js, then Bootstrap JS --><script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script><script src="static/js/school_classes.js"></script> </div></body> </html>您應(yīng)該在此HTML中了解一些Thymeleaf屬性:
- @{/logout} –返回在后端定義的注銷(xiāo)URL
- th:if="${#authorization.expression('isAuthenticated()')}" –僅在用戶(hù)登錄時(shí)打印HTML
- @{//oauth2/authorization/okta} –這是Spring Security重定向到Okta的URL。 您也可以鏈接到/login ,但這只是呈現(xiàn)相同的鏈接,您必須單擊它。
- th:unless="${#authorization.expression('isAuthenticated()')}" –僅在用戶(hù)注銷(xiāo)后才在節(jié)點(diǎn)內(nèi)打印HTML
現(xiàn)在,重新啟動(dòng)配置項(xiàng)目和school-ui。 如果導(dǎo)航到輸入http://localhost:8080 ,則應(yīng)該看到以下屏幕:
登錄后,屏幕應(yīng)顯示如下:
恭喜,您已經(jīng)使用Spring Cloud config和Eureka創(chuàng)建了微服務(wù)架構(gòu)來(lái)進(jìn)行服務(wù)發(fā)現(xiàn)! 現(xiàn)在,讓我們更進(jìn)一步,并對(duì)每個(gè)服務(wù)進(jìn)行Dockerize。
使用Docker打包Spring應(yīng)用程序
Docker是一項(xiàng)了不起的技術(shù),它允許創(chuàng)建類(lèi)似于虛擬機(jī)的系統(tǒng)映像,但是共享與主機(jī)操作系統(tǒng)相同的內(nèi)核。 此功能可以提高系統(tǒng)性能和啟動(dòng)時(shí)間。 此外,Docker提供了一個(gè)精巧的內(nèi)置系統(tǒng),該系統(tǒng)可確保一旦創(chuàng)建映像就可以; 它永遠(yuǎn)不會(huì)改變。 換句話(huà)說(shuō):不再有“它可以在我的機(jī)器上工作!”
提示:需要更深的Docker背景嗎? 看看我們的《 Docker開(kāi)發(fā)人員指南》 。
您需要為每個(gè)項(xiàng)目創(chuàng)建一個(gè)Docker映像。 每個(gè)映像在每個(gè)項(xiàng)目的根文件夾中應(yīng)具有相同的Maven配置和Dockerfile內(nèi)容(例如, school-ui/Dockerfile )。
在每個(gè)項(xiàng)目的pom中,添加dockerfile-maven-plugin :
<plugins>...<plugin><groupId>com.spotify</groupId><artifactId>dockerfile-maven-plugin</artifactId><version>1.4.9</version><executions><execution><id>default</id><goals><goal>build</goal><goal>push</goal></goals></execution></executions><configuration><repository>developer.okta.com/microservice-docker-${project.artifactId}</repository><tag>${project.version}</tag><buildArgs><JAR_FILE>${project.build.finalName}.jar</JAR_FILE></buildArgs></configuration></plugin> </plugins>每次運(yùn)行./mvnw install時(shí),此XML都會(huì)配置Dockerfile Maven插件以構(gòu)建Docker映像。 將使用名稱(chēng)developer.okta.com/microservice-docker-${project.artifactId}創(chuàng)建每個(gè)圖像,其中project.artifactId因project.artifactId而異。
在每個(gè)項(xiàng)目的根目錄中創(chuàng)建一個(gè)Dockerfile文件。
FROM openjdk:8-jdk-alpine VOLUME /tmp ADD target/*.jar app.jar ENV JAVA_OPTS=" ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar"Dockerfile遵循Spring Boot與Docker的建議。
現(xiàn)在,更改school-ui/src/main/resources/bootstrap.yml以添加新的failFast設(shè)置:
eureka:client:serviceUrl:defaultZone: ${EUREKA_SERVER:http://localhost:8761/eureka} spring:application:name: school-uicloud:config:discovery:enabled: trueserviceId: CONFIGSERVERfailFast: truespring.cloud.failFast: true設(shè)置告訴Spring Cloud Config在找不到配置服務(wù)器時(shí)立即終止應(yīng)用程序。 這將對(duì)下一步很有用。
添加Docker Compose以運(yùn)行所有內(nèi)容
創(chuàng)建一個(gè)名為docker-compose.yml的新文件,該文件定義每個(gè)項(xiàng)目的啟動(dòng)方式:
version: '3' services:discovery:image: developer.okta.com/microservice-docker-discovery:0.0.1-SNAPSHOTports:- 8761:8761config:image: developer.okta.com/microservice-docker-config:0.0.1-SNAPSHOTvolumes:- ./config-data:/var/config-dataenvironment:- JAVA_OPTS=-DEUREKA_SERVER=http://discovery:8761/eureka-Dspring.cloud.config.server.native.searchLocations=/var/config-datadepends_on:- discoveryports:- 8888:8888school-service:image: developer.okta.com/microservice-docker-school-service:0.0.1-SNAPSHOTenvironment:- JAVA_OPTS=-DEUREKA_SERVER=http://discovery:8761/eurekadepends_on:- discovery- configschool-ui:image: developer.okta.com/microservice-docker-school-ui:0.0.1-SNAPSHOTenvironment:- JAVA_OPTS=-DEUREKA_SERVER=http://discovery:8761/eurekarestart: on-failuredepends_on:- discovery- configports:- 8080:8080如您所見(jiàn),每個(gè)項(xiàng)目現(xiàn)在都是Docker中聲明的服務(wù),用于組成文件。 它將暴露其端口和其他一些屬性。
- 除發(fā)現(xiàn)外,所有項(xiàng)目都將具有變量值-DEUREKA_SERVER=http://discovery:8761/eureka 。 這將告訴您在哪里可以找到發(fā)現(xiàn)服務(wù)器。 Docker Compose在服務(wù)之間創(chuàng)建一個(gè)虛擬網(wǎng)絡(luò),每個(gè)服務(wù)使用的DNS名稱(chēng)就是其名稱(chēng):這就是為什么可以將discovery用作主機(jī)名的原因。
- Config服務(wù)將具有用于配置文件的卷。 該卷將映射到docker容器內(nèi)的/var/config-data 。 同樣,屬性spring.cloud.config.server.native.searchLocations將被覆蓋為相同的值。 您必須將文件school-ui.properties存儲(chǔ)在卷映射上指定的同一文件夾中(在上面的示例中, 相對(duì)文件夾./config-data )。
- school-ui項(xiàng)目的屬性將restart: on-failure 。 這將Docker Compose設(shè)置為在應(yīng)用程序失敗后立即重新啟動(dòng)。 與failFast屬性一起使用可以使應(yīng)用程序繼續(xù)嘗試啟動(dòng),直到Discovery和Config項(xiàng)目完全準(zhǔn)備好為止。
就是這樣! 現(xiàn)在,構(gòu)建圖像:
cd config && ./mvnw clean install cd ../discovery && ./mvnw clean install cd .. && ./mvnw clean install在school-ui項(xiàng)目中,最后一個(gè)命令可能會(huì)失敗,并顯示以下錯(cuò)誤:
java.lang.IllegalStateException: Failed to load ApplicationContext Caused by: java.lang.IllegalStateException: No instances found of configserver (CONFIGSERVER)要解決此問(wèn)題,請(qǐng)創(chuàng)建一個(gè)school-ui/src/test/resources/test.properties文件并添加屬性,以使Okta的配置通過(guò),并且在測(cè)試時(shí)不使用發(fā)現(xiàn)或配置服務(wù)器。
okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default okta.oauth2.clientId=TEST spring.cloud.discovery.enabled=false spring.cloud.config.discovery.enabled = false spring.cloud.config.enabled = false然后修改UIWebApplicationTests.java以加載此文件以用于測(cè)試屬性:
import org.springframework.test.context.TestPropertySource;... @TestPropertySource(locations="classpath:test.properties") public class UIWebApplicationTests {... }現(xiàn)在,您應(yīng)該能夠在school-ui項(xiàng)目中運(yùn)行./mvnw clean install 。
完成后,運(yùn)行Docker Compose以啟動(dòng)所有容器(在docker-compose.yml所在的目錄中)。
docker-compose up -d Starting okta-microservice-docker-post-final_discovery_1 ... done Starting okta-microservice-docker-post-final_config_1 ... done Starting okta-microservice-docker-post-final_school-ui_1 ... done Starting okta-microservice-docker-post-final_school-service_1 ... done現(xiàn)在,您應(yīng)該能夠像以前一樣瀏覽該應(yīng)用程序。
使用Spring配置文件來(lái)修改您的微服務(wù)的配置
現(xiàn)在,您已經(jīng)到達(dá)了微服務(wù)之旅的最后階段。 Spring Profiles是一個(gè)功能強(qiáng)大的工具。 使用配置文件,可以通過(guò)完全注入不同的依賴(lài)項(xiàng)或配置來(lái)修改程序行為。
假設(shè)您有一個(gè)結(jié)構(gòu)良好的軟件,其持久層與業(yè)務(wù)邏輯分離。 例如,您還提供對(duì)MySQL和PostgreSQL的支持。 每個(gè)數(shù)據(jù)庫(kù)可能有不同的數(shù)據(jù)訪問(wèn)類(lèi),這些數(shù)據(jù)訪問(wèn)類(lèi)僅由定義的概要文件加載。
另一個(gè)用例是配置:不同的配置文件可能具有不同的配置。 以身份驗(yàn)證為例。 您的測(cè)試環(huán)境會(huì)進(jìn)行身份驗(yàn)證嗎? 如果是這樣,則不應(yīng)使用與生產(chǎn)相同的用戶(hù)目錄。
將您的配置項(xiàng)目更改為在Okta中有兩個(gè)應(yīng)用程序:一個(gè)默認(rèn)(用于開(kāi)發(fā)),另一個(gè)用于生產(chǎn)。 在Okta網(wǎng)站上創(chuàng)建一個(gè)新的Web應(yīng)用程序,并將其命名為“ okta-docker-production”。
現(xiàn)在,在您的config項(xiàng)目中,創(chuàng)建一個(gè)名為school-ui-production.properties的新文件。 您已經(jīng)有了school-ui.properties ,每個(gè)School UI實(shí)例都將使用它。 在文件末尾添加環(huán)境時(shí),Spring將合并兩個(gè)文件,并優(yōu)先于最特定的文件。 使用生產(chǎn)應(yīng)用程序的客戶(hù)端ID和密碼保存文件,如下所示:
school-ui-production.properties
okta.oauth2.clientId={YOUR_PRODUCTION_CLIENT_ID} okta.oauth2.clientSecret={YOUR_PRODUCTION_CLIENT_SECRET}現(xiàn)在,使用Maven運(yùn)行配置項(xiàng)目,然后運(yùn)行以下兩個(gè)curl命令:
./mvnw spring-boot:run> curl http://localhost:8888/school-ui.propertiesokta.oauth2.issuer: https://{yourOktaDomain}/oauth2/default okta.oauth2.clientId: ==YOUR DEV CLIENT ID HERE== okta.oauth2.clientSecret: ==YOUR DEV CLIENT SECRET HERE==> curl http://localhost:8888/school-ui-production.properties okta.oauth2.issuer: https://{yourOktaDomain}/oauth2/default okta.oauth2.clientId: ==YOUR PROD CLIENT ID HERE== okta.oauth2.clientSecret: ==YOUR PROD CLIENT SECRET HERE==如您所見(jiàn),即使文件school-ui-production具有兩個(gè)屬性, config項(xiàng)目也會(huì)顯示三個(gè)屬性(因?yàn)榕渲靡押喜?#xff09;。
現(xiàn)在,您可以在docker-compose.yml中將school-ui服務(wù)docker-compose.yml為使用production配置文件:
school-ui:image: developer.okta.com/microservice-docker-school-ui:0.0.1-SNAPSHOTenvironment:- JAVA_OPTS=-DEUREKA_SERVER=http://discovery:8761/eureka-Dspring.profiles.active=productionrestart: on-failuredepends_on:- discovery- configports:- 8080:8080您還需要將school-ui-production.properties復(fù)制到您的config-data目錄中。 然后關(guān)閉所有Docker容器并重新啟動(dòng)它們。
docker-compose down docker-compose up -d您應(yīng)該在school-ui容器的日志中看到以下內(nèi)容:
The following profiles are active: production而已! 現(xiàn)在,您可以在生產(chǎn)配置文件中運(yùn)行微服務(wù)架構(gòu)。 頭暈!
提示:如果要證明使用了okta-docker-production應(yīng)用程序而不是okta-docker ,可以在Okta中停用okta-docker應(yīng)用程序,并確認(rèn)您仍然可以登錄http://localhost:8080 。
了解有關(guān)微服務(wù),Spring,Docker和現(xiàn)代應(yīng)用程序安全性的更多信息
在這篇文章中,您了解了有關(guān)微服務(wù)以及如何部署它們的更多信息,以及:
- 什么是微服務(wù)?
- 服務(wù)應(yīng)該如何發(fā)現(xiàn)其依賴(lài)關(guān)系而無(wú)需事先知道它們的位置。
- 如何以信息的中心點(diǎn)維護(hù)分布式配置。 該配置可以管理一個(gè)或多個(gè)應(yīng)用程序和環(huán)境。
- 如何使用Spring Cloud Config配置OAuth 2.0。
- 如何使用Docker和Docker Compose部署微服務(wù)
- 如何使用Spring Profiles在生產(chǎn)環(huán)境中進(jìn)行部署。
您可以在oktadeveloper / okta-spring-microservices-docker-example上的GitHub上找到本教程的完整源代碼。
如果您有興趣在Spring中學(xué)習(xí)有關(guān)微服務(wù)或現(xiàn)代應(yīng)用程序開(kāi)發(fā)的更多信息,建議您查看以下資源:
- 使用Spring Boot 2.0和OAuth 2.0構(gòu)建并保護(hù)微服務(wù)
- 使用JHipster和OAuth 2.0開(kāi)發(fā)微服務(wù)架構(gòu)
- 使用Spring Boot為Microbrews構(gòu)建微服務(wù)架構(gòu)
- Spring Boot 2.1:出色的OIDC,OAuth 2.0和反應(yīng)式API支持
- 使用Spring Boot和MongoDB構(gòu)建一個(gè)反應(yīng)式應(yīng)用程序
如果您對(duì)此帖子有任何疑問(wèn),請(qǐng)?jiān)谙旅姘l(fā)表評(píng)論。 您可以在Twitter上關(guān)注@oktadev以獲取更多精彩內(nèi)容!
Build Spring Microservices和Dockerize Them for Production''最初于2019年2月28日發(fā)布在Okta開(kāi)發(fā)者博客上。
“我喜歡編寫(xiě)身份驗(yàn)證和授權(quán)代碼。” ?從來(lái)沒(méi)有Java開(kāi)發(fā)人員。 厭倦了一次又一次地建立相同的登錄屏幕? 嘗試使用Okta API進(jìn)行托管身份驗(yàn)證,授權(quán)和多因素身份驗(yàn)證。
翻譯自: https://www.javacodegeeks.com/2019/04/build-spring-microservices-dockerize-production.html
總結(jié)
以上是生活随笔為你收集整理的构建Spring微服务并对其进行Dockerize生产的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 硝酸铵溶于水化学式 硝酸铵溶于水的化学方
- 下一篇: react 消息队列_具有AkkaRea