fix: a fatal filter bug (#626)

pull/641/head
John Niang 2020-03-08 11:27:41 +08:00 committed by GitHub
parent fd251a4724
commit 55a9d573bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 105 additions and 140 deletions

View File

@ -6,10 +6,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.client.RestTemplate;
@ -17,17 +15,6 @@ import run.halo.app.cache.InMemoryCacheStore;
import run.halo.app.cache.LevelCacheStore;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.filter.CorsFilter;
import run.halo.app.filter.LogFilter;
import run.halo.app.security.filter.AdminAuthenticationFilter;
import run.halo.app.security.filter.ApiAuthenticationFilter;
import run.halo.app.security.filter.ContentFilter;
import run.halo.app.security.handler.ContentAuthenticationFailureHandler;
import run.halo.app.security.handler.DefaultAuthenticationFailureHandler;
import run.halo.app.security.service.OneTimeTokenService;
import run.halo.app.service.OptionService;
import run.halo.app.service.UserService;
import run.halo.app.utils.HaloUtils;
import run.halo.app.utils.HttpClientUtils;
import java.security.KeyManagementException;
@ -83,123 +70,4 @@ public class HaloConfiguration {
}
/**
* Creates a CorsFilter.
*
* @return Cors filter registration bean
*/
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> corsFilter = new FilterRegistrationBean<>();
corsFilter.setOrder(Ordered.HIGHEST_PRECEDENCE + 10);
corsFilter.setFilter(new CorsFilter());
corsFilter.addUrlPatterns("/api/*");
return corsFilter;
}
/**
* Creates a LogFilter.
*
* @return Log filter registration bean
*/
public FilterRegistrationBean<LogFilter> logFilter() {
FilterRegistrationBean<LogFilter> logFilter = new FilterRegistrationBean<>();
logFilter.setOrder(Ordered.HIGHEST_PRECEDENCE + 9);
logFilter.setFilter(new LogFilter());
logFilter.addUrlPatterns("/*");
return logFilter;
}
@Bean
public FilterRegistrationBean<ContentFilter> contentFilter(HaloProperties haloProperties,
OptionService optionService,
StringCacheStore cacheStore,
OneTimeTokenService oneTimeTokenService) {
ContentFilter contentFilter = new ContentFilter(haloProperties, optionService, cacheStore, oneTimeTokenService);
contentFilter.setFailureHandler(new ContentAuthenticationFailureHandler());
String adminPattern = HaloUtils.ensureBoth(haloProperties.getAdminPath(), "/") + "**";
contentFilter.addExcludeUrlPatterns(
adminPattern,
"/api/**",
"/install",
"/version",
"/js/**",
"/css/**");
FilterRegistrationBean<ContentFilter> contentFrb = new FilterRegistrationBean<>();
contentFrb.addUrlPatterns("/*");
contentFrb.setFilter(contentFilter);
contentFrb.setOrder(-1);
return contentFrb;
}
@Bean
public FilterRegistrationBean<ApiAuthenticationFilter> apiAuthenticationFilter(HaloProperties haloProperties,
ObjectMapper objectMapper,
OptionService optionService,
StringCacheStore cacheStore,
OneTimeTokenService oneTimeTokenService) {
ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(haloProperties, optionService, cacheStore, oneTimeTokenService);
apiFilter.addExcludeUrlPatterns(
"/api/content/*/comments",
"/api/content/**/comments/**",
"/api/content/options/comment"
);
DefaultAuthenticationFailureHandler failureHandler = new DefaultAuthenticationFailureHandler();
failureHandler.setProductionEnv(haloProperties.isProductionEnv());
failureHandler.setObjectMapper(objectMapper);
// Set failure handler
apiFilter.setFailureHandler(failureHandler);
FilterRegistrationBean<ApiAuthenticationFilter> authenticationFilter = new FilterRegistrationBean<>();
authenticationFilter.setFilter(apiFilter);
authenticationFilter.addUrlPatterns("/api/content/*");
authenticationFilter.setOrder(0);
return authenticationFilter;
}
@Bean
public FilterRegistrationBean<AdminAuthenticationFilter> adminAuthenticationFilter(StringCacheStore cacheStore,
UserService userService,
HaloProperties haloProperties,
ObjectMapper objectMapper,
OptionService optionService,
OneTimeTokenService oneTimeTokenService) {
AdminAuthenticationFilter adminAuthenticationFilter = new AdminAuthenticationFilter(cacheStore, userService,
haloProperties, optionService, oneTimeTokenService);
DefaultAuthenticationFailureHandler failureHandler = new DefaultAuthenticationFailureHandler();
failureHandler.setProductionEnv(haloProperties.isProductionEnv());
failureHandler.setObjectMapper(objectMapper);
// Config the admin filter
adminAuthenticationFilter.addExcludeUrlPatterns(
"/api/admin/login",
"/api/admin/refresh/*",
"/api/admin/installations",
"/api/admin/recoveries/migrations/*",
"/api/admin/migrations/*",
"/api/admin/is_installed",
"/api/admin/password/code",
"/api/admin/password/reset"
);
adminAuthenticationFilter.setFailureHandler(failureHandler);
FilterRegistrationBean<AdminAuthenticationFilter> authenticationFilter = new FilterRegistrationBean<>();
authenticationFilter.setFilter(adminAuthenticationFilter);
authenticationFilter.addUrlPatterns("/api/admin/*", "/api/content/comments");
authenticationFilter.setOrder(1);
return authenticationFilter;
}
}

