Starter pour travailler avec Spring Cloud

Bonjour Ă  tous!


Dans cet article, je vais démontrer les composants de base pour créer des services de mixage RESTful réactifs à l'aide de Spring WebFlux, Spring Security, Spring Cloud Netflix Eureka (Service Discovery), Hystrix (Disjoncteur), Ruban (Client Side Load Balancer), Configuration externe (via le référentiel git) , Spring Cloud Sleuth, Spring Cloud Gateway, Spring Boot Reactive MongoDB. Ainsi que Spring Boot Admin et Zipkin pour la surveillance.


Cette revue a été faite aprÚs avoir étudié les livres Spring Microservices in Action et Hands-On Spring 5 Security for Reactive Applications.


Dans cet article, nous allons crĂ©er une application Ă©lĂ©mentaire avec trois requĂȘtes: obtenir une liste de jeux, obtenir une liste de joueurs, crĂ©er un jeu Ă  partir de l'id des joueurs, une demande de vĂ©rification du rollback (repli Hystrix) en cas de longue attente d'une rĂ©ponse. Et l'implĂ©mentation de l'authentification via le jeton JWT basĂ© sur le livre Hands-On Spring 5 Security for Reactive Applications.


Je ne décrirai pas comment chaque application est créée dans l'IDE, car cet article est destiné à un utilisateur expérimenté.


Structure du projet


Texte alternatif


Le projet se compose de deux modules. Le module spring-servers peut ĂȘtre copiĂ© en toute sĂ©curitĂ© d'un projet Ă  l'autre. Il n'y a presque pas de code et de configurations. Le module tictactoe-services contient les modules et microservices de notre application. Je remarquerai immĂ©diatement qu'en ajoutant un auth-module domain-module aux services, je viole l'un des principes de l'architecture des microservices concernant l'autonomie des microservices. Mais au stade du dĂ©veloppement de ces modules, je pense que c'est la solution la plus optimale.


Configuration Gradle


J'aime quand toute la configuration Gradle est dans un fichier, j'ai donc configuré tout le projet dans un 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') } } } 

L'utilisation d'un fichier de configuration commun vous permet de rendre les dépendances communes aux microservices, dans ce cas les services avec un nom se terminant par «service», en un seul endroit. MAIS, cela viole à nouveau le principe d'autonomie des microservices. En plus des dépendances courantes, vous pouvez ajouter des tùches aux sous-projets. J'ai ajouté gradle.plugin.com.palantir.gradle.docker:gradle-docker tùches de plugin gradle.plugin.com.palantir.gradle.docker:gradle-docker pour travailler avec Docker .


Module d'authentification


Maintenant, considĂ©rez le module d'authentification JWT. Une description du package d' auth de ce module peut ĂȘtre trouvĂ©e dans le livre d'authentification rĂ©active que j'ai mentionnĂ© ci-dessus.


Texte alternatif


Ah, config attarder sur le paquet config plus en détail.


La classe des propriétés «complexes» 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; } } 

Cette classe contient des propriétés «complexes» pour la configuration de la base de données inMemory.


Classe de configuration du module 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; } 

Dans le fichier de ressources, vous devez spécifier ces variables. Dans ma configuration, le jeton s'éteint au bout de 10 heures.


Classe de configuration des filtreurs MicroserviceServiceJwtAuthWebFilter.java


 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)); } } 

Pendant la construction, ce filtre transmet le service pour travailler avec JWT et une liste de chemins que ce filtre traitera.


Classe de configuration Reactive Spring Boot Security 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(); } } 

Il y a trois annotations intéressantes ici.


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

Une annotation qui connecte ce module en fonction de la variable de microservice dans le fichier de configuration spécifié dans l'annotation Cela est nécessaire pour désactiver la vérification générale des jetons dans certains modules. Dans cette application, il s'agit d'un webapi-service qui a sa propre implémentation du bean SecurityWebFilterChain .


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

Cette annotation vous permet également de prendre des propriétés du service principal dans lequel ce module est importé. En d'autres termes, les variables


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

Prenez leurs valeurs dans la configuration du microservice du descendant.


Et, une annotation qui vous permet de joindre des annotations de sĂ©curitĂ© comme @PreAuthorize(“hasRole('MY_ROLE')”)


 @EnableReactiveMethodSecurity 

Et dans ce module, le bean SecurityWebFilterChain est crĂ©Ă©, ce qui configure l'accĂšs Ă  l'actionneur, l'URL autorisĂ©e et l'URL sur laquelle le jeton JWT est vĂ©rifiĂ©. Il convient de noter que l'accĂšs au filtre de jeton JWT doit ĂȘtre ouvert.


Configuration de SpringWebFluxConfig.java


