问题
拦截器的使用场景是一个很常见的问题,下面是一些常见企业应用。
场景
根据拦截器的name来看作用是:
- 拦截未登陆用户
- 账号安全相关
- 权限相关
- 防止CSRF攻击
- 风控相关
使用
实现HandlerInterceptor接口
HandlerInterceptor接口定义如下:
public interface HandlerInterceptor {
/**
* Intercept the execution of a handler. Called after HandlerMapping determined
* an appropriate handler object, but before HandlerAdapter invokes the handler.
* <p>DispatcherServlet processes a handler in an execution chain, consisting
* of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can decide to abort the execution chain,
* typically sending a HTTP error or writing a custom response.
* @param request current HTTP request
* @param response current HTTP response
* @param handler chosen handler to execute, for type and/or instance evaluation
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
* @throws Exception in case of errors
*/
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;
/**
* Intercept the execution of a handler. Called after HandlerAdapter actually
* invoked the handler, but before the DispatcherServlet renders the view.
* Can expose additional model objects to the view via the given ModelAndView.
* <p>DispatcherServlet processes a handler in an execution chain, consisting
* of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can post-process an execution,
* getting applied in inverse order of the execution chain.
* @param request current HTTP request
* @param response current HTTP response
* @param handler handler (or {@link HandlerMethod}) that started async
* execution, for type and/or instance examination
* @param modelAndView the {@code ModelAndView} that the handler returned
* (can also be {@code null})
* @throws Exception in case of errors
*/
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;
/**
* Callback after completion of request processing, that is, after rendering
* the view. Will be called on any outcome of handler execution, thus allows
* for proper resource cleanup.
* <p>Note: Will only be called if this interceptor's {@code preHandle}
* method has successfully completed and returned {@code true}!
* <p>As with the {@code postHandle} method, the method will be invoked on each
* interceptor in the chain in reverse order, so the first interceptor will be
* the last to be invoked.
* @param request current HTTP request
* @param response current HTTP response
* @param handler handler (or {@link HandlerMethod}) that started async
* execution, for type and/or instance examination
* @param ex exception thrown on handler execution, if any
* @throws Exception in case of errors
*/
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;
}
在调用下一个拦截器之前会先执行preHandle方法,从下一个拦截器返回之后会执行postHandle方法,最后会执行afterCompletion方法。我么只需要重写preHandler和postHandler方法。
xml文件配置
如上上图所示,将自己定义的实现了HandlerInterceptor方法的拦截器配置到"Interceptors"标签中即可。
拦截器场景分析
登陆拦截
public class LoginInterceptor implements HandlerInterceptor {
private static final Logger logger= LoggerFactory.getLogger(LoginInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
User user = ContextUtil.getCurrentUser();
if (user == null) {
redirectLogin(request, response);
return false;
}
if(user.getUserType() == UserTypeEnum.SHOP.getCode()){
SSOHelper.logoutV2(request, response);
redirectLogin(request, response);
return false;
}
logger.info("[LoginInterceptor - preHandle ] requestUrl:{} currentUser: {} ",request.getRequestURI(),hipacUser
.getUserNickName());
LoginUtil.setCurrentUser(user);
return true;
}
private void redirectLogin(HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException {
if (ContextUtil.isAjax()) {
// ajax请求
PrintWriter out = response.getWriter();
response.setStatus(403);
out.print("_NO_LOGIN_SESSION");
out.flush();
return;
} else {
try {
StringBuilder sb=new StringBuilder(request.getRequestURI());
sb.append("?");
Enumeration<String> names = request.getParameterNames();
while (names.hasMoreElements()){
String key = names.nextElement();
sb.append(key).append("=").append(request.getParameter(key)).append("&");
}
response.sendRedirect(request.getContextPath() + "/admin/toIndex.do");
}catch (Exception e){
logger.error("[LoginInterceptor - redirectLogin ] fail ",e);
response.sendRedirect(request.getContextPath() + "/admin/toIndex.do");
}
return;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
LoginUtil.removeCurrentUser();
}
}
可以看到以上拦截器的作用的检查用户是否存在,如果不存在会重定向到登陆页面。
其中获取用户信息的方法如下:
/**
* pc端查询登录用户信息
*
* @return
*/
public static User getCurrentUser() {
String userId = getCurrentUserId();
if (StringUtils.isBlank(userId)) {
return null;
}
UserService userService = ApplicationContextUtil.getBean(UserService.class);
return userService.getUserById(userId);
}
账号安全拦截器
如果用户被打标成登陆密码过于简单,会引导用户去修改密码。
实现如下:
public class AccountSecurityInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(AccountSecurityInterceptor.class);
@Autowired
private TagQueryApi tagQueryApi;
@Autowired
private UserQueryApi userQueryApi;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
String userId = ContextUtil.getCurrentUser().getId();
ResultData<UserTO> userTOResultData = userQueryApi.getUserInfoById(userId);
if(null == userTOResultData || null == userTOResultData.getData()){
logger.error("[LoginInterceptor - redirectSecurity]用户信息获取失败.");
return true;
}
ResultData<TTagValueTO> resultDate = tagQueryApi.queryTag(userId, WebConstants.USER_IS_UPDATE_PASS);
if (null == resultDate || !resultDate.isSuccess()) {
logger.error("[LoginInterceptor - redirectSecurity]获取用户打标接口失败.");
return true;
}
//if (null != resultDate && null != resultDate.getData()) {
if (null == resultDate.getData()) {
if (ContextUtil.isAjax()) {
// ajax请求
PrintWriter out = response.getWriter();
response.setStatus(403);
out.print("LOW_ACCOUNT_SECURITY");
out.flush();
} else {
response.sendRedirect(request.getContextPath() + "/admin/toAccountSecurity.do");
}
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
}
CSRF防御拦截器
对list之外的请求进行过滤,防止CSRF攻击。
public class CSRFInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(CSRFInterceptor.class);
public static final List<String> list = Lists.newArrayList(
"http://","http://");
// 顶级域名
public static final String DOMAIN = "";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 验证 Referer来源
String referer = request.getHeader("Referer");
String domain="http://";
String httpsDomain = "https://";
if (StringUtils.isNotBlank(domain) && StringUtils.isNotBlank(referer) && !(referer.startsWith(domain) || referer.startsWith(httpsDomain))){
String host = getHost(referer);
if(StringUtils.isNotBlank(host) && host.endsWith(DOMAIN)){
return true;
}
for (String s : list) {
if (referer.startsWith(s)){
return true;
}
}
throw new Exception(302,"不可信的网站来源!referer="+referer+",handler="+handler);
}
return true;
}
private String getHost(String url){
try {
URL u = new URL(url);
String host = u.getHost();
host.replaceAll("/","");
return u.getHost();
} catch (MalformedURLException e) {
return null;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
源码分析
springMVC中的核心就是DispatchServlet类,该类的核心又是doDispatch方法。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//这里先调用preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(request, mv);
//这里调用posthandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
//触发执行afterCompletion方法
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
总结
正如拦截器的名字,应用到的主要功能是对不合法,或者没有权限,或者不安全的账号进行过滤或者引导。下面会单独写一篇文章介绍接口权限的实现。
谢谢阅读!禁止转载。