我有一个简单的过滤器,只是为了检查一个请求是否包含一个带有静态密钥的特殊标头 - 没有用户身份验证 - 只是为了保护端点。如果密钥不匹配,则抛出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)
}
}
ExceptionController
@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的异常映射吗?
答案 0 :(得分:5)
由java servlet规范Filter
指定,在调用Servlet
之前始终执行。现在@ControllerAdvice
仅对在DispatcherServlet
内执行的控制器有用。因此,使用Filter
并期望@ControllerAdvice
或在此情况下@ExceptionHandler
将被调用不会发生。
您需要在过滤器中放置相同的逻辑(用于编写JSON响应)或者使用trigger()来代替过滤器进行此检查。最简单的方法是扩展HandlerInterceptor
,然后覆盖并实现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;
}
}
答案 1 :(得分:4)
您无法使用@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() {
}
}
答案 2 :(得分:0)
Java类中的Servlet过滤器用于以下目的:
从Filter抛出的异常可能不会被 @ControllerAdvice 捕获,因为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