使用 @ControllerAdvice 制作简单的 servlet 过滤器
Posted
技术标签:
【中文标题】使用 @ControllerAdvice 制作简单的 servlet 过滤器【英文标题】:Make simple servlet filter work with @ControllerAdvice 【发布时间】:2015-07-31 20:15:09 【问题描述】:我有一个简单的过滤器,只是为了检查请求是否包含带有静态密钥的特殊标头 - 没有用户身份验证 - 只是为了保护端点。这个想法是如果键不匹配则抛出AccessForbiddenException
,然后将其映射到带有@ControllerAdvice
注释的类的响应。但是我不能让它工作。我的@ExceptionHandler
没有被调用。
ClientKeyFilter
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Controller
import javax.servlet.*
import javax.servlet.http.HttpServletRequest
@Controller //I know that @Component might be here
public class ClientKeyFilter implements Filter
@Value('$CLIENT_KEY')
String clientKey
public void init(FilterConfig filterConfig)
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
req = (HttpServletRequest) req
def reqClientKey = req.getHeader('Client-Key')
if (!clientKey.equals(reqClientKey))
throw new AccessForbiddenException('Invalid API key')
chain.doFilter(req, res)
public void destroy()
AccessForbiddenException
public class AccessForbiddenException extends RuntimeException
AccessForbiddenException(String message)
super(message)
异常控制器
@ControllerAdvice
class ExceptionController
static final Logger logger = LoggerFactory.getLogger(ExceptionController)
@ExceptionHandler(AccessForbiddenException)
public ResponseEntity handleException(HttpServletRequest request, AccessForbiddenException e)
logger.error('Caught exception.', e)
return new ResponseEntity<>(e.getMessage(), I_AM_A_TEAPOT)
我哪里错了?简单的 servlet 过滤器可以和 spring-boot 的异常映射一起工作吗?
【问题讨论】:
过滤器永远不会发生这种情况。@ControllerAdvice
仅对到达DispatcherServlet
的请求有用,Filter
s 总是在此之前执行。要么将该逻辑放在过滤器中,要么使用 HandlerInterceptor
代替过滤器。
@M.Deinum,我终于用上了HandlerInterceptor
。如果您想将其添加为答案,我很乐意接受。
【参考方案1】:
正如java servlet 规范Filter
s 所指定的,总是在调用Servlet
之前执行。现在@ControllerAdvice
仅对在DispatcherServlet
内执行的控制器有用。因此,使用Filter
并期待@ControllerAdvice
或在本例中为@ExceptionHandler
,将不会被调用。
您需要在过滤器中放入相同的逻辑(用于编写 JSON 响应),或者使用执行此检查的 HandlerInterceptor
代替过滤器。最简单的方法是扩展 HandlerInterceptorAdapter
并重写并实现 preHandle
方法并将过滤器中的逻辑放入该方法中。
public class ClientKeyInterceptor extends HandlerInterceptorAdapter
@Value('$CLIENT_KEY')
String clientKey
@Override
public boolean preHandle(ServletRequest req, ServletResponse res, Object handler)
String reqClientKey = req.getHeader('Client-Key')
if (!clientKey.equals(reqClientKey))
throw new AccessForbiddenException('Invalid API key')
return true;
【讨论】:
自从您发布答案后可能会发生变化,但现在的签名是:public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
【参考方案2】:
你不能使用@ControllerAdvice
,因为它会在某些控制器出现异常的情况下被调用,但你的ClientKeyFilter
不是@Controller
。
您应该将@Controller
注释替换为@Component
,然后像这样设置响应正文和状态:
@Component
public class ClientKeyFilter implements Filter
@Value('$CLIENT_KEY')
String clientKey
public void init(FilterConfig filterConfig)
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String reqClientKey = request.getHeader("Client-Key");
if (!clientKey.equals(reqClientKey))
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid API key");
return;
chain.doFilter(req, res);
public void destroy()
【讨论】:
【参考方案3】:Java 类中的 Servlet 过滤器用于以下目的:
在客户端访问后端资源之前检查来自客户端的请求。 在发送回客户端之前检查来自服务器的响应。@ControllerAdvice 可能无法捕获来自 Filter 的异常抛出,因为 in 可能无法到达 DispatcherServlet。我在我的项目中处理如下:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException
String token = null;
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && (bearerToken.contains("Bearer ")))
if (bearerToken.startsWith("Bearer "))
token = bearerToken.substring(7, bearerToken.length());
try
AuthenticationInfo authInfo = TokenHandler.validateToken(token);
logger.debug("Found id:", authInfo.getId());
authInfo.uri = request.getRequestURI();
AuthPersistenceBean persistentBean = new AuthPersistenceBean(authInfo);
SecurityContextHolder.getContext().setAuthentication(persistentBean);
logger.debug("Found id:'', added into SecurityContextHolder", authInfo.getId());
catch (AuthenticationException authException)
logger.error("User Unauthorized: Invalid token provided");
raiseException(request, response);
return;
catch (Exception e)
raiseException(request, response);
return;
// 包装错误响应
private void raiseException(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ApiError apiError = new ApiError(HttpStatus.UNAUTHORIZED);
apiError.setMessage("User Unauthorized: Invalid token provided");
apiError.setPath(request.getRequestURI());
byte[] body = new ObjectMapper().writeValueAsBytes(apiError);
response.getOutputStream().write(body);
// ApiError 类
public class ApiError
// 4xx and 5xx
private HttpStatus status;
// holds a user-friendly message about the error.
private String message;
// holds a system message describing the error in more detail.
private String debugMessage;
// returns the part of this request's URL
private String path;
public ApiError(HttpStatus status)
this();
this.status = status;
//setter and getters
【讨论】:
以上是关于使用 @ControllerAdvice 制作简单的 servlet 过滤器的主要内容,如果未能解决你的问题,请参考以下文章