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);
}
}
Теперь обновим конфигурацию безопасности, чтобы добавить