halo的安全模块
一、相关的Filter
AbstractAuthenticationFilter
ContentFilter
ApiAuthenticationFilter
AdminAuthenticationFilter
二、类之间的关系
halo定义了一个AbstractAuthenticationFilter这个类继承了spring框架给我们提供好的OncePerRequestFilter
OncePerRequestFilter是一个过滤器基类,旨在确保在任何servlet容器上按请求分派并执行一次。
AbstractAuthenticationFilter的三个子类具体定义了过滤的规则
三、相关Filter的功能
1.拦截所有的请求
@Component
@Order(-1)
public class ContentFilter extends AbstractAuthenticationFilter {
public ContentFilter(HaloProperties haloProperties,
OptionService optionService,
AbstractStringCacheStore cacheStore,
OneTimeTokenService oneTimeTokenService) {
super(haloProperties, optionService, cacheStore, oneTimeTokenService);
//将所有的路径交给过滤器来管理
addUrlPatterns("/**");
String adminPattern = HaloUtils.ensureBoth(haloProperties.getAdminPath(), "/") + "**";
//需要直接放行的路径
addExcludeUrlPatterns(
adminPattern,
"/api/**",
"/install",
"/version",
"/js/**",
"/css/**");
// set failure handler
setFailureHandler(new ContentAuthenticationFailureHandler());
}
@Override
protected String getTokenFromRequest(HttpServletRequest request) {
return null;
}
@Override
protected void doAuthenticate(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// Do nothing
filterChain.doFilter(request, response);
}
}
2./api/content/**路径的过滤
@Slf4j
@Component
@Order(0)
public class ApiAuthenticationFilter extends AbstractAuthenticationFilter {
private final OptionService optionService;
public ApiAuthenticationFilter(HaloProperties haloProperties,
OptionService optionService,
AbstractStringCacheStore cacheStore,
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());
//在haloConfiguration中注入的ObjectMapper
failureHandler.setObjectMapper(objectMapper);
setFailureHandler(failureHandler);
}
@Override
protected void doAuthenticate(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//没有开启身份验证模式
if (!haloProperties.isAuthEnabled()) {
//放行所有api的请求
filterChain.doFilter(request, response);
return;
}
// Get api_enable from option
Boolean apiEnabled = optionService.getByPropertyOrDefault(ApiProperties.API_ENABLED, Boolean.class, false);
if (!apiEnabled) {
throw new ForbiddenException("API has been disabled by blogger currently");
}
// Get access key
//从request中获取api_access_key
String accessKey = getTokenFromRequest(request);
if (StringUtils.isBlank(accessKey)) {
// If the access key is missing
throw new AuthenticationException("Missing API access key");
}
// Get access key from option
//从optionservice中获取api_access_key
Optional<String> optionalAccessKey = optionService.getByProperty(ApiProperties.API_ACCESS_KEY, String.class);
if (!optionalAccessKey.isPresent()) {
// If the access key is not set
throw new AuthenticationException("API access key hasn't been set by blogger");
}
//不相同就抛出异常
if (!StringUtils.equals(accessKey, optionalAccessKey.get())) {
// If the access key is mismatch
throw new AuthenticationException("API access key is mismatch").setErrorData(accessKey);
}
//相同就放行
// Do filter
filterChain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
boolean result = super.shouldNotFilter(request);
if (antPathMatcher.match("/api/content/*/comments", request.getServletPath())) {
Boolean commentApiEnabled = optionService.getByPropertyOrDefault(CommentProperties.API_ENABLED, Boolean.class, true);
if (!commentApiEnabled) {
// If the comment api is disabled
result = false;
}
}
return result;
}
@Override
protected String getTokenFromRequest(@NonNull HttpServletRequest request) {
return getTokenFromRequest(request, API_ACCESS_KEY_QUERY_NAME, API_ACCESS_KEY_HEADER_NAME);
}
}
3."/api/admin/**", "/api/content/comments"所有请求过滤
@Slf4j
@Component
@Order(1)
public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
private final HaloProperties haloProperties;
private final UserService userService;
public AdminAuthenticationFilter(AbstractStringCacheStore cacheStore,
UserService userService,
HaloProperties haloProperties,
OptionService optionService,
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/migrations/halo",
"/api/admin/is_installed",
"/api/admin/password/code",
"/api/admin/password/reset",
"/api/admin/login/precheck"
);
// 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()) {
// Set security
userService.getCurrentUser().ifPresent(user ->
SecurityContextHolder.setContext(new SecurityContextImpl(new AuthenticationImpl(new UserDetail(user)))));
// Do filter
filterChain.doFilter(request, response);
return;
}
// Get token from request
String token = getTokenFromRequest(request);
if (StringUtils.isBlank(token)) {
throw new AuthenticationException("未登录,请登录后访问");
}
// Get user id from cache
Optional<Integer> optionalUserId = cacheStore.getAny(SecurityUtils.buildTokenAccessKey(token), Integer.class);
if (!optionalUserId.isPresent()) {
throw new AuthenticationException("Token 已过期或不存在").setErrorData(token);
}
// Get the user
User user = userService.getById(optionalUserId.get());
// Build user detail
UserDetail userDetail = new UserDetail(user);
// Set security
SecurityContextHolder.setContext(new SecurityContextImpl(new AuthenticationImpl(userDetail)));
// Do filter
filterChain.doFilter(request, response);
}
@Override
protected String getTokenFromRequest(@NonNull HttpServletRequest request) {
//admin_token / ADMIN-Authorization
return getTokenFromRequest(request, ADMIN_TOKEN_QUERY_NAME, ADMIN_TOKEN_HEADER_NAME);
}
}