Ответ
JWT (JSON Web Token) — стандарт для создания токенов доступа, широко используемый в REST API и микросервисных архитектурах.
Архитектура JWT-авторизации:
graph LR
A[Клиент] -->|1. Логин/пароль| B[Сервер авторизации]
B -->|2. Проверка учетных данных| C[(База данных)]
B -->|3. Генерация JWT| D[Клиент]
D -->|4. Запрос с токеном| E[Защищенный ресурс]
E -->|5. Верификация токена| F[Доступ к ресурсу]
1. Генерация JWT токена (на стороне сервера):
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
// Секретный ключ (должен храниться безопасно!)
String secretKey = "mySuperSecretKeyAtLeast32CharactersLong";
// Создание JWT
String token = Jwts.builder()
.setSubject(user.getUsername()) // Идентификатор пользователя
.claim("roles", user.getRoles()) // Дополнительные claims
.claim("userId", user.getId())
.setIssuedAt(new Date()) // Время создания
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 час
.signWith(SignatureAlgorithm.HS512, secretKey.getBytes()) // Алгоритм подписи
.compact();
// Отправка клиенту
response.setHeader("Authorization", "Bearer " + token);
2. Верификация токена (в каждом защищенном endpoint):
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
public boolean validateToken(String token) {
try {
Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(secretKey.getBytes())
.parseClaimsJws(token);
Claims claims = claimsJws.getBody();
String username = claims.getSubject();
List<String> roles = claims.get("roles", List.class);
// Проверка expiration
return !claims.getExpiration().before(new Date());
} catch (Exception e) {
// Невалидный токен
return false;
}
}
3. Spring Security интеграция (рекомендуемый способ):
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public JwtAuthenticationFilter jwtFilter() {
return new JwtAuthenticationFilter();
}
}
// Кастомный фильтр для JWT
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
if (jwtUtil.validateToken(token)) {
Authentication auth = jwtUtil.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
chain.doFilter(request, response);
}
}
4. Best Practices и безопасность:
- Хранение секретного ключа: Используйте environment variables или специализированные хранилища (HashiCorp Vault, AWS Secrets Manager)
- Алгоритмы подписи:
HS256/HS512— симметричные (требуют общего секрета)RS256/RS512— асимметричные (private key для подписи, public для проверки)
- Refresh tokens: Для продления сессии без повторного логина
{ "access_token": "eyJ...", "refresh_token": "def...", "expires_in": 3600 } - Защита от компрометации:
- Короткое время жизни access token (15-60 минут)
- Blacklist токенов при logout
- HTTPS обязателен
- Проверка issuer (
iss) и audience (aud)
5. Альтернативы и дополнения:
- OAuth 2.0 — полный протокол авторизации (JWT часто используется как access token)
- OpenID Connect — идентификация поверх OAuth 2.0
- Spring Security OAuth2 — готовая реализация для Spring приложений
Ответ 18+ 🔞
О, слушай, смотри, вот эта вся хуйня с JWT — это ж как в баню с собой тазик тащить, только в мире апишек. Все им пользуются, а половина нихуя не понимает, как оно внутри работает, просто тыкают, пока не заработает.
Архитектура, блядь, этой пляски с бубном:
graph LR
A[Клиент] -->|1. Логин/пароль| B[Сервер авторизации]
B -->|2. Проверка учетных данных| C[(База данных)]
B -->|3. Генерация JWT| D[Клиент]
D -->|4. Запрос с токеном| E[Защищенный ресурс]
E -->|5. Верификация токена| F[Доступ к ресурсу]
1. Рождение токена, или «Пойди туда, не знаю куда» (на сервере):
Смотри, вот ты на сервере, пользователь прислал логин-пароль, ты проверил — вроде не мудак. Надо ему пропуск выписать. Делается это так:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
// Секретный ключ (это твоя мантра, храни как зеницу ока, а то потом охуеешь!)
String secretKey = "mySuperSecretKeyAtLeast32CharactersLong";
// Начинаем колдовать
String token = Jwts.builder()
.setSubject(user.getUsername()) // Кто этот тип?
.claim("roles", user.getRoles()) // А чем он, блядь, дышит? Какие права?
.claim("userId", user.getId())
.setIssuedAt(new Date()) // Когда выписан?
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // Годится до... (час)
.signWith(SignatureAlgorithm.HS512, secretKey.getBytes()) // И ставим печать, мать её!
.compact();
// И тыкаем ему этот пропуск в заголовок
response.setHeader("Authorization", "Bearer " + token);
Вот и всё, токен готов. Клиент его получил и теперь ходит с ним, как дурак с писаной торбой.
2. Проверка пропуска, или «А ты кто такой?» (в каждом защищённом методе):
А теперь представь: приходит этот тип к твоему endpoint, суёт свой токен. А ты должен понять — свой он или левый.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
public boolean validateToken(String token) {
try {
// Пытаемся разобрать эту абракадабру
Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(secretKey.getBytes()) // С тем же секретным ключом!
.parseClaimsJws(token);
Claims claims = claimsJws.getBody();
String username = claims.getSubject(); // Ага, так ты Вася!
List<String> roles = claims.get("roles", List.class); // И права вот эти...
// Смотрим, не просрочен ли пропуск
return !claims.getExpiration().before(new Date());
} catch (Exception e) {
// Токен кривой, поддельный или срок вышел — нахуй такого гостя
return false;
}
}
3. Интеграция со Spring Security, чтобы не ебаться вручную каждый раз:
Ну это для ленивых, но умных. Настраиваешь один раз и потом только попиваешь чаёк.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // Для API часто выключают, но знай, что делаешь!
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll() // Сюда пускаем всех
.antMatchers("/api/admin/**").hasRole("ADMIN") // Сюда только начальство
.anyRequest().authenticated() // Всё остальное — только по пропускам
.and()
.addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class); // Ставим наш фильтр-шпингалет
}
@Bean
public JwtAuthenticationFilter jwtFilter() {
return new JwtAuthenticationFilter();
}
}
// Сам фильтр, который каждого входящего будет щупать
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) {
String header = request.getHeader("Authorization"); // Ищет волшебную бумажку
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7); // Отрезаем "Bearer "
if (jwtUtil.validateToken(token)) { // Проверяем
Authentication auth = jwtUtil.getAuthentication(token); // Создаём объект аутентификации
SecurityContextHolder.getContext().setAuthentication(auth); // И кладём в контекст
}
}
chain.doFilter(request, response); // Пропускаем дальше
}
}
4. Best Practices, или как не обосраться:
- Ключ, ёпта! Не хардкодь его в коде, как последний ламер. Environment variables, Vault, что угодно. Иначе однажды проснёшься знаменитым.
- Алгоритмы:
HS256/HS512— симметричные. Один ключ и на подпись, и на проверку. Просто, но если ключ утек — пиздец всему.RS256/RS512— асимметричные. Приватным ключом подписал, публичным проверил. Умнее.
- Refresh tokens: Чтобы пользователь каждый час не бегал логиниться, как угорелый. Даёшь ему два токена: короткоживущий
access_token(час) и долгоживущийrefresh_token(неделя). Первый кончился — меняешь по второму.{ "access_token": "eyJ...", "refresh_token": "def...", "expires_in": 3600 } - Безопасность, мать её:
- Жизнь access token — 15-60 минут. Не делай его бессмертным.
- При логауте хорошо бы токены в чёрный список (blacklist) пихать, но это уже сложнее.
- HTTPS — ОБЯЗАТЕЛЕН. Иначе все твои токены летят по сети как голые по проспекту.
- Проверяй
iss(кто выдал) иaud(для кого), если в этом есть смысл.
5. А что ещё есть, кроме этого?
- OAuth 2.0 — это уже не просто токен, а целый протокол с дедушкой-авторизационным сервером, бабушкой-ресурсным сервером и внучкой-клиентом. JWT часто внутри него в качестве access token используется.
- OpenID Connect — надстройка над OAuth 2.0, которая ещё и личность пользователя подтверждает.
- Spring Security OAuth2 — если ты на Spring, то можешь не изобретать велосипед, а взять готовый, с багажником и фарами.
Вот и вся магия. Главное — не накосячить с безопасностью, а то будет не «JWT-авторизация», а «JWT-аутентификация твоих косяков перед всем интернетом».