Refactor authentication filter

pull/146/head
johnniang 2019-04-17 00:41:40 +08:00
parent 8517f98ee4
commit 54b4f9da68
4 changed files with 174 additions and 157 deletions

View File

@ -1,16 +1,5 @@
package run.halo.app.config; package run.halo.app.config;
import run.halo.app.cache.InMemoryCacheStore;
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.handler.AdminAuthenticationFailureHandler;
import run.halo.app.security.handler.DefaultAuthenticationFailureHandler;
import run.halo.app.service.UserService;
import run.halo.app.utils.HttpClientUtils;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -23,12 +12,16 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import run.halo.app.cache.InMemoryCacheStore;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.filter.CorsFilter; import run.halo.app.filter.CorsFilter;
import run.halo.app.filter.LogFilter; import run.halo.app.filter.LogFilter;
import run.halo.app.security.filter.AdminAuthenticationFilter; import run.halo.app.security.filter.AdminAuthenticationFilter;
import run.halo.app.security.filter.ApiAuthenticationFilter; import run.halo.app.security.filter.ApiAuthenticationFilter;
import run.halo.app.security.handler.AdminAuthenticationFailureHandler; import run.halo.app.security.handler.AdminAuthenticationFailureHandler;
import run.halo.app.security.handler.DefaultAuthenticationFailureHandler; import run.halo.app.security.handler.DefaultAuthenticationFailureHandler;
import run.halo.app.service.UserService;
import run.halo.app.utils.HttpClientUtils; import run.halo.app.utils.HttpClientUtils;
import java.security.KeyManagementException; import java.security.KeyManagementException;
@ -99,7 +92,7 @@ public class HaloConfiguration {
@Bean @Bean
public FilterRegistrationBean<ApiAuthenticationFilter> apiAuthenticationFilter(HaloProperties haloProperties, ObjectMapper objectMapper) { public FilterRegistrationBean<ApiAuthenticationFilter> apiAuthenticationFilter(HaloProperties haloProperties, ObjectMapper objectMapper) {
ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(); ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(haloProperties);
DefaultAuthenticationFailureHandler failureHandler = new DefaultAuthenticationFailureHandler(); DefaultAuthenticationFailureHandler failureHandler = new DefaultAuthenticationFailureHandler();
failureHandler.setProductionEnv(haloProperties.getProductionEnv()); failureHandler.setProductionEnv(haloProperties.getProductionEnv());
@ -107,6 +100,7 @@ public class HaloConfiguration {
// Set failure handler // Set failure handler
apiFilter.setFailureHandler(failureHandler); apiFilter.setFailureHandler(failureHandler);
apiFilter.addExcludeUrlPatterns("/api/admin/*");
FilterRegistrationBean<ApiAuthenticationFilter> authenticationFilter = new FilterRegistrationBean<>(); FilterRegistrationBean<ApiAuthenticationFilter> authenticationFilter = new FilterRegistrationBean<>();
authenticationFilter.setFilter(apiFilter); authenticationFilter.setFilter(apiFilter);

View File

@ -0,0 +1,147 @@
package run.halo.app.security.filter;
import org.springframework.lang.NonNull;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
import run.halo.app.config.properties.HaloProperties;
import run.halo.app.security.handler.AuthenticationFailureHandler;
import run.halo.app.security.handler.DefaultAuthenticationFailureHandler;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* Abstract authentication filter.
*
* @author johnniang
* @date 19-4-16
*/
public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter {
private AuthenticationFailureHandler failureHandler;
/**
* Exclude url patterns.
*/
private Set<String> excludeUrlPatterns = new HashSet<>(2);
/**
* Try authenticating url, method patterns.
*/
private Map<String, String> tryAuthUrlMethodPatterns = new HashMap<>(2);
private final AntPathMatcher antPathMatcher;
private final HaloProperties haloProperties;
protected AbstractAuthenticationFilter(HaloProperties haloProperties) {
this.haloProperties = haloProperties;
antPathMatcher = new AntPathMatcher();
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
Assert.notNull(request, "Http servlet request must not be null");
return excludeUrlPatterns.stream().anyMatch(p -> antPathMatcher.match(p, request.getServletPath()));
}
/**
* Should skip authentication failure.
*
* @param request http servlet request must not be null.
* @return true if the request should skip authentication failure; false otherwise
*/
protected boolean shouldSkipAuthenticateFailure(@NonNull HttpServletRequest request) {
Assert.notNull(request, "Http servlet request must not be null");
for (String url : tryAuthUrlMethodPatterns.keySet()) {
if (antPathMatcher.match(url, request.getServletPath())
&& tryAuthUrlMethodPatterns.get(url).equalsIgnoreCase(request.getMethod())) {
return true;
}
}
return false;
}
/**
* Sets exclude url patterns.
*
* @param excludeUrlPatterns exclude urls
*/
public void setExcludeUrlPatterns(@NonNull Collection<String> excludeUrlPatterns) {
Assert.notNull(excludeUrlPatterns, "Exclude url patterns must not be null");
this.excludeUrlPatterns = new HashSet<>(excludeUrlPatterns);
}
/**
* Adds exclude url patterns.
*
* @param excludeUrlPatterns exclude urls
*/
public void addExcludeUrlPatterns(@NonNull String... excludeUrlPatterns) {
Assert.notNull(excludeUrlPatterns, "Exclude url patterns must not be null");
Collections.addAll(this.excludeUrlPatterns, excludeUrlPatterns);
}
/**
* Gets exclude url patterns.
*
* @return exclude url patterns.
*/
@NonNull
public Set<String> getExcludeUrlPatterns() {
return excludeUrlPatterns;
}
/**
* Adds try authenticating url method pattern.
*
* @param url url must not be blank
* @param method method must not be blank
*/
public void addTryAuthUrlMethodPattern(@NonNull String url, @NonNull String method) {
Assert.hasText(url, "Try authenticating url must not be blank");
Assert.hasText(method, "Try authenticating method must not be blank");
tryAuthUrlMethodPatterns.put(url, method);
}
/**
* Gets authentication failure handler. (Default: @DefaultAuthenticationFailureHandler)
*
* @return authentication failure handler
*/
@NonNull
public AuthenticationFailureHandler getFailureHandler() {
if (failureHandler == null) {
synchronized (this) {
if (failureHandler == null) {
// Create default authentication failure handler
DefaultAuthenticationFailureHandler failureHandler = new DefaultAuthenticationFailureHandler();
failureHandler.setProductionEnv(haloProperties.getProductionEnv());
this.failureHandler = failureHandler;
}
}
}
return failureHandler;
}
/**
* Sets authentication failure handler.
*
* @param failureHandler authentication failure handler
*/
public void setFailureHandler(@NonNull AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "Authentication failure handler must not be null");
this.failureHandler = failureHandler;
}
}

View File

@ -1,5 +1,11 @@
package run.halo.app.security.filter; package run.halo.app.security.filter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import run.halo.app.cache.StringCacheStore; import run.halo.app.cache.StringCacheStore;
import run.halo.app.config.properties.HaloProperties; import run.halo.app.config.properties.HaloProperties;
import run.halo.app.exception.AuthenticationException; import run.halo.app.exception.AuthenticationException;
@ -7,35 +13,17 @@ import run.halo.app.model.entity.User;
import run.halo.app.security.authentication.AuthenticationImpl; import run.halo.app.security.authentication.AuthenticationImpl;
import run.halo.app.security.context.SecurityContextHolder; import run.halo.app.security.context.SecurityContextHolder;
import run.halo.app.security.context.SecurityContextImpl; import run.halo.app.security.context.SecurityContextImpl;
import run.halo.app.security.handler.AuthenticationFailureHandler;
import run.halo.app.security.handler.DefaultAuthenticationFailureHandler;
import run.halo.app.security.support.UserDetail; import run.halo.app.security.support.UserDetail;
import run.halo.app.service.UserService; import run.halo.app.service.UserService;
import run.halo.app.utils.JsonUtils; import run.halo.app.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
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;
import run.halo.app.exception.AuthenticationException;
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.AuthenticationFailureHandler;
import run.halo.app.security.handler.DefaultAuthenticationFailureHandler;
import run.halo.app.security.support.UserDetail;
import run.halo.app.utils.JsonUtils;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.List;
import java.util.Optional;
/** /**
* Admin authentication filter. * Admin authentication filter.
@ -43,7 +31,7 @@ import java.util.*;
* @author johnniang * @author johnniang
*/ */
@Slf4j @Slf4j
public class AdminAuthenticationFilter extends OncePerRequestFilter { public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
/** /**
* Admin session key. * Admin session key.
@ -60,33 +48,19 @@ public class AdminAuthenticationFilter extends OncePerRequestFilter {
*/ */
public final static String ADMIN_TOKEN_PARAM_NAME = "adminToken"; public final static String ADMIN_TOKEN_PARAM_NAME = "adminToken";
private AuthenticationFailureHandler failureHandler;
private final HaloProperties haloProperties; private final HaloProperties haloProperties;
private final StringCacheStore cacheStore; private final StringCacheStore cacheStore;
private final UserService userService; private final UserService userService;
private final AntPathMatcher antPathMatcher;
/**
* Exclude url patterns.
*/
private Set<String> excludeUrlPatterns = new HashSet<>(2);
/**
* Try authenticating url, method patterns.
*/
private Map<String, String> tryAuthUrlMethodPatterns = new HashMap<>(2);
public AdminAuthenticationFilter(StringCacheStore cacheStore, public AdminAuthenticationFilter(StringCacheStore cacheStore,
UserService userService, UserService userService,
HaloProperties haloProperties) { HaloProperties haloProperties) {
super(haloProperties);
this.cacheStore = cacheStore; this.cacheStore = cacheStore;
this.userService = userService; this.userService = userService;
this.haloProperties = haloProperties; this.haloProperties = haloProperties;
antPathMatcher = new AntPathMatcher();
} }
@Override @Override
@ -151,107 +125,6 @@ public class AdminAuthenticationFilter extends OncePerRequestFilter {
getFailureHandler().onFailure(request, response, new AuthenticationException("You have to login before accessing admin api")); getFailureHandler().onFailure(request, response, new AuthenticationException("You have to login before accessing admin api"));
} }
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
Assert.notNull(request, "Http servlet request must not be null");
return excludeUrlPatterns.stream().anyMatch(p -> antPathMatcher.match(p, request.getServletPath()));
}
/**
* Should skip authentication failure.
*
* @param request http servlet request must not be null.
* @return true if the request should skip authentication failure; false otherwise
*/
protected boolean shouldSkipAuthenticateFailure(@NonNull HttpServletRequest request) {
Assert.notNull(request, "Http servlet request must not be null");
for (String url : tryAuthUrlMethodPatterns.keySet()) {
if (antPathMatcher.match(url, request.getServletPath())
&& tryAuthUrlMethodPatterns.get(url).equalsIgnoreCase(request.getMethod())) {
return true;
}
}
return false;
}
/**
* Gets authentication failure handler. (Default: @DefaultAuthenticationFailureHandler)
*
* @return authentication failure handler
*/
public AuthenticationFailureHandler getFailureHandler() {
if (failureHandler == null) {
synchronized (this) {
if (failureHandler == null) {
// Create default authentication failure handler
DefaultAuthenticationFailureHandler failureHandler = new DefaultAuthenticationFailureHandler();
failureHandler.setProductionEnv(haloProperties.getProductionEnv());
this.failureHandler = failureHandler;
}
}
}
return failureHandler;
}
/**
* Sets authentication failure handler.
*
* @param failureHandler authentication failure handler
*/
public void setFailureHandler(@NonNull AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "Authentication failure handler must not be null");
this.failureHandler = failureHandler;
}
/**
* Sets exclude url patterns.
*
* @param excludeUrlPatterns exclude urls
*/
public void setExcludeUrlPatterns(@NonNull Collection<String> excludeUrlPatterns) {
Assert.notNull(excludeUrlPatterns, "Exclude url patterns must not be null");
this.excludeUrlPatterns = new HashSet<>(excludeUrlPatterns);
}
/**
* Adds exclude url patterns.
*
* @param excludeUrlPatterns exclude urls
*/
public void addExcludeUrlPatterns(@NonNull String... excludeUrlPatterns) {
Assert.notNull(excludeUrlPatterns, "Exclude url patterns must not be null");
Collections.addAll(this.excludeUrlPatterns, excludeUrlPatterns);
}
/**
* Gets exclude url patterns.
*
* @return exclude url patterns.
*/
public Set<String> getExcludeUrlPatterns() {
return excludeUrlPatterns;
}
/**
* Adds try authenticating url method pattern.
*
* @param url url must not be blank
* @param method method must not be blank
*/
public void addTryAuthUrlMethodPattern(@NonNull String url, @NonNull String method) {
Assert.hasText(url, "Try authenticating url must not be blank");
Assert.hasText(method, "Try authenticating method must not be blank");
tryAuthUrlMethodPatterns.put(url, method);
}
/** /**
* Gets token from request. * Gets token from request.
* *

View File

@ -1,30 +1,33 @@
package run.halo.app.security.filter; package run.halo.app.security.filter;
import run.halo.app.security.handler.AuthenticationFailureHandler; import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter; import run.halo.app.config.properties.HaloProperties;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/** /**
* Api authentication Filter * Api authentication Filter
* *
* @author johnniang * @author johnniang
*/ */
public class ApiAuthenticationFilter extends OncePerRequestFilter { public class ApiAuthenticationFilter extends AbstractAuthenticationFilter {
private AuthenticationFailureHandler failureHandler; private final AntPathMatcher antPathMatcher;
public ApiAuthenticationFilter(HaloProperties haloProperties) {
super(haloProperties);
antPathMatcher = new AntPathMatcher();
}
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// TODO Handle authentication // TODO Handle authentication
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} }
public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
this.failureHandler = failureHandler;
}
} }