From f7d16e06a37ba8e1df0438116e53bba46663eb5c Mon Sep 17 00:00:00 2001 From: johnniang Date: Mon, 6 May 2019 17:01:59 +0800 Subject: [PATCH] Complete api authentication --- .../halo/app/config/HaloConfiguration.java | 6 +- .../halo/app/config/SwaggerConfiguration.java | 7 +- .../run/halo/app/model/support/HaloConst.java | 5 -- .../filter/AbstractAuthenticationFilter.java | 10 +++ .../filter/AdminAuthenticationFilter.java | 10 +-- .../filter/ApiAuthenticationFilter.java | 78 +++++++++++++++++-- 6 files changed, 90 insertions(+), 26 deletions(-) diff --git a/src/main/java/run/halo/app/config/HaloConfiguration.java b/src/main/java/run/halo/app/config/HaloConfiguration.java index da9d4d226..db2a272cd 100644 --- a/src/main/java/run/halo/app/config/HaloConfiguration.java +++ b/src/main/java/run/halo/app/config/HaloConfiguration.java @@ -102,8 +102,10 @@ public class HaloConfiguration { } @Bean - public FilterRegistrationBean apiAuthenticationFilter(HaloProperties haloProperties, ObjectMapper objectMapper) { - ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(haloProperties); + public FilterRegistrationBean apiAuthenticationFilter(HaloProperties haloProperties, + ObjectMapper objectMapper, + OptionService optionService) { + ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(haloProperties, optionService); DefaultAuthenticationFailureHandler failureHandler = new DefaultAuthenticationFailureHandler(); failureHandler.setProductionEnv(haloProperties.isProductionEnv()); diff --git a/src/main/java/run/halo/app/config/SwaggerConfiguration.java b/src/main/java/run/halo/app/config/SwaggerConfiguration.java index b1dcb8af0..0cd172f20 100644 --- a/src/main/java/run/halo/app/config/SwaggerConfiguration.java +++ b/src/main/java/run/halo/app/config/SwaggerConfiguration.java @@ -8,13 +8,13 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.http.HttpHeaders; import org.springframework.lang.NonNull; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestMethod; import run.halo.app.config.properties.HaloProperties; import run.halo.app.model.entity.User; import run.halo.app.security.filter.AdminAuthenticationFilter; +import run.halo.app.security.filter.ApiAuthenticationFilter; import run.halo.app.security.support.UserDetail; import springfox.documentation.builders.*; import springfox.documentation.schema.AlternateTypeRule; @@ -34,7 +34,6 @@ import java.util.Collections; import java.util.List; import static run.halo.app.model.support.HaloConst.HALO_VERSION; -import static run.halo.app.model.support.HaloConst.TOKEN_HEADER; import static springfox.documentation.schema.AlternateTypeRules.newRule; /** @@ -140,8 +139,8 @@ public class SwaggerConfiguration { private List portalApiKeys() { return Arrays.asList( - new ApiKey("Token from header", HttpHeaders.AUTHORIZATION, In.HEADER.name()), - new ApiKey("Token from query", "token", In.QUERY.name()) + new ApiKey("Token from header", ApiAuthenticationFilter.API_TOKEN_HEADER_NAME, In.HEADER.name()), + new ApiKey("Token from query", ApiAuthenticationFilter.API_TOKEN_QUERY_NAME, In.QUERY.name()) ); } diff --git a/src/main/java/run/halo/app/model/support/HaloConst.java b/src/main/java/run/halo/app/model/support/HaloConst.java index 27a200d67..3890bc070 100644 --- a/src/main/java/run/halo/app/model/support/HaloConst.java +++ b/src/main/java/run/halo/app/model/support/HaloConst.java @@ -25,11 +25,6 @@ public class HaloConst { */ public static final String HALO_VERSION = "1.0.0"; - /** - * Token of header param - */ - public static final String TOKEN_HEADER = "token"; - /** * Suffix of freemarker template file */ 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 32b9bee78..20171ea5f 100644 --- a/src/main/java/run/halo/app/security/filter/AbstractAuthenticationFilter.java +++ b/src/main/java/run/halo/app/security/filter/AbstractAuthenticationFilter.java @@ -1,6 +1,7 @@ package run.halo.app.security.filter; import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; import org.springframework.web.filter.OncePerRequestFilter; @@ -41,6 +42,15 @@ public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter antPathMatcher = new AntPathMatcher(); } + /** + * Gets token from request. + * + * @param request http servlet request must not be null + * @return token or null + */ + @Nullable + protected abstract String getTokenFromRequest(@NonNull HttpServletRequest request); + @Override protected boolean shouldNotFilter(HttpServletRequest request) { Assert.notNull(request, "Http servlet request must not be null"); 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 27c150f68..026e244a2 100644 --- a/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java +++ b/src/main/java/run/halo/app/security/filter/AdminAuthenticationFilter.java @@ -137,14 +137,8 @@ public class AdminAuthenticationFilter extends AbstractAuthenticationFilter { getFailureHandler().onFailure(request, response, new AuthenticationException("You have to login before accessing admin api")); } - /** - * Gets token from request. - * - * @param request http servlet request must not be null - * @return token or null - */ - @Nullable - private String getTokenFromRequest(@NonNull HttpServletRequest request) { + @Override + protected String getTokenFromRequest(@NonNull HttpServletRequest request) { Assert.notNull(request, "Http servlet request must not be null"); // Get from header 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 16f97847c..4e1fd1a55 100644 --- a/src/main/java/run/halo/app/security/filter/ApiAuthenticationFilter.java +++ b/src/main/java/run/halo/app/security/filter/ApiAuthenticationFilter.java @@ -1,33 +1,97 @@ package run.halo.app.security.filter; -import org.springframework.util.AntPathMatcher; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.lang.NonNull; +import org.springframework.util.Assert; 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.OtherProperties; +import run.halo.app.service.OptionService; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.HashSet; -import java.util.Set; +import java.util.Optional; /** * Api authentication Filter * * @author johnniang */ +@Slf4j public class ApiAuthenticationFilter extends AbstractAuthenticationFilter { - private final AntPathMatcher antPathMatcher; + public final static String API_TOKEN_HEADER_NAME = "API-" + HttpHeaders.AUTHORIZATION; - public ApiAuthenticationFilter(HaloProperties haloProperties) { + public final static String API_TOKEN_QUERY_NAME = "apiToken"; + + private final OptionService optionService; + + public ApiAuthenticationFilter(HaloProperties haloProperties, + OptionService optionService) { super(haloProperties); - antPathMatcher = new AntPathMatcher(); + this.optionService = optionService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - // TODO Handle authentication + // Get token + String token = getTokenFromRequest(request); + + if (StringUtils.isBlank(token)) { + // If the token is missing + getFailureHandler().onFailure(request, response, new AuthenticationException("Missing API token")); + return; + } + + // Get api_enable from option + Boolean apiEnabled = optionService.getByPropertyOrDefault(OtherProperties.API_ENABLED, Boolean.class, false); + + if (!apiEnabled) { + getFailureHandler().onFailure(request, response, new ForbiddenException("API has been disabled by blogger currently")); + return; + } + + // Get token from option + Optional optionalToken = optionService.getByProperty(OtherProperties.API_TOKEN, String.class); + + if (!optionalToken.isPresent()) { + // If the token is not set + getFailureHandler().onFailure(request, response, new AuthenticationException("API Token hasn't been set by blogger")); + return; + } + + if (!StringUtils.equals(token, optionalToken.get())) { + // If the token is mismatch + getFailureHandler().onFailure(request, response, new AuthenticationException("Token is mismatch")); + return; + } + + // Do filter filterChain.doFilter(request, response); } + + @Override + protected String getTokenFromRequest(@NonNull HttpServletRequest request) { + Assert.notNull(request, "Http servlet request must not be null"); + + // Get from header + String token = request.getHeader(API_TOKEN_HEADER_NAME); + + // Get from param + if (StringUtils.isBlank(token)) { + token = request.getParameter(API_TOKEN_QUERY_NAME); + + log.debug("Got token from parameter: [{}: {}]", API_TOKEN_QUERY_NAME, token); + } else { + log.debug("Got token from header: [{}: {}]", API_TOKEN_HEADER_NAME, token); + } + + return token; + } }