Введение в OAuth 2.0 и JWT на Java
Современные веб-приложения и API требуют надежных механизмов аутентификации и авторизации. Два наиболее популярных стандарта в этой области — OAuth 2.0 и JWT (JSON Web Tokens). Если вы начинающий Java-разработчик, понимание этих технологий откроет вам путь к созданию безопасных и масштабируемых приложений.
В этой статье мы разберем, что такое OAuth 2.0 и JWT, как они работают вместе, и как реализовать их на Java с использованием популярных библиотек. Вы узнаете, как настроить сервер авторизации, генерировать и проверять JWT-токены, а также защитить свои REST API.
Что такое OAuth 2.0 и JWT?
OAuth 2.0 — протокол делегированного доступа
OAuth 2.0 — это протокол авторизации, который позволяет приложениям получать ограниченный доступ к ресурсам пользователя без передачи его учетных данных. Представьте, что вы хотите дать приложению доступ к вашим фотографиям в Google. Вместо того чтобы сообщать приложению свой пароль от Google, вы используете OAuth 2.0, который выдает приложению временный токен с ограниченными правами.
Основные участники OAuth 2.0:
- Resource Owner — пользователь, владеющий ресурсами
- Client — приложение, запрашивающее доступ
- Authorization Server — сервер, выдающий токены
- Resource Server — сервер, предоставляющий защищенные ресурсы
JWT — компактный формат токена
JWT (JSON Web Token) — это открытый стандарт (RFC 7519) для создания токенов доступа, которые содержат утверждения (claims) в формате JSON. JWT состоит из трех частей, разделенных точками:
- Header — содержит тип токена и алгоритм подписи (например, HS256 или RS256)
- Payload — содержит утверждения (например, ID пользователя, роли, срок действия)
- Signature — подпись, которая гарантирует целостность токена
Пример JWT: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
Реализация OAuth 2.0 и JWT на Java
Настройка проекта с Spring Security
Для реализации OAuth 2.0 и JWT на Java мы будем использовать Spring Boot и Spring Security. Начнем с добавления зависимостей в файл pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
Создание JWT-провайдера
Создадим класс JwtProvider, который будет отвечать за генерацию и валидацию JWT-токенов:
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
@Component
public class JwtProvider {
private final SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
private final long expirationMs = 3600000; // 1 час
public String generateToken(String username) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expirationMs);
return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(secretKey)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
public String getUsernameFromToken(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
}
Реализация OAuth 2.0 Resource Server
Теперь создадим конфигурацию безопасности для защиты наших эндпоинтов:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
}
Создание фильтра аутентификации
Фильтр будет перехватывать все запросы, извлекать JWT из заголовка и проверять его:
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java