Введение в OAuth 2.0 и JWT на Java
Современные веб-приложения и API редко обходятся без механизмов аутентификации и авторизации. Два самых популярных стандарта в этой области — OAuth 2.0 и JWT (JSON Web Token). Если вы начинающий Java-разработчик, понимание этих технологий откроет вам путь к созданию безопасных и масштабируемых систем.
OAuth 2.0 — это протокол авторизации, который позволяет приложениям получать ограниченный доступ к ресурсам пользователя без передачи его учетных данных. JWT, в свою очередь, — это компактный формат токена, который используется для передачи утверждений (claims) между сторонами. В этой статье мы разберем, как объединить эти технологии в Java-приложении, используя Spring Security и популярные библиотеки.
Основы OAuth 2.0: как это работает
OAuth 2.0 определяет несколько ролей участников взаимодействия:
- Resource Owner — пользователь, владеющий защищенными данными
- Client — приложение, которое запрашивает доступ
- Authorization Server — сервер, выдающий токены после аутентификации
- Resource Server — сервер, хранящий защищенные ресурсы
Основная идея протокола — клиент получает временный токен доступа, который предъявляет ресурсному серверу. Самый распространенный сценарий — Authorization Code Flow, который используется в веб-приложениях.
Пример настройки OAuth 2.0 клиента в Spring Boot
Добавьте зависимость в pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
Настройте application.yml для работы с Google OAuth 2.0:
spring:
security:
oauth2:
client:
registration:
google:
client-id: YOUR_CLIENT_ID
client-secret: YOUR_CLIENT_SECRET
scope:
- email
- profile
Теперь Spring Security автоматически обработает редирект на страницу входа Google и вернет токен доступа.
JWT: структура и создание токена
JWT состоит из трех частей, разделенных точками:
- Header — содержит тип токена и алгоритм подписи (например, HS256)
- Payload — содержит утверждения (claims): данные пользователя, срок действия, роль
- Signature — подпись, созданная из заголовка и полезной нагрузки с использованием секретного ключа
Для работы с JWT в Java чаще всего используют библиотеку jjwt. Добавьте зависимость:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
Создание JWT токена на Java
Вот пример класса для генерации и валидации JWT:
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import java.util.Date;
public class JwtUtil {
private final SecretKey secretKey = Keys.hmacShaKeyFor(
"my-secret-key-which-should-be-at-least-256-bits-long".getBytes()
);
public String generateToken(String username, String role) {
return Jwts.builder()
.subject(username)
.claim("role", role)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + 3600000)) // 1 час
.signWith(secretKey)
.compact();
}
public Claims validateToken(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload();
}
}
Обратите внимание: в реальном проекте секретный ключ должен храниться в безопасном месте (например, в переменных окружения или Vault).
Интеграция OAuth 2.0 и JWT в REST API
Типичная архитектура выглядит так:
- Пользователь входит через OAuth 2.0 провайдера (Google, GitHub)
- Сервер получает от провайдера токен доступа и информацию о пользователе
- Сервер создает собственный JWT токен с необходимыми утверждениями
- Клиент использует JWT для доступа к защищенным эндпоинтам
Фильтр JWT для Spring Security
Создайте фильтр, который перехватывает запросы и проверяет JWT:
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Collections;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
try {
var claims = jwtUtil.validateToken(token);
String username = claims.getSubject();
String role = claims.get("role", String.class);
var auth = new UsernamePasswordAuthenticationToken(
username, null,
Collections.singleton(() -> "ROLE_" + role)
);
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (Exception e) {
SecurityContextHolder.clearContext();
}
}
filterChain.doFilter(request, response);
}
}