在web应用中有大量场景需要对用户进行安全校,一般人的做法就是硬编码的方式直接埋到到业务代码中,但可曾想过这样做法会导致代码不够简洁(大量重复代码)、有个性化时难维护(每个业务逻辑访问控制策略都不相同甚至差异很大)、容易发生安全泄露(有些业务可能不需要当前登录信息,但被访问的数据可能是敏感数据由于遗忘而没有受到保护)。
为了更安全、更方便的进行访问安全控制,我们可以想到的就是使用springmvc的拦截器(HandlerInterceptor),但其实更推荐使用更为成熟的spring security来完成认证和鉴权。
拦截器
拦截器HandlerInterceptor确实可以帮我们完成登录拦截、或是权限校验、或是防重复提交等需求。其实基于它也可以实现基于url或方法级的安全控制。
如果你对spring mvc的请求处理流程相对的了解,它的原理容易理解,具体可以参阅我之前的分享。
public interface HandlerInterceptor { /** * Intercept the execution of a handler. Called after HandlerMapping determined * an appropriate handler object, but before HandlerAdapter invokes the handler. * * 在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理 * * handler:controller内的方法,可以通过HandlerMethod method= ((HandlerMethod)handler);获取到@RequestMapping */ 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. * * 在业务处理器<a>本文来源gao($daima.com搞@代@#码8网^</a>处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView */ 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. * * 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面) * */ void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception; } //你可以基于有些url进行拦截 @Configuration public class UserSecurityInterceptor extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { String[] securityUrls = new String[]{"/**"}; String[] excludeUrls = new String[]{"/**/esb/**", "/**/dictionary/**"}; registry.addInterceptor(userLoginInterceptor()).excludePathPatterns(excludeUrls).addPathPatterns(securityUrls); super.addInterceptors(registry); } /** fixed: url中包含// 报错 * org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL was not normalized. * @return */ @Bean public HttpFirewall allowUrlEncodedSlashHttpFirewall() { DefaultHttpFirewall firewall = new DefaultHttpFirewall(); firewall.setAllowUrlEncodedSlash(true); return firewall; } @Bean public AuthInterceptor userLoginInterceptor() { return new AuthInterceptor(); } public class AuthInterceptor implements HandlerInterceptor { public Logger logger = LoggerFactory.getLogger(AuthInterceptor.class); @Autowired private ApplicationContext applicationContext; public AuthInterceptor() { } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { LoginUserInfo user = null; try { user = (LoginUserInfo) SSOUserUtils.getCurrentLoginUser(); } catch (Exception e) { logger.error("从SSO登录信息中获取用户信息失败! 详细错误信息:%s", e); throw new ServletException("从SSO登录信息中获取用户信息失败!", e); } String[] profiles = applicationContext.getEnvironment().getActiveProfiles(); if (!Arrays.isNullOrEmpty(profiles)) { if ("dev".equals(profiles[0])) { return true; } } if (user == null || UserUtils.ANONYMOUS_ROLE_ID.equals(user.getRoleId())) { throw new ServletException("获取登录用户信息失败!"); } 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 { } } }