Dans cette configuration, les MapReactiveUserDetailsService sont créés pour configurer l'actionneur et les autres utilisateurs du systÚme en mémoire.


 @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); } 

Le ReactiveUserDetailsService qui est nécessaire pour assembler notre référentiel d'utilisateurs avec Spring Security .


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

Un bean pour crĂ©er un client WebClient pour effectuer des requĂȘtes rĂ©actives.


 @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())); } 

Lors de la création, deux filtres sont ajoutés. LoadBalancer et un filtre qui prennent une instance d' Authentication du contexte ReactiveSecurityContext et en créent un jeton afin que le serveur cible l'authentifie et l'autorise en conséquence.


Et pour la commodité de travailler avec le type et les dates MongoDB ObjectId , j'ai ajouté un bac de création 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(); } 

Service de jeu de microservice


Le service de jeu de microservice a la structure suivante:


Texte alternatif


Comme vous pouvez le voir, un seul fichier de configuration ApplicationConfig


Configurateur ApplicationConfig.java


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

Il contient une variable avec l'adresse du user-service et il y a deux annotations intéressantes:


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

Cette annotation est requise pour indiquer le référentiel MongoDB au configurateur.


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

Cette annotation importe les configurations du auth-module .


Service GameService.java


Ce service n'a que le code intéressant suivant:


 @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(); } 

Cette méthode lÚve aléatoirement une exception et Hystrix conformément à l'annotation renvoie le résultat de la méthode suivante:


 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); } 

Comme indiqué dans le livre mentionné ci-dessus, si quelque chose est cassé, montrons mieux que rien les données mises en cache ou alternatives.


Service webapi de microservice


Il s'agit d'une sorte de middleware entre la passerelle et les microservices internes qui ne sont pas visibles de l'extérieur. Le but de ce service est d'obtenir une sélection d'autres services et de former une réponse à l'utilisateur sur sa base.


Texte alternatif


Nous commençons l'examen avec la configuration.


Configuration de 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 ) { 

Ici, nous créons un gestionnaire d'authentification à partir des services userDetailsService , que nous avons définis plus tÎt dans le auth-module .


  UserDetailsRepositoryReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService); 

Et nous créons un filtre avec ce gestionnaire, et ajoutons également un convertisseur d'instance d'authentification afin d'obtenir les données utilisateur encodées en 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); }) ); 

Nous ajoutons un gestionnaire d'autorisation rĂ©ussi, dont l'essence consiste Ă  placer le jeton JWT dans l'en-tĂȘte de demande gĂ©nĂ©rĂ© Ă  partir de l' Authentication afin que l'authentification ne puisse ĂȘtre effectuĂ©e qu'Ă  l'aide d'un jeton invitĂ© valide.


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

Nous rĂ©solvons les adresses de la liste blanche. Comme je l'ai Ă©crit plus tĂŽt, les adresses qui seront traitĂ©es par le filtre JWT doivent Ă©galement ĂȘtre ouvertes


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

Nous protégeons l'actionneur et certaines adresses avec une authentification de base


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

Rendre l'accĂšs au jeton d'authentification obligatoire


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

Ajoutez des filtres. Pour authentifier et vérifier le jeton JWT.


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

Et comme je l'ai écrit ci-dessus, ce service désactive la vérification des jetons JWT, commune aux autres services, en spécifiant la valeur de la variable micoservice=false dans le fichier application.properites .


ContrĂŽleur d'Ă©mission, d'enregistrement et d'autorisation de jetons AuthController.java


Je ne décrirai pas ce contrÎleur, car il est purement spécifique.


Service WebApiService.java


Ce service est appelé dans le contrÎleur WebApiMethodProtectedController.jav et a une annotation intéressante:


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

Cette annotation permet d'accéder aux méthodes uniquement aux utilisateurs autorisés ayant le rÎle d'invité.


Comment tester


Créez un environnement:


Texte alternatif


Obtenez un jeton


Texte alternatif


Mettez à jour la variable TOKEN dans l'environnement avec le jeton reçu.


Enregistrer un nouvel utilisateur


Texte alternatif


AprÚs l'enregistrement, vous recevrez un jeton d'utilisateur. Il expire dans 10 heures. Lorsqu'il expire, vous devez en obtenir un nouveau. Pour ce faire, demandez à nouveau le jeton invité, mettez à jour l'environnement et exécutez la demande


Texte alternatif


Ensuite, vous pouvez obtenir une liste d'utilisateurs, de jeux ou créer un nouveau jeu. Et testez également Hystrix, consultez les configurations de service et chiffrez les variables pour le référentiel git.


Les références


Source: https://habr.com/ru/post/fr434810/


All Articles