- — вызывается клиентским приложением пользовательского интерфейса — использует аутентификацию на основе JWT.
Код: Выделить всё
/api/**
- — вызываются другим приложением — должны быть защищены аутентификацией клиента.
Код: Выделить всё
/integration/**
- Остальное — общедоступно.
Я сделал это после нескольких сообщений здесь с похожими вопросами и придумал систему безопасности. конфигурация, как показано ниже. Все работает так, как и ожидалось, но, как видите, мне придется выполнить множество дополнительных проверок. Есть ли лучший способ добиться этого?
Эта конфигурация устанавливает /api/** для использования проверки JWT, а остальная часть открыта для публичного доступа.
Код: Выделить всё
@Bean
public SecurityFilterChain configureSecurityFilters(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(a -> a
.requestMatchers(new AntPathRequestMatcher("/api/**")).authenticated()
.anyRequest().permitAll()
)
.csrf().disable()
.addFilterBefore(jwtValidationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
Код: Выделить всё
@Bean
@Order(1)
public SecurityFilterChain x502Config(HttpSecurity http, HttpServletRequest request) throws Exception {
http
.securityMatcher("/integration/**")
.csrf().disable()
.addFilterBefore(customX509AuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests()
.anyRequest().permitAll();
return http.build();
}
Код: Выделить всё
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.Arrays;
@Component
public class CustomX509AuthenticationFilter extends OncePerRequestFilter {
@Value("${server.ssl.client-auth}")
private String mutualTLSConfig;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
X509Certificate[] clientCertificates = (X509Certificate[]) request.getAttribute(
"jakarta.servlet.request.X509Certificate");
if (isMutualTLSEnabled() && (clientCertificates == null
|| clientCertificates.length == 0)) {
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.name());
return;
} else if (isMutualTLSEnabled()) {
X509Certificate clientCertificate = clientCertificates[0];
String username = extractUsernameFromCertificate(clientCertificate);
Authentication authentication = new UsernamePasswordAuthenticationToken(username, "",
Arrays.asList());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String extractUsernameFromCertificate(X509Certificate certificate) {
Principal principal = certificate.getSubjectDN();
String name = principal.getName();
int startIndex = name.indexOf("CN=") + 3;
int endIndex = name.indexOf(",", startIndex);
if (endIndex == -1) {
endIndex = name.length();
}
return name.substring(startIndex, endIndex);
}
// Ignore this validation if "none" is set (dev purposes)
// "need" will never be used in this application (because if used, client-auth will be applied to all requests)
private boolean isMutualTLSEnabled() {
return StringUtils.isNotEmpty(mutualTLSConfig) && mutualTLSConfig.equalsIgnoreCase("want");
}
// I had to add this, otherwise all requests goes through this - which means the "securityMatcher" configuration is not applied properly.
@Override protected boolean shouldNotFilter(HttpServletRequest request) {
RequestMatcher matcher = new NegatedRequestMatcher(new AntPathRequestMatcher("/integration/**"));
return matcher.matches(request);
}
}
Код: Выделить всё
jwtValidationFilter
Код: Выделить всё
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
final String requstUri = req.getRequestURI();
final String jwtAuthToken = getCookieValue(req, AUTH_TOKEN_COOKIE_NAME);
// Validates the token and set the authentication in SecurityContextHolder
....
....
UsernamePasswordAuthenticationToken authentication = getAuthentication(jwtToken, res);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
Использование JDK 17, Spring Boot 3.0.5 .
Подробнее здесь: https://stackoverflow.com/questions/761 ... e-with-jwt