Spring Cloudを使用するためのスターター

みなさんこんにちは!







この記事では、Spring WebFlux、Spring Security、Spring Cloud Netflix Eureka(Service Discovery)、Hystrix(Circuit Breaker)、Ribbon(Client Side Load Balancer)、External Configuration(git repository)を使用して、Reactive RESTfulミキサーサービスを作成するための基本コンポーネントを紹介します、Spring Cloud Sleuth、Spring Cloud Gateway、Spring Boot Reactive MongoDB。 Spring Boot Adminと監視用のZipkinも同様です。







このレビューは、Spring Microservices in ActionおよびHands-On Spring 5 Security for Reactive Applicationsの書籍を研究した後に行われました。







この記事では、ゲームのリストを取得する、プレーヤーのリストを取得する、プレーヤーIDからゲームを作成する、応答を長時間待つ場合のロールバック(Hystrixフォールバック)を確認する要求の3つのクエリを使用して、基本アプリケーションを作成します。 そして、Reactive Applicationの書籍Hands-On Spring 5 Securityに基づくJWTトークンを介した認証の実装。







この記事は経験豊富なユーザーを対象としているため、IDEで各アプリケーションを作成する方法については説明しません。







プロジェクト構造



代替テキスト







プロジェクトは2つのモジュールで構成されています。 spring-servers



モジュールは、プロジェクト間で安全にコピーできます。 コードと構成はほとんどありません。 tictactoe-services



モジュールには、アプリケーションのモジュールとマイクロtictactoe-services



が含まれています。 auth-module



domain-module



をサービスに追加すると、マイクロサービスの自律性に関するマイクロサービスアーキテクチャの原則の1つに違反することにすぐに気付くでしょう。 しかし、これらのモジュールの開発段階では、これが最も最適なソリューションだと思います。







Gradle設定



Gradle構成全体が1つのファイルにあるのが好きなので、1つのbuild.gradle



プロジェクト全体を構成しました。







