0%
Sécurité des Microservices

Sécurité

Sécurité des microservices avec Spring Security et OAuth2

10-15 min

Sécurité des Microservices avec Spring Security et OAuth2

La sécurité est un aspect crucial des architectures microservices. Spring Security et OAuth2 permettent de mettre en place une authentification et une autorisation robustes.

1. Configuration du Serveur d’Authorization

1.1 Création du Projet Auth Server

curl https://start.spring.io/starter.tgz \
    -d dependencies=oauth2-resource-server,oauth2-authorization-server \
    -d groupId=com.example \
    -d artifactId=auth-server \
    -d name=auth-server \
    -d description=OAuth2%20Authorization%20Server \
    -d packageName=com.example.auth \
    -d packaging=jar \
    -d javaVersion=17 \
    -d type=maven-project \
    | tar -xzvf -

1.2 Configuration du Serveur

# application.yml
server:
  port: 9000

spring:
  application:
    name: auth-server
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:9000
          jwk-set-uri: http://localhost:9000/.well-known/jwks.json

1.3 Configuration de la Sécurité

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .csrf().disable()
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/oauth2/**", "/login/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .jwtAuthenticationConverter(jwtAuthenticationConverter())
                )
            )
            .build();
    }

    @Bean
    public Converter<Jwt, AbstractAuthenticationToken> jwtAuthenticationConverter() {
        JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
        jwtConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
            List<String> roles = jwt.getClaimAsStringList("roles");
            return roles.stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toList());
        });
        return jwtConverter;
    }
}

2. Configuration des Clients

2.1 Ajout des Dépendances

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.2 Configuration de la Sécurité

# application.yml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:9000
          jwk-set-uri: http://localhost:9000/.well-known/jwks.json

2.3 Configuration des Services

@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .csrf().disable()
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/actuator/**").permitAll()
                .requestMatchers("/api/products/**").hasRole("USER")
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .jwtAuthenticationConverter(jwtAuthenticationConverter())
                )
            )
            .build();
    }

    @Bean
    public Converter<Jwt, AbstractAuthenticationToken> jwtAuthenticationConverter() {
        JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
        jwtConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
            List<String> roles = jwt.getClaimAsStringList("roles");
            return roles.stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toList());
        });
        return jwtConverter;
    }
}

3. Gestion des Tokens

3.1 Service de Token

@Service
public class TokenService {
    private final JwtEncoder jwtEncoder;
    private final JwtDecoder jwtDecoder;

    public TokenService(JwtEncoder jwtEncoder, JwtDecoder jwtDecoder) {
        this.jwtEncoder = jwtEncoder;
        this.jwtDecoder = jwtDecoder;
    }

    public String generateToken(String username, List<String> roles) {
        Instant now = Instant.now();
        JwtClaimsSet claims = JwtClaimsSet.builder()
            .issuer("self")
            .issuedAt(now)
            .expiresAt(now.plus(1, ChronoUnit.HOURS))
            .subject(username)
            .claim("roles", roles)
            .build();

        return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
    }

    public Jwt decodeToken(String token) {
        return jwtDecoder.decode(token);
    }
}

3.2 Contrôleur d’Authentification

@RestController
@RequestMapping("/auth")
public class AuthController {
    private final TokenService tokenService;

    public AuthController(TokenService tokenService) {
        this.tokenService = tokenService;
    }

    @PostMapping("/token")
    public ResponseEntity<TokenResponse> generateToken(@RequestBody LoginRequest request) {
        // Vérification des credentials (à implémenter selon vos besoins)
        String token = tokenService.generateToken(
            request.getUsername(),
            Collections.singletonList("USER")
        );
        return ResponseEntity.ok(new TokenResponse(token));
    }
}

4. Sécurité Inter-Services

4.1 Configuration du Client OAuth2

@Configuration
public class OAuth2ClientConfig {
    @Bean
    public OAuth2RestTemplate oauth2RestTemplate(
            OAuth2ClientContext oauth2ClientContext,
            OAuth2ProtectedResourceDetails details) {
        return new OAuth2RestTemplate(details, oauth2ClientContext);
    }
}

4.2 Service avec Client OAuth2

@Service
public class OrderService {
    private final OAuth2RestTemplate oauth2RestTemplate;

    public OrderService(OAuth2RestTemplate oauth2RestTemplate) {
        this.oauth2RestTemplate = oauth2RestTemplate;
    }

    public Product getProduct(Long id) {
        return oauth2RestTemplate.getForObject(
            "http://product-service/api/products/" + id,
            Product.class
        );
    }
}

5. Tests

5.1 Test de Sécurité

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class SecurityIntegrationTest {
    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void whenUnauthorized_then401IsReturned() {
        ResponseEntity<Product> response = restTemplate.getForEntity(
            "/api/products/1", Product.class);
        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
    }

    @Test
    void whenAuthorized_thenProductIsReturned() {
        String token = generateToken();
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(token);
        HttpEntity<String> entity = new HttpEntity<>(headers);

        ResponseEntity<Product> response = restTemplate.exchange(
            "/api/products/1",
            HttpMethod.GET,
            entity,
            Product.class
        );

        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertNotNull(response.getBody());
    }
}

5.2 Test des Rôles

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class RoleBasedSecurityTest {
    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void whenUserRole_thenCannotAccessAdminEndpoint() {
        String token = generateToken("USER");
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(token);
        HttpEntity<String> entity = new HttpEntity<>(headers);

        ResponseEntity<Void> response = restTemplate.exchange(
            "/api/admin/users",
            HttpMethod.GET,
            entity,
            Void.class
        );

        assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
    }

    @Test
    void whenAdminRole_thenCanAccessAdminEndpoint() {
        String token = generateToken("ADMIN");
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(token);
        HttpEntity<String> entity = new HttpEntity<>(headers);

        ResponseEntity<Void> response = restTemplate.exchange(
            "/api/admin/users",
            HttpMethod.GET,
            entity,
            Void.class
        );

        assertEquals(HttpStatus.OK, response.getStatusCode());
    }
}

Conclusion

La sécurité avec Spring Security et OAuth2 offre plusieurs avantages :

  • Authentification robuste avec JWT
  • Gestion fine des autorisations avec les rôles
  • Sécurité inter-services
  • Intégration avec Spring Cloud
  • Support des standards OAuth2 et OpenID Connect

Dans le prochain tutoriel, nous verrons comment déployer nos microservices avec Docker.

Commentaires

Les commentaires sont alimentés par GitHub Discussions

Connectez-vous avec GitHub pour participer à la discussion

Lien copié !