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