diff --git a/src/main/java/run/halo/app/config/HaloConfiguration.java b/src/main/java/run/halo/app/config/HaloConfiguration.java index 0b8d63d03..390de3685 100644 --- a/src/main/java/run/halo/app/config/HaloConfiguration.java +++ b/src/main/java/run/halo/app/config/HaloConfiguration.java @@ -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() { - FilterRegistrationBean 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() { - FilterRegistrationBean logFilter = new FilterRegistrationBean<>(); - - logFilter.setOrder(Ordered.HIGHEST_PRECEDENCE + 9); - logFilter.setFilter(new LogFilter()); - logFilter.addUrlPatterns("/*"); - - return logFilter; - } - - @Bean - public FilterRegistrationBean 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 contentFrb = new FilterRegistrationBean<>(); - contentFrb.addUrlPatterns("/*"); - contentFrb.setFilter(contentFilter); - contentFrb.setOrder(-1); - - return contentFrb; - } - - @Bean - public FilterRegistrationBean 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 authenticationFilter = new FilterRegistrationBean<>(); - authenticationFilter.setFilter(apiFilter); - authenticationFilter.addUrlPatterns("/api/content/*"); - authenticationFilter.setOrder(0); - - return authenticationFilter; - } - - @Bean - public FilterRegistrationBean 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 authenticationFilter = new FilterRegistrationBean<>(); - authenticationFilter.setFilter(adminAuthenticationFilter); - authenticationFilter.addUrlPatterns("/api/admin/*", "/api/content/comments"); - authenticationFilter.setOrder(1); - - return authenticationFilter; - } } diff --git a/src/main/java/run/halo/app/filter/CorsFilter.java b/src/main/java/run/halo/app/filter/CorsFilter.java index 77279f2fd..978884f55 100644 --- a/src/main/java/run/halo/app/filter/CorsFilter.java +++ b/src/main/java/run/halo/app/filter/CorsFilter.java @@ -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); diff --git a/src/main/java/run/halo/app/filter/LogFilter.java b/src/main/java/run/halo/app/filter/LogFilter.java index bc68c0b97..4cd7b31fd 100644 --- a/src/main/java/run/halo/app/filter/LogFilter.java +++ b/src/main/java/run/halo/app/filter/LogFilter.java @@ -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 diff --git a/src/main/java/run/halo/app/security/filter/AbstractAuthenticationFilter.java b/src/main/java/run/halo/app/security/filter/AbstractAuthenticationFilter.java index 4e38b42e1..7749ab6e2 100644 --- a/src/main/java/run/halo/app/security/filter/AbstractAuthenticationFilter.java +++ b/src/main/java/run/halo/app/security/filter/AbstractAuthenticationFilter.java @@ -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 excludeUrlPatterns = new HashSet<>(16); + private Set 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 urlPatterns) { + Assert.notNull(urlPatterns, "UrlPatterns must not be null"); + this.urlPatterns = new LinkedHashSet<>(urlPatterns); + } + + public Collection 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) * diff --git a/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java b/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java index 16e7c7994..6b67ecf66 100644 --- a/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java +++ b/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java @@ -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 diff --git a/src/main/java/run/halo/app/security/filter/ApiAuthenticationFilter.java b/src/main/java/run/halo/app/security/filter/ApiAuthenticationFilter.java index e934441d8..24ae01ca3 100644 --- a/src/main/java/run/halo/app/security/filter/ApiAuthenticationFilter.java +++ b/src/main/java/run/halo/app/security/filter/ApiAuthenticationFilter.java @@ -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; diff --git a/src/main/java/run/halo/app/security/filter/ContentFilter.java b/src/main/java/run/halo/app/security/filter/ContentFilter.java index 69ad2aef9..b4039e6e8 100644 --- a/src/main/java/run/halo/app/security/filter/ContentFilter.java +++ b/src/main/java/run/halo/app/security/filter/ContentFilter.java @@ -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