Как сделать аутентификацию JWT максимально безопасной?JAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Как сделать аутентификацию JWT максимально безопасной?

Сообщение Anonymous »

Я изучаю и внедряю аутентификацию JWT с помощью Spring Boot, Spring Security и библиотеки io.jsonwebtoken (jjwt). У меня есть несколько вопросов, поэтому, если вы мне поможете, я буду признателен. Ниже приведены мои вопросы:
  • Мне не нужны определенные URL-адреса, такие как /api/auth/refresh-token, /api/auth/ access-token для прохождения класса фильтрации jwt, поскольку я хочу заменить старый просроченный/недействительный токен на более новый. Если мы не внесем в белый список, аутентификация всегда будет неудачной.

    Прав ли я, внося эти URL-адреса в белый список для фильтрации jwt?
  • Правильно ли? использование метода mustNotFilter() класса OncePerRequestFilter уместно в данном случае или это нужно как-то сделать в классе SecurityConfiguration?
[*]У меня есть конечная точка /api/auth/login, которая разрешена для всех в классе SecurityConfiguration, но я не внес белый список в JwtAuthenticationFilter класс, и тем не менее он работает нормально и генерирует токены обновления и доступа.
[*]Я получаю исключение TokenExpiration, когда пытаюсь извлечь имя пользователя из старого токена, что кажется очевидным, но как тогда я буду извлекать имя пользователя, которое будет использоваться для генерации нового токена?
[*]Должен ли я проверять, истек ли срок действия старого токена, прежде чем создавать новые токены, или мне все равно следует его генерировать, если клиент запрашивает его?
5.Должен ли я также генерировать новый токен доступа при создании нового токена обновления?

Класс SecurityConfiguration:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(registry -> {
registry.requestMatchers("/api/auth/**").permitAll();
registry.anyRequest().authenticated();
})
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.
STATELESS
))
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin(AbstractAuthenticationFilterConfigurer::permitAll)
.build();
}@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(registry -> {
registry.requestMatchers("/api/auth/**").permitAll();
registry.anyRequest().authenticated();
})
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin(AbstractAuthenticationFilterConfigurer::permitAll)
.build();
}

Класс JwtAuthenticationFilter:
@Override
protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response,
@Nonnull FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
final String jwt;

if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}

jwt = authHeader.substring(7);
String username = jwtService.extractUsername(jwt);

if (username != null && SecurityContextHolder.
getContext
().getAuthentication() == null) {
var user = userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(jwt, user)) {

if (jwtService.isTokenExpired(jwt)) {
throw new TokenExpiredException("Token expired");
}

UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(
user,
null,
user.getAuthorities()
);
usernamePasswordAuthenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.
getContext
().setAuthentication(usernamePasswordAuthenticationToken);
} else {
response.setStatus(HttpServletResponse.
SC_UNAUTHORIZED
);
response.getWriter().write("Unauthorized: Invalid token");
return;
}
}
filterChain.doFilter(request, response);
}

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
return path.startsWith("/api/auth/refresh-token");
}

Метод класса JwtService (место, где я получаю исключение TokenExpired):
@Service
public class JwtService {

@Value("${jwt.secret.key}")
private String secretKey;

@Value("${jwt.access.expiration.time}")
private long accessTokenExpiryDate;

@Value("${jwt.refresh.expiration.time}")
private long refreshTokenExpiryDate;

@Value("${spring.application.name}")
private String issuer;

public String extractUsername(String token) {
return extractClaims(token, Claims::getSubject);
}

public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap(), userDetails, accessTokenExpiryDate);
}

public String generateRefreshToken(UserDetails userDetails) {
return generateToken(new HashMap(), userDetails, refreshTokenExpiryDate);
}

public boolean isRefreshTokenValid(String token, UserDetails userDetails) {
return isTokenValid(token, userDetails);
}

public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = extractUsername(token);
final String tokenIssuer = extractClaims(token, Claims::getIssuer);
return tokenIssuer.equalsIgnoreCase(issuer)
&& username.equalsIgnoreCase(userDetails.getUsername());
}

private String generateToken(Map extraClaims, UserDetails userDetails, long expiryDate) {
return Jwts.builder()
.id(UUID.randomUUID().toString())
.claim("authorities", Arrays.toString(userDetails.getAuthorities().toArray()))
.claims(extraClaims)
.issuer(issuer)
.subject(userDetails.getUsername())
.issuedAt(Date.from(Instant.now()))
.expiration(Date.from(Instant.now().plusMillis(expiryDate)))
.signWith(getSecretKey())
.compact();
}

public boolean isTokenExpired(String token) {
return extractExpiration(token).before(Date.from(Instant.now()));
}

private Date extractExpiration(String token) {
return extractClaims(token, Claims::getExpiration);
}

private Claims extractAllClaims(String token) {
return Jwts.parser()
.verifyWith(getSecretKey())
.build()
.parseSignedClaims(token)
.getPayload();
}

private SecretKey getSecretKey() {
byte[] secretKeyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(secretKeyBytes);
}

private T extractClaims(String token, Function claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
}

Методы класса UserService:
public TokenResponse refreshToken(String oldRefreshToken) {

String jwt = oldRefreshToken.substring(7);

var username = jwtService.extractUsername(jwt);
var user = userRepository.findUserByUsernameEqualsIgnoreCase(username).orElseThrow(UserNotFoundException::new);
if (!jwtService.isRefreshTokenValid(jwt, user)) {
throw new TokenExpiredException("Refresh token is not expired yet");
}
var newRefreshToken = jwtService.generateRefreshToken(user);
var newAccessToken = jwtService.generateToken(user);
return new TokenResponse(newAccessToken, newRefreshToken);
}

public String accessToken(String refreshToken) {
var jwt = refreshToken.substring(7);
var username = jwtService.extractUsername(jwt);
var user = userRepository.findUserByUsernameEqualsIgnoreCase(username).orElseThrow(UserNotFoundException::new);
if (!jwtService.isRefreshTokenValid(jwt, user)) {
throw new TokenExpiredException("Refresh token is not expired yet");
}
return jwtService.generateToken(user);
}
public TokenResponse refreshToken(String oldRefreshToken) {

String jwt = oldRefreshToken.substring(7);

var username = jwtService.extractUsername(jwt);
var user = userRepository.findUserByUsernameEqualsIgnoreCase(username).orElseThrow(UserNotFoundException::new);
if (!jwtService.isRefreshTokenValid(jwt, user)) {
throw new TokenExpiredException("Refresh token is not expired yet");
}
var newRefreshToken = jwtService.generateRefreshToken(user);
var newAccessToken = jwtService.generateToken(user);
return new TokenResponse(newAccessToken, newRefreshToken);
}

public String accessToken(String refreshToken) {
var jwt = refreshToken.substring(7);
var username = jwtService.extractUsername(jwt);
var user = userRepository.findUserByUsernameEqualsIgnoreCase(username).orElseThrow(UserNotFoundException::new);
if (!jwtService.isRefreshTokenValid(jwt, user)) {
throw new TokenExpiredException("Refresh token is not expired yet");
}
return jwtService.generateToken(user);
}


Подробнее здесь: https://stackoverflow.com/questions/793 ... s-possible
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»