build.gradle
 buildscript { ext { springBootVersion = '2.1.1.RELEASE' gradleDockerVersion = '0.20.1' } repositories { mavenCentral() maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") classpath("gradle.plugin.com.palantir.gradle.docker:gradle-docker:${gradleDockerVersion}") } } allprojects { group = 'com.tictactoe' apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' apply plugin: 'com.palantir.docker' apply plugin: 'com.palantir.docker-run' apply plugin: 'com.palantir.docker-compose' } docker.name = 'com.tictactoe' bootJar.enabled = false sourceCompatibility = 11 repositories { mavenCentral() maven { url "https://repo.spring.io/milestone" } } subprojects { ext['springCloudVersion'] = 'Greenwich.M3' sourceSets.configureEach { sourceSet -> tasks.named(sourceSet.compileJavaTaskName, { options.annotationProcessorGeneratedSourcesDirectory = file("$buildDir/generated/sources/annotationProcessor/java/${sourceSet.name}") }) } repositories { mavenCentral() maven { url "https://repo.spring.io/milestone" } } dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" } } dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') compileOnly('org.projectlombok:lombok') annotationProcessor('org.projectlombok:lombok') } } project(':spring-servers') { bootJar.enabled = false task cleanAll { dependsOn subprojects*.tasks*.findByName('clean') } task buildAll { dependsOn subprojects*.tasks*.findByName('build') } dockerCompose { template 'docker-compose.spring-servers.template.yml' dockerComposeFile 'docker-compose.spring-servers.yml' } } project(':tictactoe-services') { bootJar.enabled = false task cleanAll { dependsOn subprojects*.tasks*.findByName('clean') } task buildAll { dependsOn subprojects*.tasks*.findByName('build') } } // Tictactoe Modules project(':tictactoe-services:domain-module') { bootJar.enabled = false jar { enabled = true group 'com.tictactoe' baseName = 'domain-module' version = '1.0' } dependencies { implementation('org.springframework.boot:spring-boot-starter-security') implementation('org.springframework.boot:spring-boot-starter-data-mongodb-reactive') implementation('org.springframework.boot:spring-boot-starter-validation') implementation('com.fasterxml.jackson.core:jackson-annotations:2.9.3') implementation 'com.intellij:annotations:+@jar' compileOnly('org.projectlombok:lombok') testCompile group: 'junit', name: 'junit', version: '4.12' } } project(':tictactoe-services:auth-module') { bootJar.enabled = false jar { enabled = true baseName = 'auth-module' version = '1.0' } dependencies { implementation project(':tictactoe-services:domain-module') implementation('org.springframework.boot:spring-boot-starter-webflux') implementation('org.springframework.boot:spring-boot-starter-data-mongodb-reactive') implementation('org.springframework.boot:spring-boot-starter-security') implementation('org.springframework.cloud:spring-cloud-starter-netflix-ribbon') implementation('org.springframework.security:spring-security-oauth2-core') implementation('org.springframework.security:spring-security-oauth2-jose') implementation 'com.intellij:annotations:+@jar' testImplementation('org.springframework.boot:spring-boot-starter-test') testImplementation('io.projectreactor:reactor-test') testImplementation('org.springframework.security:spring-security-test') } } project(':tictactoe-services:user-service') { bootJar { launchScript() baseName = 'user-service' version = '0.1.0' } dependencies { implementation project(':tictactoe-services:domain-module') implementation project(':tictactoe-services:auth-module') } } project(':tictactoe-services:game-service') { bootJar { launchScript() baseName = 'game-service' version = '0.1.0' } dependencies { implementation project(':tictactoe-services:domain-module') implementation project(':tictactoe-services:auth-module') } } project(':tictactoe-services:webapi-service') { bootJar { launchScript() baseName = 'webapi-service' version = '0.1.0' } dependencies { implementation project(':tictactoe-services:domain-module') implementation project(':tictactoe-services:auth-module') } } // Spring Servers project(':spring-servers:discovery-server') { bootJar { launchScript() baseName = 'discovery-server' version = '0.1.0' } dependencies { implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-server') implementation('org.springframework.boot:spring-boot-starter-security') compile('javax.xml.bind:jaxb-api:2.3.0') compile('javax.activation:activation:1.1') compile('org.glassfish.jaxb:jaxb-runtime:2.3.0') testImplementation('org.springframework.boot:spring-boot-starter-test') } } project(':spring-servers:config-server') { bootJar { launchScript() baseName = 'config-server' version = '0.1.0' } dependencies { implementation('org.springframework.boot:spring-boot-starter-security') implementation('org.springframework.cloud:spring-cloud-config-server') implementation('org.springframework.cloud:spring-cloud-starter-config') implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client') testImplementation('org.springframework.boot:spring-boot-starter-test') } } project(':spring-servers:gateway-server') { bootJar { launchScript() baseName = 'gateway-server' version = '0.1.0' } dependencies { implementation('org.springframework.boot:spring-boot-starter-webflux') implementation('org.springframework.boot:spring-boot-starter-actuator') implementation('org.springframework.cloud:spring-cloud-starter-gateway') implementation('org.springframework.cloud:spring-cloud-starter-config') implementation('org.springframework.cloud:spring-cloud-starter-netflix-ribbon') implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client') testImplementation('org.springframework.boot:spring-boot-starter-test') } } project(':spring-servers:admin-server') { ext['springBootAdminVersion'] = '2.1.1' bootJar { launchScript() baseName = 'admin-server' version = '0.1.0' } dependencies { implementation('org.springframework.boot:spring-boot-starter-web') implementation('org.springframework.boot:spring-boot-starter-security') implementation('de.codecentric:spring-boot-admin-starter-server') implementation('org.springframework.cloud:spring-cloud-starter-config') implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client') testImplementation('org.springframework.boot:spring-boot-starter-test') testImplementation('org.springframework.security:spring-security-test') } dependencyManagement { imports { mavenBom "de.codecentric:spring-boot-admin-dependencies:${springBootAdminVersion}" } } } subprojects { subproject -> if (file("${subproject.projectDir}/docker/Dockerfile").exists()) { docker { // workingbit - replace with your dockerhub's username name "workingbit/${subproject.group}.${subproject.bootJar.baseName}" tags 'latest' dockerfile file("${subproject.projectDir}/docker/Dockerfile") files tasks.bootJar.archivePath, 'docker/run.sh' buildArgs "JAR_FILE": "${subproject.bootJar.baseName}-${subproject.bootJar.version}.jar", "RUN_SH": "run.sh" } } else { docker.name = 'noop' } if (subproject.name.endsWith('service')) { dependencies { implementation('org.springframework.boot:spring-boot-starter-actuator') implementation('org.springframework.boot:spring-boot-starter-webflux') implementation('org.springframework.boot:spring-boot-starter-data-mongodb-reactive') implementation('org.springframework.boot:spring-boot-starter-security') implementation('org.springframework.security:spring-security-oauth2-core') implementation('org.springframework.security:spring-security-oauth2-jose') implementation('org.springframework.cloud:spring-cloud-starter-config') implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client') implementation('org.springframework.cloud:spring-cloud-starter-netflix-hystrix') implementation('org.springframework.cloud:spring-cloud-starter-netflix-ribbon') implementation('org.springframework.cloud:spring-cloud-starter-sleuth') implementation('org.springframework.cloud:spring-cloud-starter-zipkin') implementation('org.springframework.security:spring-security-rsa') implementation('com.intellij:annotations:+@jar') implementation('org.apache.commons:commons-lang3:3.8.1') runtimeOnly('org.springframework.boot:spring-boot-devtools') testImplementation('org.springframework.boot:spring-boot-starter-test') testImplementation('de.flapdoodle.embed:de.flapdoodle.embed.mongo') testImplementation('io.projectreactor:reactor-test') } } }
      
      





共通の構成ファイルを使用すると、1つの場所で、マイクロサービス(この場合、「service」で終わる名前のサービス)に共通の依存関係を作成できます。 しかし、これは再びマイクロサービスの自律の原則に違反しています。 一般的な依存関係に加えて、サブプロジェクトにタスクを追加できます。 gradle.plugin.com.palantir.gradle.docker:gradle-docker



プラグインタスクをDocker



動作するように追加しました。







認証モジュール



次に、JWT認証モジュールを検討します。 このモジュールのauth



パッケージの説明は、上記で示したリアクティブ認証に関する本に記載されています。







代替テキスト







ああ、構成パッケージについて詳しく見ていきconfig









「複雑な」プロパティのクラスApplicationClientsProperties.java



 @Data @Component @ConfigurationProperties("appclients") public class ApplicationClientsProperties { private List<ApplicationClient> clients = new ArrayList<>(); @Data public static class ApplicationClient { private String username; private String password; private String[] roles; } }
      
      





このクラスには、inMemoryデータベース構成の「複雑な」プロパティが含まれます。







AuthModuleConfig.javaモジュール構成クラス



 @Data @Configuration @PropertySource("classpath:moduleConfig.yml") public class AuthModuleConfig { @Value("${tokenExpirationMinutes:60}") private Integer tokenExpirationMinutes; @Value("${tokenIssuer:workingbit-example.com}") private String tokenIssuer; @Value("${tokenSecret:secret}") // length minimum 256 bites private String tokenSecret; }
      
      





リソースファイルでは、これらの変数を指定する必要があります。 私の構成では、トークンは10時間後に無効になります。







MicroserviceServiceJwtAuthWebFilter.java Filter Matchers構成クラス



 public class MicroserviceServiceJwtAuthWebFilter extends JwtAuthWebFilter { private final String[] matchersStrings; public MicroserviceServiceJwtAuthWebFilter(JwtService jwtService, String[] matchersStrings) { super(jwtService); this.matchersStrings = matchersStrings; } @Override protected ServerWebExchangeMatcher getAuthMatcher() { List<ServerWebExchangeMatcher> matchers = Arrays.stream(this.matchersStrings) .map(PathPatternParserServerWebExchangeMatcher::new) .collect(Collectors.toList()); return ServerWebExchangeMatchers.matchers(new OrServerWebExchangeMatcher(matchers)); } }
      
      





この設計中に、JWTを操作するサービスと、このフィルターが処理するパスのリストがこのフィルターに転送されます。







リアクティブスプリングブートセキュリティMicroserviceSpringSecurityWebFluxConfig.java構成クラス



 @ConditionalOnProperty(value = "microservice", havingValue = "true") @EnableReactiveMethodSecurity @PropertySource(value = "classpath:/application.properties") public class MicroserviceSpringSecurityWebFluxConfig { @Value("${whiteListedAuthUrls}") private String[] whiteListedAuthUrls; @Value("${jwtTokenMatchUrls}") private String[] jwtTokenMatchUrls; /** * Bean which configures whiteListed and JWT filter urls * Also it configures authentication for Actuator. Actuator takes configured AuthenticationManager automatically * which uses MapReactiveUserDetailsService to configure inMemory users */ @Bean public SecurityWebFilterChain springSecurityFilterChain( ServerHttpSecurity http, JwtService jwtService ) { MicroserviceServiceJwtAuthWebFilter userServiceJwtAuthWebFilter = new MicroserviceServiceJwtAuthWebFilter(jwtService, jwtTokenMatchUrls); http.csrf().disable(); http .authorizeExchange() .pathMatchers(whiteListedAuthUrls) .permitAll() .and() .authorizeExchange() .pathMatchers("/actuator/**").hasRole("SYSTEM") .and() .httpBasic() .and() .addFilterAt(userServiceJwtAuthWebFilter, SecurityWebFiltersOrder.AUTHENTICATION); return http.build(); } }
      
      





ここには3つの興味深い注釈があります。







 @ConditionalOnProperty(value = "microservice", havingValue = "true")
      
      





構成ファイル内のmicroservice変数に応じてこのモジュールを接続する注釈。注釈に示されています。 これは、一部のモジュールで一般的なトークンチェックを無効にするために必要です。 このアプリケーションでは、これはSecurityWebFilterChain



Beanの独自の実装を持つwebapi-service



です。







 @PropertySource(value = "classpath:/application.properties")
      
      





このアノテーションにより、このモジュールがインポートされるメインサービスからプロパティを取得することもできます。 つまり、変数







 @Value("${whiteListedAuthUrls}") private String[] whiteListedAuthUrls; @Value("${jwtTokenMatchUrls}") private String[] jwtTokenMatchUrls;
      
      





子孫のマイクロサービス構成から値を取得します。







また、 @PreAuthorize(“hasRole('MY_ROLE')”)



などのセキュリティアノテーションを添付できるアノテーション







 @EnableReactiveMethodSecurity
      
      





このモジュールでは、 SecurityWebFilterChain



Beanが作成されます。これにより、アクチュエーターへのアクセス、許可されたURL、およびJWTトークンがチェックされるURLが構成されます。 JWTトークンフィルターへのアクセスは開いている必要があることに注意してください。







SpringWebFluxConfig.javaの構成



この構成では、 MapReactiveUserDetailsService



が作成されて、メモリー内のアクチュエーターおよびその他のシステムユーザーが構成されます。







 @Bean @Primary public MapReactiveUserDetailsService userDetailsRepositoryInMemory() { List<UserDetails> users = applicationClients.getClients() .stream() .map(applicationClient -> User.builder() .username(applicationClient.getUsername()) .password(passwordEncoder().encode(applicationClient.getPassword())) .roles(applicationClient.getRoles()).build()) .collect(toList()); return new MapReactiveUserDetailsService(users); }
      
      





ユーザーリポジトリとSpring Security



をつなぐために必要なReactiveUserDetailsService









 @Bean public ReactiveUserDetailsService userDetailsRepository(UserRepository users) { return (email) -> users.findByEmail(email).cast(UserDetails.class); }
      
      





リアクティブリクエストを実行するためのWebClient



クライアントを作成するためのBean。







 @Bean public WebClient loadBalancedWebClientBuilder(JwtService jwtService) { return WebClient.builder() .filter(lbFunction) .filter(authorizationFilter(jwtService)) .build(); } private ExchangeFilterFunction authorizationFilter(JwtService jwtService) { return ExchangeFilterFunction .ofRequestProcessor(clientRequest -> ReactiveSecurityContextHolder.getContext() .map(securityContext -> ClientRequest.from(clientRequest) .header(HttpHeaders.AUTHORIZATION, jwtService.getHttpAuthHeaderValue(securityContext.getAuthentication())) .build())); }
      
      





作成中に、2つのフィルターが追加されます。 LoadBalancer



およびReactiveSecurityContext



コンテキストからAuthentication



インスタンスをLoadBalancer



し、そこからトークンを作成するフィルター。フィルターがターゲットサーバーによって認証され、それに応じて承認されます。







そして、MongoDB ObjectId



タイプと日付を操作するために、objectMapper作成ビンを追加しました。







 @Bean @Primary ObjectMapper objectMapper() { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); builder.serializerByType(ObjectId.class, new ToStringSerializer()); builder.deserializerByType(ObjectId.class, new JsonDeserializer() { @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { Map oid = p.readValueAs(Map.class); return new ObjectId( (Integer) oid.get("timestamp"), (Integer) oid.get("machineIdentifier"), ((Integer) oid.get("processIdentifier")).shortValue(), (Integer) oid.get("counter")); } }); builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return builder.build(); }
      
      





マイクロサービスゲームサービス



マイクロサービスゲームサービスの構造は次のとおりです。







代替テキスト







ご覧のとおり、1つのApplicationConfig構成ファイルのみ







Configurator ApplicationConfig.java



 @Data @Configuration @EnableReactiveMongoRepositories("com.tictactoe.gameservice.repository") @Import({ApplicationClientsProperties.class, SpringWebFluxConfig.class, MicroserviceSpringSecurityWebFluxConfig.class}) public class ApplicationConfig { @Value("${userserviceUrl}") private String userServiceUrl; }
      
      





user-service



アドレスを持つ変数が含まれており、2つの興味深いアノテーションがあります。







 @EnableReactiveMongoRepositories("com.tictactoe.gameservice.repository")
      
      





このアノテーションは、コンフィギュレーターMongoDBリポジトリーを示すために必要です。







 @Import({ApplicationClientsProperties.class, SpringWebFluxConfig.class, MicroserviceSpringSecurityWebFluxConfig.class})
      
      





この注釈は、 auth-module



から構成をインポートします。







GameService.javaサービス



このサービスには、次の興味深いコードのみがあります。







 @HystrixCommand public Flux<Game> getAllGames() { return gameRepository.findAll(); } @HystrixCommand(fallbackMethod = "buildFallbackAllGames", threadPoolKey = "licenseByOrgThreadPool", threadPoolProperties = {@HystrixProperty(name = "coreSize", value = "30"), @HystrixProperty(name = "maxQueueSize", value = "10")}, commandProperties = { @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "75"), @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "7000"), @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "15000"), @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "5")} ) public Flux<Game> getAllGamesLong() { // logger.debug("LicenseService.getLicensesByOrg Correlation id: {}", UserContextHolder.getContext().getCorrelationId()); randomlyRunLong(); return gameRepository.findAll(); }
      
      





このメソッドはランダムに例外をスローし、Hystrixは注釈に従って、次のメソッドの結果を返します。







 private Flux<Game> buildFallbackAllGames() { User fakeUserBlack = new User("fakeUserBlack", "password", Collections.emptyList()); User fakeUserWhite = new User("fakeUserBlack", "password", Collections.emptyList()); Game game = new Game(fakeUserBlack, fakeUserWhite); List<Game> games = List.of(game); return Flux.fromIterable(games); }
      
      





上記の本で述べたように、何かが壊れている場合、キャッシュされたデータまたは代替データを表示するよりも良いものを表示しましょう。







マイクロサービスwebapi-service



これは、ゲートウェイと外部からは見えない内部マイクロサービスとの間の一種のミドルウェアです。 このサービスの目的は、他のサービスから選択を取得し、それに基づいてユーザーへの応答を形成することです。







代替テキスト







設定からレビューを開始します。







SpringSecurityWebFluxConfig.java構成



 @Configuration @EnableReactiveMethodSecurity public class SpringSecurityWebFluxConfig { private static final String AUTH_TOKEN_PATH = "/auth/token"; @Value("${whiteListedAuthUrls}") private String[] whiteListedAuthUrls; @Value("${jwtTokenMatchUrls}") private String[] jwtTokenMatchUrls; @Bean @Primary public SecurityWebFilterChain systemSecurityFilterChain( ServerHttpSecurity http, JwtService jwtService, @Qualifier("userDetailsRepository") ReactiveUserDetailsService userDetailsService ) {
      
      





ここでは、 auth-module



前に定義したuserDetailsService



サービスから認証マネージャーを作成しauth-module









  UserDetailsRepositoryReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
      
      





そして、このマネージャーでフィルターを作成し、 x-www-form-urlencoded



エンコードされたユーザーデータを取得するために認証インスタンスコンバーターも追加します。







  AuthenticationWebFilter tokenWebFilter = new AuthenticationWebFilter(authenticationManager); tokenWebFilter.setServerAuthenticationConverter(exchange -> Mono.justOrEmpty(exchange) .filter(ex -> AUTH_TOKEN_PATH.equalsIgnoreCase(ex.getRequest().getPath().value())) .flatMap(ServerWebExchange::getFormData) .filter(formData -> !formData.isEmpty()) .map((formData) -> { String email = formData.getFirst("email"); String password = formData.getFirst("password"); return new UsernamePasswordAuthenticationToken(email, password); }) );
      
      





成功した認証ハンドラーを追加します。その本質は、 Authentication



から生成されたリクエストヘッダーにJWTトークンを入れて、有効なゲストトークンを使用してのみ認証を行えるようにすることです。







  tokenWebFilter.setAuthenticationSuccessHandler(new JwtAuthSuccessHandler(jwtService)); MicroserviceServiceJwtAuthWebFilter webApiJwtServiceWebFilter = new MicroserviceServiceJwtAuthWebFilter(jwtService, jwtTokenMatchUrls); http.csrf().disable(); http .authorizeExchange()
      
      





ホワイトリストからアドレスを解決します。 前に書いたように、JWTフィルターによって処理されるアドレスも開く必要があります







  .pathMatchers(whiteListedAuthUrls) .permitAll() .and() .authorizeExchange()
      
      





基本認証でアクチュエータと一部のアドレスを保護します







  .pathMatchers("/actuator/**").hasRole("SYSTEM") .pathMatchers(HttpMethod.GET, "/url-protected/**").hasRole("GUEST") .pathMatchers(HttpMethod.POST, "/url-protected/**").hasRole("USER") .and() .httpBasic() .and() .authorizeExchange()
      
      





認証トークンへのアクセスを必須にする







  .pathMatchers(AUTH_TOKEN_PATH).authenticated() .and()
      
      





フィルターを追加します。 JWTトークンを認証および検証するため。







  .addFilterAt(webApiJwtServiceWebFilter, SecurityWebFiltersOrder.AUTHENTICATION) .addFilterAt(tokenWebFilter, SecurityWebFiltersOrder.AUTHENTICATION); return http.build(); }
      
      





また、上で書いたように、このサービスは、 application.properites



ファイルでmicoservice=false



変数の値を指定することにより、他のサービスに共通のJWTトークンチェックを無効にします。







トークン発行、登録、および承認コントローラーAuthController.java



このコントローラーは純粋に特定のものであるため、説明しません。







WebApiService.javaサービス



このサービスは、 WebApiMethodProtectedController.jav



コントローラーで呼び出され、興味深いアノテーションが付いています。







 @PreAuthorize("hasRole('GUEST')") public Flux<User> getAllUsers() { }
      
      





このアノテーションは、ゲストロールを持つ許可ユーザーのみにメソッドへのアクセスを許可します。







テスト方法



環境を作成します。







代替テキスト







トークンを取得







代替テキスト







環境内のTOKEN変数を受信したトークンで更新します。







新しいユーザーを登録する







代替テキスト







登録後、ユーザートークンを受け取ります。 10時間で期限切れになります。 有効期限が切れたら、新しいものを取得する必要があります。 これを行うには、ゲストトークンを再度リクエストし、環境を更新してリクエストを実行します







代替テキスト







次に、ユーザー、ゲームのリストを取得したり、新しいゲームを作成したりできます。 また、Hystrixをテストします。サービスの構成を確認し、gitリポジトリの変数を暗号化します。







参照資料






All Articles