Keycloak protected API 试图暴露 swagger.json
Keycloak protected API trying to expose swagger.json
我有一个使用 SpringBoot、Jersey2 和 Keycloak 构建的 API。我正在使用 SpringBoot 适配器和 SpringAdapter。一切都很好。
我现在偶然发现了这个页面https://github.com/swagger-api/swagger-core/wiki/Swagger-Core-Jersey-2.X-Project-Setup-1.5
并开始使用 swagger-core 包为我的 API 生成一个 swagger.json
文件。 swagger-jersey2 依赖项将在 link 处公开 swagger.json
文件,如下所示:http://localhost:8080/swagger.json
。但是,我无法公开访问 url 因为 keycloak 阻止了它。
在我下面的 SecurityConfig
class 中,我有以下内容:
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
{
//....other code above
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable().authorizeRequests().antMatchers("*").permitAll();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
如何更改配置以允许在无需传递不记名令牌的情况下访问 swagger.json
? (我测试了使用不记名令牌的访问并且它有效但我需要它可以在没有不记名令牌的情况下访问)
我最终通过执行以下操作解决了这个问题:
- 创建一个新的
PubliResource
注释。
- 在 classes 启动时创建了一个列表,用
PublicResource
注释,然后即使没有检测到标记,也明确地让请求投向那些 classes。
import java.lang.annotation.*;
/***
* Used for marking a class accessible to a non-authorized user for
* @GET, @PUT, @POST, and @DELETE annotations
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface PublicResource {
}
然后我做了一个AuthenticationTokenProcessingFilter
豆子。
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.representations.AccessToken;
import org.reflections.Reflections;
import org.reflections.util.ConfigurationBuilder;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.TreeSet;
public class AuthenticationTokenProcessingFilter extends GenericFilterBean {
private static Set<String> nonAuthenticationWhiteListSet = new TreeSet<>();
static {
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.forPackages("com.package.code.resources"); //IMPORTANT
Reflections reflections = new Reflections(configurationBuilder);
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(PublicResource.class);
for (Class<?> annotatedClass : annotated) {
if (!annotatedClass.isAnnotationPresent(Path.class)) {
continue;
}
String classPath = annotatedClass.getAnnotation(Path.class).value();
for (Method method : annotatedClass.getDeclaredMethods()) {
String fullPath = classPath;
if (method.isAnnotationPresent(GET.class)
|| method.isAnnotationPresent(POST.class)
|| method.isAnnotationPresent(PUT.class)
|| method.isAnnotationPresent(DELETE.class)) {
if (method.isAnnotationPresent(Path.class)) {
fullPath += method.getDeclaredAnnotation(Path.class).value();
}
fullPath = fullPath.replaceAll("\{.*\}", "[^/]+");
nonAuthenticationWhiteListSet.add(fullPath);
}
}
}
}
public AuthenticationTokenProcessingFilter() {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest)) {
throw new RuntimeException("Expecting a HTTP request");
}
RefreshableKeycloakSecurityContext context = (RefreshableKeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
if (context == null) {
handleNoSecurityContext(request, response, chain);
return;
}
chain.doFilter(request, response);
}
private void handleNoSecurityContext(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String path = httpRequest.getPathInfo();
if (isPublicResource(path)) {
chain.doFilter(request, response);
return;
}
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
private boolean isPublicResource(String path) {
for (String regex : nonAuthenticationWhiteListSet) {
if (path.matches(regex)) {
return true;
}
}
return false;
}
}
然后在我的主要 class 中,我这样做是为了将其注册为过滤器。
@Configuration
@EnableAutoConfiguration(exclude = {FallbackWebSecurityAutoConfiguration.class, SpringBootWebSecurityConfiguration.class, DataSourceAutoConfiguration.class})
@ComponentScan
@EnableAsync
public class MyApi {
//...other code plus the main method
@Bean
public AuthenticationTokenProcessingFilter authFilter() {
return new AuthenticationTokenProcessingFilter();
}
}
我有一个使用 SpringBoot、Jersey2 和 Keycloak 构建的 API。我正在使用 SpringBoot 适配器和 SpringAdapter。一切都很好。
我现在偶然发现了这个页面https://github.com/swagger-api/swagger-core/wiki/Swagger-Core-Jersey-2.X-Project-Setup-1.5
并开始使用 swagger-core 包为我的 API 生成一个 swagger.json
文件。 swagger-jersey2 依赖项将在 link 处公开 swagger.json
文件,如下所示:http://localhost:8080/swagger.json
。但是,我无法公开访问 url 因为 keycloak 阻止了它。
在我下面的 SecurityConfig
class 中,我有以下内容:
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
{
//....other code above
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable().authorizeRequests().antMatchers("*").permitAll();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
如何更改配置以允许在无需传递不记名令牌的情况下访问 swagger.json
? (我测试了使用不记名令牌的访问并且它有效但我需要它可以在没有不记名令牌的情况下访问)
我最终通过执行以下操作解决了这个问题:
- 创建一个新的
PubliResource
注释。 - 在 classes 启动时创建了一个列表,用
PublicResource
注释,然后即使没有检测到标记,也明确地让请求投向那些 classes。
import java.lang.annotation.*;
/***
* Used for marking a class accessible to a non-authorized user for
* @GET, @PUT, @POST, and @DELETE annotations
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface PublicResource {
}
然后我做了一个AuthenticationTokenProcessingFilter
豆子。
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.representations.AccessToken;
import org.reflections.Reflections;
import org.reflections.util.ConfigurationBuilder;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.TreeSet;
public class AuthenticationTokenProcessingFilter extends GenericFilterBean {
private static Set<String> nonAuthenticationWhiteListSet = new TreeSet<>();
static {
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.forPackages("com.package.code.resources"); //IMPORTANT
Reflections reflections = new Reflections(configurationBuilder);
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(PublicResource.class);
for (Class<?> annotatedClass : annotated) {
if (!annotatedClass.isAnnotationPresent(Path.class)) {
continue;
}
String classPath = annotatedClass.getAnnotation(Path.class).value();
for (Method method : annotatedClass.getDeclaredMethods()) {
String fullPath = classPath;
if (method.isAnnotationPresent(GET.class)
|| method.isAnnotationPresent(POST.class)
|| method.isAnnotationPresent(PUT.class)
|| method.isAnnotationPresent(DELETE.class)) {
if (method.isAnnotationPresent(Path.class)) {
fullPath += method.getDeclaredAnnotation(Path.class).value();
}
fullPath = fullPath.replaceAll("\{.*\}", "[^/]+");
nonAuthenticationWhiteListSet.add(fullPath);
}
}
}
}
public AuthenticationTokenProcessingFilter() {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest)) {
throw new RuntimeException("Expecting a HTTP request");
}
RefreshableKeycloakSecurityContext context = (RefreshableKeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
if (context == null) {
handleNoSecurityContext(request, response, chain);
return;
}
chain.doFilter(request, response);
}
private void handleNoSecurityContext(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String path = httpRequest.getPathInfo();
if (isPublicResource(path)) {
chain.doFilter(request, response);
return;
}
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
private boolean isPublicResource(String path) {
for (String regex : nonAuthenticationWhiteListSet) {
if (path.matches(regex)) {
return true;
}
}
return false;
}
}
然后在我的主要 class 中,我这样做是为了将其注册为过滤器。
@Configuration
@EnableAutoConfiguration(exclude = {FallbackWebSecurityAutoConfiguration.class, SpringBootWebSecurityConfiguration.class, DataSourceAutoConfiguration.class})
@ComponentScan
@EnableAsync
public class MyApi {
//...other code plus the main method
@Bean
public AuthenticationTokenProcessingFilter authFilter() {
return new AuthenticationTokenProcessingFilter();
}
}