View File

@ -1,7 +1,10 @@
package run.halo.app.filter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.filter.GenericFilterBean;
@ -21,6 +24,8 @@ import static run.halo.app.model.support.HaloConst.API_ACCESS_KEY_HEADER_NAME;
*
* @author johnniang
*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
public class CorsFilter extends GenericFilterBean {
private final static String ALLOW_HEADERS = StringUtils.joinWith(",", HttpHeaders.CONTENT_TYPE, ADMIN_TOKEN_HEADER_NAME, API_ACCESS_KEY_HEADER_NAME);

View File

@ -2,6 +2,9 @@ package run.halo.app.filter;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
@ -16,6 +19,8 @@ import java.io.IOException;
* @author johnniang
*/
@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 9)
public class LogFilter extends OncePerRequestFilter {
@Override

View File

@ -7,6 +7,7 @@ import org.springframework.lang.Nullable;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UrlPathHelper;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.exception.BadRequestException;
@ -26,10 +27,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import static run.halo.app.model.support.HaloConst.ONE_TIME_TOKEN_HEADER_NAME;
import static run.halo.app.model.support.HaloConst.ONE_TIME_TOKEN_QUERY_NAME;
@ -45,6 +43,8 @@ public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter
protected final AntPathMatcher antPathMatcher;
private final UrlPathHelper urlPathHelper = new UrlPathHelper();
protected final HaloProperties haloProperties;
protected final OptionService optionService;
@ -59,6 +59,9 @@ public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter
*/
private Set<String> excludeUrlPatterns = new HashSet<>(16);
private Set<String> urlPatterns = new LinkedHashSet<>();
AbstractAuthenticationFilter(HaloProperties haloProperties,
OptionService optionService,
StringCacheStore cacheStore,
@ -86,7 +89,11 @@ public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter
protected boolean shouldNotFilter(HttpServletRequest request) {
Assert.notNull(request, "Http servlet request must not be null");
return excludeUrlPatterns.stream().anyMatch(p -> antPathMatcher.match(p, request.getRequestURI()));
// check white list
boolean result = excludeUrlPatterns.stream().anyMatch(p -> antPathMatcher.match(p, urlPathHelper.getRequestUri(request)));
return result || urlPatterns.stream().noneMatch(p -> antPathMatcher.match(p, urlPathHelper.getRequestUri(request)));
}
/**
@ -121,6 +128,20 @@ public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter
this.excludeUrlPatterns = new HashSet<>(excludeUrlPatterns);
}
public void setUrlPatterns(Collection<String> urlPatterns) {
Assert.notNull(urlPatterns, "UrlPatterns must not be null");
this.urlPatterns = new LinkedHashSet<>(urlPatterns);
}
public Collection<String> getUrlPatterns() {
return this.urlPatterns;
}
public void addUrlPatterns(String... urlPatterns) {
Assert.notNull(urlPatterns, "UrlPatterns must not be null");
Collections.addAll(this.urlPatterns, urlPatterns);
}
/**
* Gets authentication failure handler. (Default: @DefaultAuthenticationFailureHandler)
*

View File

@ -1,8 +1,11 @@
package run.halo.app.security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.Order;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.exception.AuthenticationException;
@ -10,6 +13,7 @@ import run.halo.app.model.entity.User;
import run.halo.app.security.authentication.AuthenticationImpl;
import run.halo.app.security.context.SecurityContextHolder;
import run.halo.app.security.context.SecurityContextImpl;
import run.halo.app.security.handler.DefaultAuthenticationFailureHandler;
import run.halo.app.security.service.OneTimeTokenService;
import run.halo.app.security.support.UserDetail;
import run.halo.app.security.util.SecurityUtils;
@ -32,6 +36,8 @@ import static run.halo.app.model.support.HaloConst.ADMIN_TOKEN_QUERY_NAME;
* @author johnniang
*/
@Slf4j
@Component
@Order(1)
public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
private final HaloProperties haloProperties;
@ -42,10 +48,32 @@ public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
UserService userService,
HaloProperties haloProperties,
OptionService optionService,
OneTimeTokenService oneTimeTokenService) {
OneTimeTokenService oneTimeTokenService,
ObjectMapper objectMapper) {
super(haloProperties, optionService, cacheStore, oneTimeTokenService);
this.userService = userService;
this.haloProperties = haloProperties;
addUrlPatterns("/api/admin/**", "/api/content/comments");
addExcludeUrlPatterns(
"/api/admin/login",
"/api/admin/refresh/*",
"/api/admin/installations",
"/api/admin/recoveries/migrations/**",
"/api/admin/migrations/**",
"/api/admin/is_installed",
"/api/admin/password/code",
"/api/admin/password/reset"
);
// set failure handler
DefaultAuthenticationFailureHandler failureHandler = new DefaultAuthenticationFailureHandler();
failureHandler.setProductionEnv(haloProperties.isProductionEnv());
failureHandler.setObjectMapper(objectMapper);
setFailureHandler(failureHandler);
}
@Override

View File

@ -1,14 +1,18 @@
package run.halo.app.security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.Order;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.exception.AuthenticationException;
import run.halo.app.exception.ForbiddenException;
import run.halo.app.model.properties.ApiProperties;
import run.halo.app.model.properties.CommentProperties;
import run.halo.app.security.handler.DefaultAuthenticationFailureHandler;
import run.halo.app.security.service.OneTimeTokenService;
import run.halo.app.service.OptionService;
@ -28,6 +32,8 @@ import static run.halo.app.model.support.HaloConst.API_ACCESS_KEY_QUERY_NAME;
* @author johnniang
*/
@Slf4j
@Component
@Order(0)
public class ApiAuthenticationFilter extends AbstractAuthenticationFilter {
private final OptionService optionService;
@ -35,14 +41,28 @@ public class ApiAuthenticationFilter extends AbstractAuthenticationFilter {
public ApiAuthenticationFilter(HaloProperties haloProperties,
OptionService optionService,
StringCacheStore cacheStore,
OneTimeTokenService oneTimeTokenService) {
OneTimeTokenService oneTimeTokenService,
ObjectMapper objectMapper) {
super(haloProperties, optionService, cacheStore, oneTimeTokenService);
this.optionService = optionService;
addUrlPatterns("/api/content/**");
addExcludeUrlPatterns(
"/api/content/**/comments",
"/api/content/**/comments/**",
"/api/content/options/comment"
);
// set failure handler
DefaultAuthenticationFailureHandler failureHandler = new DefaultAuthenticationFailureHandler();
failureHandler.setProductionEnv(haloProperties.isProductionEnv());
failureHandler.setObjectMapper(objectMapper);
setFailureHandler(failureHandler);
}
@Override
protected void doAuthenticate(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (!haloProperties.isAuthEnabled()) {
filterChain.doFilter(request, response);
return;

View File

@ -1,9 +1,13 @@
package run.halo.app.security.filter;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.security.handler.ContentAuthenticationFailureHandler;
import run.halo.app.security.service.OneTimeTokenService;
import run.halo.app.service.OptionService;
import run.halo.app.utils.HaloUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
@ -17,6 +21,8 @@ import java.io.IOException;
* @author johnniang
* @date 19-5-6
*/
@Component
@Order(-1)
public class ContentFilter extends AbstractAuthenticationFilter {
public ContentFilter(HaloProperties haloProperties,
@ -24,6 +30,18 @@ public class ContentFilter extends AbstractAuthenticationFilter {
StringCacheStore cacheStore,
OneTimeTokenService oneTimeTokenService) {
super(haloProperties, optionService, cacheStore, oneTimeTokenService);
String adminPattern = HaloUtils.ensureBoth(haloProperties.getAdminPath(), "/") + "**";
addExcludeUrlPatterns(
adminPattern,
"/api/**",
"/install",
"/version",
"/js/**",
"/css/**");
// set failure handler
setFailureHandler(new ContentAuthenticationFailureHandler());
}
@Override