OAuth 2.0 и JWT авторизация на Java: пошаговое руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

OAuth 2.0 и JWT авторизация на Java: на практике

Современные веб-приложения и API редко обходятся без механизмов аутентификации и авторизации. Два самых популярных стандарта — OAuth 2.0 и JWT (JSON Web Token) — позволяют безопасно передавать данные о пользователе и предоставлять доступ к ресурсам. В этой статье мы разберём, как реализовать OAuth 2.0 и JWT авторизацию на Java на практическом примере с использованием Spring Boot. Вы узнаете, как настроить сервер авторизации, генерировать и проверять JWT-токены, а также защитить конечные точки вашего REST API.

Что такое OAuth 2.0 и JWT: краткий обзор

OAuth 2.0 — это протокол делегированного доступа, который позволяет сторонним приложениям получать ограниченный доступ к ресурсам пользователя без передачи его учётных данных. Он широко используется для входа через социальные сети (Google, Facebook) и для защиты API.

JWT (JSON Web Token) — это компактный и самодостаточный формат токена, который содержит информацию о пользователе (claims) и подписан для проверки целостности. JWT часто используется как access token в OAuth 2.0, поскольку он не требует хранения на сервере и легко передаётся в HTTP-заголовках.

На практике OAuth 2.0 определяет поток (flow) получения токена, а JWT — формат самого токена. Вместе они образуют мощную и безопасную систему авторизации.

Настройка проекта Spring Boot с зависимостями

Для начала создадим базовый проект Spring Boot. Мы будем использовать Spring Security, Spring Web и библиотеку для работы с JWT (например, jjwt).

Добавьте следующие зависимости в ваш pom.xml (Maven) или build.gradle (Gradle):

<!-- Maven -->
<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>

Теперь создадим класс конфигурации безопасности, который отключит CSRF и определит цепочку фильтров.

Реализация сервера авторизации и генерация JWT

Для OAuth 2.0 нам потребуется эндпоинт, который будет принимать учётные данные клиента и возвращать JWT-токен. В простейшем случае это может быть имитация сервера авторизации.

Создадим сервис для работы с JWT:

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Service;

import javax.crypto.SecretKey;
import java.util.Date;

@Service
public class JwtService {

    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()
                .subject(username)
                .issuedAt(now)
                .expiration(expiryDate)
                .signWith(secretKey)
                .compact();
    }

    public String extractUsername(String token) {
        return Jwts.parser()
                .verifyWith(secretKey)
                .build()
                .parseSignedClaims(token)
                .getPayload()
                .getSubject();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser()
                .verifyWith(secretKey)
                .build()
                .parseSignedClaims(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

Теперь создадим контроллер для аутентификации:

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/auth")
public class AuthController {

    private final JwtService jwtService;

    public AuthController(JwtService jwtService) {
        this.jwtService = jwtService;
    }

    @PostMapping("/login")
    public String login(@RequestParam String username, @RequestParam String password) {
        // В реальном проекте здесь проверка пользователя в БД
        if ("admin".equals(username) && "password".equals(password)) {
            return jwtService.generateToken(username);
        }
        throw new RuntimeException("Invalid credentials");
    }
}

После вызова /api/auth/login с правильными учётными данными вы получите JWT-токен.

Защита REST API с помощью JWT фильтра

Чтобы защитить конечные точки, нам нужно создать фильтр, который будет перехватывать каждый запрос, извлекать JWT из заголовка Authorization и проверять его.

Создадим фильтр:

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.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Collections;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtService jwtService;

    public JwtAuthenticationFilter(JwtService jwtService) {
        this.jwtService = jwtService;
    }

    @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);
            if (jwtService.validateToken(token)) {
                String username = jwtService.extractUsername(token);
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(
                                username, null, Collections.emptyList());
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        filterChain.doFilter(request, response);
    }
}

Теперь обновим конфигурацию безопасности, чтобы добавить

Блоги

Рекомендаций по книгам