Введение в OAuth 2.0 и JWT авторизацию на Java
Современные веб-приложения и API требуют надежных механизмов аутентификации и авторизации. Два стандарта, которые стали де-факто в мире Java-разработки — это OAuth 2.0 и JWT (JSON Web Tokens). OAuth 2.0 предоставляет протокол для делегирования доступа, а JWT — компактный и самодостаточный формат передачи данных. В этой статье мы разберем, как объединить эти технологии на Java с использованием Spring Security и популярных библиотек.
Вы узнаете, как настроить сервер авторизации, генерировать и проверять JWT-токены, а также защитить свои REST-эндпоинты. Материал будет полезен как начинающим, так и опытным Java-разработчикам, желающим углубить знания в области безопасности.
Основы OAuth 2.0: Потоки и роли
OAuth 2.0 определяет четыре роли: Resource Owner (владелец ресурса), Client (приложение), Authorization Server (сервер авторизации) и Resource Server (сервер ресурсов). Наиболее популярные потоки (grants) включают:
- Authorization Code — для веб-приложений с серверной частью.
- Client Credentials — для сервер-серверной коммуникации.
- Resource Owner Password — для доверенных клиентов (устаревает).
- Implicit — для SPA (больше не рекомендуется).
На Java мы можем реализовать OAuth 2.0 сервер с помощью spring-security-oauth2-authorization-server. Рассмотрим пример настройки базового сервера авторизации.
Настройка Authorization Server на Spring Boot
Добавьте зависимости в pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Создайте конфигурацию SecurityConfig.java:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId("client-id")
.clientId("my-client")
.clientSecret("{noop}secret")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("http://localhost:8080/login/oauth2/code/my-client")
.scope("read")
.scope("write")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
}
JWT: Структура и генерация токенов
JWT состоит из трех частей: Header, Payload и Signature. Header содержит тип токена и алгоритм шифрования. Payload включает claims (утверждения), такие как sub (субъект), exp (время истечения) и roles. Signature обеспечивает целостность данных.
Для работы с JWT на Java используем библиотеку io.jsonwebtoken:jjwt. Добавьте зависимость:
<dependency>
<groupId>io.jsonwebtoken>
<artifactId>jjwt-api</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken>
<artifactId>jjwt-impl</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
Создание JWT Token Provider
Реализуем утилитарный класс для генерации и валидации токенов:
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
@Component
public class JwtTokenProvider {
private final SecretKey secretKey = Keys.hmacShaKeyFor("my-secret-key-must-be-at-least-256-bits-long".getBytes());
private final long expirationMs = 3600000; // 1 час
public String generateToken(String username, String role) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expirationMs);
return Jwts.builder()
.subject(username)
.claim("role", role)
.issuedAt(now)
.expiration(expiryDate)
.signWith(secretKey)
.compact();
}
public String getUsernameFromToken(String token) {
return parseClaims(token).getSubject();
}
public boolean validateToken(String token) {
try {
parseClaims(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
private Claims parseClaims(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload();
}
}
Интеграция OAuth 2.0 и JWT с Resource Server
После получения access token от Authorization Server, клиент отправляет его в заголовке Authorization: Bearer <token>. Resource Server должен проверить токен. Настроим Spring Security для работы с JWT.
Конфигурация Resource Server
@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuerUri;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter