diff --git a/src/main/java/cc/ryanc/halo/config/HaloConfiguration.java b/src/main/java/cc/ryanc/halo/config/HaloConfiguration.java index db43c5504..541c88331 100644 --- a/src/main/java/cc/ryanc/halo/config/HaloConfiguration.java +++ b/src/main/java/cc/ryanc/halo/config/HaloConfiguration.java @@ -3,6 +3,11 @@ package cc.ryanc.halo.config; import cc.ryanc.halo.config.properties.HaloProperties; import cc.ryanc.halo.filter.CorsFilter; import cc.ryanc.halo.filter.LogFilter; +import cc.ryanc.halo.security.filter.AdminAuthenticationFilter; +import cc.ryanc.halo.security.filter.ApiAuthenticationFilter; +import cc.ryanc.halo.security.handler.AdminAuthenticationFailureHandler; +import cc.ryanc.halo.security.handler.DefaultAuthenticationFailureHandler; +import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; @@ -49,4 +54,30 @@ public class HaloConfiguration { return logFilter; } + + @Bean + public FilterRegistrationBean apiAuthenticationFilter(HaloProperties haloProperties, ObjectMapper objectMapper) { + ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(); + // Set failure handler + apiFilter.setFailureHandler(new DefaultAuthenticationFailureHandler(haloProperties.getProductionEnv(), objectMapper)); + + FilterRegistrationBean authenticationFilter = new FilterRegistrationBean<>(); + authenticationFilter.setFilter(apiFilter); + authenticationFilter.addUrlPatterns("/api/*"); + authenticationFilter.setOrder(0); + return authenticationFilter; + } + + @Bean + public FilterRegistrationBean adminAuthenticationFilter(HaloProperties haloProperties, ObjectMapper objectMapper) { + AdminAuthenticationFilter adminFilter = new AdminAuthenticationFilter(); + // Set failure handler + adminFilter.setFailureHandler(new AdminAuthenticationFailureHandler(haloProperties.getProductionEnv(), objectMapper)); + + FilterRegistrationBean authenticationFilter = new FilterRegistrationBean<>(); + authenticationFilter.setFilter(adminFilter); + authenticationFilter.addUrlPatterns("/admin/*"); + authenticationFilter.setOrder(1); + return authenticationFilter; + } } diff --git a/src/main/java/cc/ryanc/halo/config/WebMvcAutoConfiguration.java b/src/main/java/cc/ryanc/halo/config/WebMvcAutoConfiguration.java index f5961688a..4cbc2e7ff 100644 --- a/src/main/java/cc/ryanc/halo/config/WebMvcAutoConfiguration.java +++ b/src/main/java/cc/ryanc/halo/config/WebMvcAutoConfiguration.java @@ -1,20 +1,22 @@ package cc.ryanc.halo.config; import cc.ryanc.halo.config.properties.HaloProperties; +import cc.ryanc.halo.security.resolver.AuthenticationArgumentResolver; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.SessionLocaleResolver; +import java.util.List; import java.util.Locale; /** @@ -32,53 +34,13 @@ import java.util.Locale; @PropertySource(value = "classpath:application.yaml", ignoreResourceNotFound = true, encoding = "UTF-8") public class WebMvcAutoConfiguration implements WebMvcConfigurer { -// @Autowired -// private LoginInterceptor loginInterceptor; -// -// @Autowired -// private InstallInterceptor installInterceptor; -// -// @Autowired -// private ApiInterceptor apiInterceptor; -// -// @Autowired -// private LocaleInterceptor localeInterceptor; -// @Autowired private HaloProperties haloProperties; -// -// /** -// * 注册拦截器 -// * -// * @param registry registry -// */ -// @Override -// public void addInterceptors(InterceptorRegistry registry) { -// registry.addInterceptor(loginInterceptor) -// .addPathPatterns("/admin.*") -// .addPathPatterns("/admin/**") -// .addPathPatterns("/backup/**") -// .excludePathPatterns("/admin/login") -// .excludePathPatterns("/admin/getLogin") -// .excludePathPatterns("/admin/findPassword") -// .excludePathPatterns("/admin/sendResetPasswordEmail") -// .excludePathPatterns("/admin/toResetPassword") -// .excludePathPatterns("/admin/resetPassword") -// .excludePathPatterns("/static/**"); -// registry.addInterceptor(installInterceptor) -// .addPathPatterns("/**") -// .excludePathPatterns("/install") -// .excludePathPatterns("/install/do") -// .excludePathPatterns("/static/**"); -// registry.addInterceptor(apiInterceptor) -// .addPathPatterns("/api/**"); -// registry.addInterceptor(localeInterceptor) -// .addPathPatterns("/admin.*") -// .addPathPatterns("/admin/**") -// .addPathPatterns("/install"); -// registry.addInterceptor(localeChangeInterceptor()) -// .addPathPatterns("/install"); -// } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(new AuthenticationArgumentResolver()); + } /** * 配置静态资源路径 diff --git a/src/main/java/cc/ryanc/halo/config/properties/HaloProperties.java b/src/main/java/cc/ryanc/halo/config/properties/HaloProperties.java index fecadeee8..59b19898f 100644 --- a/src/main/java/cc/ryanc/halo/config/properties/HaloProperties.java +++ b/src/main/java/cc/ryanc/halo/config/properties/HaloProperties.java @@ -16,4 +16,9 @@ public class HaloProperties { * Doc api disabled. (Default is true) */ private Boolean docDisabled = true; + + /** + * Production env. (Default is true) + */ + private Boolean productionEnv = true; } diff --git a/src/main/java/cc/ryanc/halo/exception/AuthenticationException.java b/src/main/java/cc/ryanc/halo/exception/AuthenticationException.java new file mode 100644 index 000000000..d0b605ff0 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/exception/AuthenticationException.java @@ -0,0 +1,24 @@ +package cc.ryanc.halo.exception; + +import org.springframework.http.HttpStatus; + +/** + * Authentication exception. + * + * @author johnniang + */ +public class AuthenticationException extends HaloException { + + public AuthenticationException(String message) { + super(message); + } + + public AuthenticationException(String message, Throwable cause) { + super(message, cause); + } + + @Override + public HttpStatus getStatus() { + return HttpStatus.UNAUTHORIZED; + } +} diff --git a/src/main/java/cc/ryanc/halo/model/dto/post/PostMinimalOutputDTO.java b/src/main/java/cc/ryanc/halo/model/dto/post/PostMinimalOutputDTO.java index 4588e7c5a..7332ce78e 100644 --- a/src/main/java/cc/ryanc/halo/model/dto/post/PostMinimalOutputDTO.java +++ b/src/main/java/cc/ryanc/halo/model/dto/post/PostMinimalOutputDTO.java @@ -8,7 +8,7 @@ import lombok.EqualsAndHashCode; import lombok.ToString; /** - * Page with title only dto. + * Post minimal output dto. * * @author johnniang */ diff --git a/src/main/java/cc/ryanc/halo/model/support/ErrorResponse.java b/src/main/java/cc/ryanc/halo/model/support/ErrorResponse.java new file mode 100644 index 000000000..04a8ac91e --- /dev/null +++ b/src/main/java/cc/ryanc/halo/model/support/ErrorResponse.java @@ -0,0 +1,25 @@ +package cc.ryanc.halo.model.support; + +import lombok.*; + +/** + * Global response entity. + * + * @author johnniang + */ +@Data +@ToString +@EqualsAndHashCode +@NoArgsConstructor +@AllArgsConstructor +public class ErrorResponse { + + private Integer status; + + private String message; + + private String devMessage; + + private Object data; + +} diff --git a/src/main/java/cc/ryanc/halo/security/authentication/Authentication.java b/src/main/java/cc/ryanc/halo/security/authentication/Authentication.java new file mode 100644 index 000000000..5a5b3682a --- /dev/null +++ b/src/main/java/cc/ryanc/halo/security/authentication/Authentication.java @@ -0,0 +1,20 @@ +package cc.ryanc.halo.security.authentication; + +import cc.ryanc.halo.security.support.UserDetail; +import org.springframework.lang.NonNull; + +/** + * Authentication. + * + * @author johnniang + */ +public interface Authentication { + + /** + * Get user detail. + * + * @return user detail + */ + @NonNull + UserDetail getDetail(); +} diff --git a/src/main/java/cc/ryanc/halo/security/authentication/AuthenticationImpl.java b/src/main/java/cc/ryanc/halo/security/authentication/AuthenticationImpl.java new file mode 100644 index 000000000..556c1b68c --- /dev/null +++ b/src/main/java/cc/ryanc/halo/security/authentication/AuthenticationImpl.java @@ -0,0 +1,22 @@ +package cc.ryanc.halo.security.authentication; + +import cc.ryanc.halo.security.support.UserDetail; + +/** + * Authentication implementation. + * + * @author johnniang + */ +public class AuthenticationImpl implements Authentication { + + private final UserDetail userDetail; + + public AuthenticationImpl(UserDetail userDetail) { + this.userDetail = userDetail; + } + + @Override + public UserDetail getDetail() { + return userDetail; + } +} diff --git a/src/main/java/cc/ryanc/halo/security/context/SecurityContext.java b/src/main/java/cc/ryanc/halo/security/context/SecurityContext.java new file mode 100644 index 000000000..eb37b3808 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/security/context/SecurityContext.java @@ -0,0 +1,27 @@ +package cc.ryanc.halo.security.context; + +import cc.ryanc.halo.security.authentication.Authentication; +import org.springframework.lang.Nullable; + +/** + * Security context interface. + * + * @author johnniang + */ +public interface SecurityContext { + + /** + * Gets the currently authenticated principal. + * + * @return the Authentication or null if authentication information is unavailable + */ + @Nullable + Authentication getAuthentication(); + + /** + * Changes the currently authenticated principal, or removes the authentication information. + * + * @param authentication the new authentication or null if no further authentication should not be stored + */ + void setAuthentication(@Nullable Authentication authentication); +} diff --git a/src/main/java/cc/ryanc/halo/security/context/SecurityContextHolder.java b/src/main/java/cc/ryanc/halo/security/context/SecurityContextHolder.java new file mode 100644 index 000000000..310bbd65f --- /dev/null +++ b/src/main/java/cc/ryanc/halo/security/context/SecurityContextHolder.java @@ -0,0 +1,63 @@ +package cc.ryanc.halo.security.context; + +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +/** + * Security context holder. + * + * @author johnniang + * @date 12/11/18 + */ +public class SecurityContextHolder { + + private final static ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); + + private SecurityContextHolder() { + } + + /** + * Gets context. + * + * @return security context + */ + @NonNull + public static SecurityContext getContext() { + // Get from thread local + SecurityContext context = CONTEXT_HOLDER.get(); + if (context == null) { + // If no context is available now then create an empty context + context = createEmptyContext(); + // Set to thread local + CONTEXT_HOLDER.set(context); + } + + return context; + } + + /** + * Sets security context. + * + * @param context security context + */ + public static void setContext(@Nullable SecurityContext context) { + CONTEXT_HOLDER.set(context); + } + + /** + * Clears context. + */ + public static void clearContext() { + CONTEXT_HOLDER.remove(); + } + + /** + * Creates an empty security context. + * + * @return an empty security context + */ + @NonNull + private static SecurityContext createEmptyContext() { + return new SecurityContextImpl(); + } +} diff --git a/src/main/java/cc/ryanc/halo/security/context/SecurityContextImpl.java b/src/main/java/cc/ryanc/halo/security/context/SecurityContextImpl.java new file mode 100644 index 000000000..e6887d3ae --- /dev/null +++ b/src/main/java/cc/ryanc/halo/security/context/SecurityContextImpl.java @@ -0,0 +1,32 @@ +package cc.ryanc.halo.security.context; + +import cc.ryanc.halo.security.authentication.Authentication; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * Security context implementation. + * + * @author johnniang + */ +@ToString +@EqualsAndHashCode +@NoArgsConstructor +@AllArgsConstructor +public class SecurityContextImpl implements SecurityContext { + + private Authentication authentication; + + @Override + public Authentication getAuthentication() { + return authentication; + } + + @Override + public void setAuthentication(Authentication authentication) { + this.authentication = authentication; + } + +} diff --git a/src/main/java/cc/ryanc/halo/security/filter/AdminAuthenticationFilter.java b/src/main/java/cc/ryanc/halo/security/filter/AdminAuthenticationFilter.java new file mode 100644 index 000000000..c7ef87064 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/security/filter/AdminAuthenticationFilter.java @@ -0,0 +1,29 @@ +package cc.ryanc.halo.security.filter; + +import cc.ryanc.halo.security.handler.AuthenticationFailureHandler; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Admin authentication filter. + * + * @author johnniang + */ +public class AdminAuthenticationFilter extends OncePerRequestFilter { + + private AuthenticationFailureHandler failureHandler; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + // TODO Handle admin authentication + } + + public void setFailureHandler(AuthenticationFailureHandler failureHandler) { + this.failureHandler = failureHandler; + } +} diff --git a/src/main/java/cc/ryanc/halo/security/filter/ApiAuthenticationFilter.java b/src/main/java/cc/ryanc/halo/security/filter/ApiAuthenticationFilter.java new file mode 100644 index 000000000..9667e3b7e --- /dev/null +++ b/src/main/java/cc/ryanc/halo/security/filter/ApiAuthenticationFilter.java @@ -0,0 +1,30 @@ +package cc.ryanc.halo.security.filter; + +import cc.ryanc.halo.security.handler.AuthenticationFailureHandler; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Api authentication Filter + * + * @author johnniang + */ +public class ApiAuthenticationFilter extends OncePerRequestFilter { + + private AuthenticationFailureHandler failureHandler; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + // TODO Handle authentication + filterChain.doFilter(request, response); + } + + public void setFailureHandler(AuthenticationFailureHandler failureHandler) { + this.failureHandler = failureHandler; + } +} diff --git a/src/main/java/cc/ryanc/halo/security/handler/AdminAuthenticationFailureHandler.java b/src/main/java/cc/ryanc/halo/security/handler/AdminAuthenticationFailureHandler.java new file mode 100644 index 000000000..a0ebadf60 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/security/handler/AdminAuthenticationFailureHandler.java @@ -0,0 +1,26 @@ +package cc.ryanc.halo.security.handler; + +import cc.ryanc.halo.exception.HaloException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Authentication failure handler. + * + * @author johnniang + */ +public class AdminAuthenticationFailureHandler extends DefaultAuthenticationFailureHandler { + + public AdminAuthenticationFailureHandler(boolean productionEnv, ObjectMapper objectMapper) { + super(productionEnv, objectMapper); + } + + @Override + public void onFailure(HttpServletRequest request, HttpServletResponse response, HaloException exception) throws IOException, ServletException { + // TODO handler the admin authentication failure. + } +} diff --git a/src/main/java/cc/ryanc/halo/security/handler/AuthenticationFailureHandler.java b/src/main/java/cc/ryanc/halo/security/handler/AuthenticationFailureHandler.java new file mode 100644 index 000000000..10d959e8d --- /dev/null +++ b/src/main/java/cc/ryanc/halo/security/handler/AuthenticationFailureHandler.java @@ -0,0 +1,27 @@ +package cc.ryanc.halo.security.handler; + +import cc.ryanc.halo.exception.HaloException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Authentication failure handler. + * + * @author johnniang + */ +public interface AuthenticationFailureHandler { + + /** + * Calls when a user has been unsuccessfully authenticated. + * + * @param request http servlet request + * @param response http servlet response + * @param exception api exception + * @throws IOException io exception + * @throws ServletException service exception + */ + void onFailure(HttpServletRequest request, HttpServletResponse response, HaloException exception) throws IOException, ServletException; +} diff --git a/src/main/java/cc/ryanc/halo/security/handler/DefaultAuthenticationFailureHandler.java b/src/main/java/cc/ryanc/halo/security/handler/DefaultAuthenticationFailureHandler.java new file mode 100644 index 000000000..c2d6c2956 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/security/handler/DefaultAuthenticationFailureHandler.java @@ -0,0 +1,54 @@ +package cc.ryanc.halo.security.handler; + +import cc.ryanc.halo.exception.HaloException; +import cc.ryanc.halo.model.support.ErrorResponse; +import cc.ryanc.halo.utils.ExceptionUtils; +import cn.hutool.extra.servlet.ServletUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Default AuthenticationFailureHandler. + * + * @author johnniang + * @date 12/12/18 + */ +@Slf4j +public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler { + + private final boolean productionEnv; + + private final ObjectMapper objectMapper; + + public DefaultAuthenticationFailureHandler(boolean productionEnv, + ObjectMapper objectMapper) { + this.productionEnv = productionEnv; + this.objectMapper = objectMapper; + } + + @Override + public void onFailure(HttpServletRequest request, HttpServletResponse response, HaloException exception) throws IOException, ServletException { + log.warn("Handle unsuccessful authentication, ip: [{}]", ServletUtil.getClientIP(request)); + + ErrorResponse errorDetail = new ErrorResponse(); + + errorDetail.setMessage(exception.getMessage()); + + if (!productionEnv) { + errorDetail.setDevMessage(ExceptionUtils.getStackTrace(exception)); + } + + log.debug("Response error: [{}]", errorDetail); + + response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + response.setStatus(exception.getStatus().value()); + response.getWriter().write(objectMapper.writeValueAsString(errorDetail)); + } + +} diff --git a/src/main/java/cc/ryanc/halo/security/resolver/AuthenticationArgumentResolver.java b/src/main/java/cc/ryanc/halo/security/resolver/AuthenticationArgumentResolver.java new file mode 100644 index 000000000..0087e3e45 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/security/resolver/AuthenticationArgumentResolver.java @@ -0,0 +1,61 @@ +package cc.ryanc.halo.security.resolver; + +import cc.ryanc.halo.exception.AuthenticationException; +import cc.ryanc.halo.model.entity.User; +import cc.ryanc.halo.security.authentication.Authentication; +import cc.ryanc.halo.security.context.SecurityContextHolder; +import cc.ryanc.halo.security.support.UserDetail; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.MethodParameter; +import org.springframework.lang.Nullable; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import java.util.Optional; + +/** + * Authentication argument resolver. + * + * @author johnniang + * @date 12/11/18 + */ +@Slf4j +public class AuthenticationArgumentResolver implements HandlerMethodArgumentResolver { + + public AuthenticationArgumentResolver() { + log.debug("Initializing AuthenticationArgumentResolver"); + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + Class parameterType = parameter.getParameterType(); + return (Authentication.class.isAssignableFrom(parameterType) || + UserDetail.class.isAssignableFrom(parameterType) || + User.class.isAssignableFrom(parameterType)); + } + + @Override + @Nullable + public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) { + log.debug("Handle AuthenticationArgument"); + + Class parameterType = parameter.getParameterType(); + + Authentication authentication = Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) + .orElseThrow(() -> new AuthenticationException("You haven't signed in yet")); + + if (Authentication.class.isAssignableFrom(parameterType)) { + return authentication; + } else if (UserDetail.class.isAssignableFrom(parameterType)) { + return authentication.getDetail(); + } else if (User.class.isAssignableFrom(parameterType)) { + return authentication.getDetail().getUser(); + } + + // Should never happen... + throw new UnsupportedOperationException("Unknown parameter type: " + parameterType); + } + +} diff --git a/src/main/java/cc/ryanc/halo/security/support/UserDetail.java b/src/main/java/cc/ryanc/halo/security/support/UserDetail.java new file mode 100644 index 000000000..d2dc2b2c9 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/security/support/UserDetail.java @@ -0,0 +1,34 @@ +package cc.ryanc.halo.security.support; + +import cc.ryanc.halo.exception.AuthenticationException; +import cc.ryanc.halo.model.entity.User; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.lang.NonNull; + +/** + * User detail. + * + * @author johnniang + */ +@ToString +@EqualsAndHashCode +public class UserDetail { + + private User user; + + /** + * Gets user info. + * + * @return user info + * @throws AuthenticationException throws if the user is null + */ + @NonNull + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } +} diff --git a/src/main/java/cc/ryanc/halo/web/controller/admin/PostController.java b/src/main/java/cc/ryanc/halo/web/controller/admin/PostController.java index f7423e989..d568bbcf2 100644 --- a/src/main/java/cc/ryanc/halo/web/controller/admin/PostController.java +++ b/src/main/java/cc/ryanc/halo/web/controller/admin/PostController.java @@ -8,6 +8,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; import org.springframework.data.web.SortDefault; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -40,12 +41,12 @@ public class PostController { * @param status post status * @param page current page * @param sort sort - * * @return template path: admin/admin_post.ftl */ @GetMapping public String posts(Model model, - @RequestParam(value = "status", defaultValue = "0") PostStatus status, + @RequestParam(value = "status", defaultValue = "PUBLISHED") PostStatus status, + @PageableDefault Pageable defaultPageable, @RequestParam(value = "page", defaultValue = "0") Integer page, @SortDefault.SortDefaults({ @SortDefault(sort = "postPriority", direction = DESC),