我们已经实现了用户管理和博客管理功能,但是没有做任何权限设置。权限相关的功能就需要使用Session了。这个概念如果你还不了解,先参考管理用户状态——Cookie与Session 。
解决权限问题的常用方法是:
在Controller方法中使用Session极其简单,只需要在Controller方法中声明一个HttpSession
参数即可:
@GetMapping(value = "/create")
public String showCreatePage(Model model, HttpSession session) {
// Session中没有设置用户信息,则表示用户还没有登录
if (session.getAttribute("CURRENT_USER") == null) {
return "redirect:/";
}
model.addAttribute("post", new Post());
return "create";
}
提高
如果希望使用HttpRequest
和HttpResponse
等Servlet原生对象,也直接在方法中声明参数即可,Spring MVC会根据请求生成相应的实例,我们只需在方法实现中直接使用即可。
但是,当系统复杂、页面增多,大量页面需要做类似的权限判断时,在每一个Controller方法中都加上类似的Session判断,显然会产生大量的重复代码。这时就需要Spring MVC的拦截器(Interceptor)闪亮登场了。
Spring MVC框架中的Interceptor,与Servlet API中的Filter十分类似,用于对Web请求进行预处理/后处理。通常情况下这些预处理/后处理逻辑是通用的,可以被应用于所有或多个Web请求,例如:
上图是Spring MVC框架处理Web请求的基本流程,请求会经过DispatcherServlet
的分发后,会按顺序经过一系列的Interceptor
并执行其中的预处理方法,在请求返回时同样会执行其中的后处理方法。
在DispatcherServlet
和Controller
之间哪些竖着的彩色细条,是拦截请求进行额外处理的地方,所以命名为拦截器(Interceptor)。
Spring MVC中拦截器是实现了HandlerInterceptor
接口的Bean:
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception;
void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception;
void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) throws Exception;
}
preHandle()
:预处理回调方法,若方法返回值为true
,请求继续(调用下一个拦截器或处理器方法);若方法返回值为false
,请求处理流程中断,不会继续调用其他的拦截器或处理器方法,此时需要通过response
产生响应;postHandle()
:后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时可以通过ModelAndView
对模型数据进行处理或对视图进行处理afterCompletion()
:整个请求处理完毕回调方法,即在视图渲染完毕时调用HandlerInterceptor
有三个方法需要实现,但大部分时候可能只需要实现其中的一个方法,HandlerInterceptorAdapter
是一个实现了HandlerInterceptor
的抽象类,它的三个实现方法都为空实现(或者返回true
),继承该抽象类后可以仅仅实现其中的一个方法:
public class Interceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 在controller方法调用前打印信息
System.out.println("This is interceptor.");
// 返回true,将强求继续传递(传递到下一个拦截器,没有其它拦截器了,则传递给Controller)
return true;
}
}
定义HandlerInterceptor
后,需要创建WebMvcConfigurerAdapter
在MVC配置中将它们应用于特定的URL中。一般一个拦截器都是拦截特定的某一部分请求,这些请求通过URL模型来指定。
下面是一个配置的例子:
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleInterceptor());
registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
}
}
之前的过程中,如果我们访问创建新文章页面,那么一旦发现Session中CURRENT_USER
属性不存在就回直接跳转回首页。其实这个过程可以做两个优化
CURRENT_USER
属性不存在,就跳转到登录页面,登录成功后再跳转回来public class LoginInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 如果用户已经登录了,就会在Session中以"CURRENT_USER"属性保存当前用户对应的User对象
if (request.getSession().getAttribute("CURRENT_USER") != null) {
return true;
}
response.sendRedirect("/login?next=".concat(request.getRequestURI()));
return false;
}
}
为了实现跳转登录页面登录成功后能够返回当前页面,在Interceptor
中将当前URL作为/login
的参数next
,这里调用了request.getRequestURL()
方法获取当前请求的URL。
假设我们通过LoginController
来处理登陆,简化的示例代码如下:
@Controller
@RequestMapping("/login")
public class LoginController {
@GetMapping
public String loginPage(@RequestParam("next") Optional<String> next) {
return "login";
}
@PostMapping
public String login(@RequestParam("next") Optional<String> next, User user, HttpSession session) {
// Get User instance
session.setAttribute("CURRENT_USER", user);
return "redirect:".concat(next.orElse("/"));
}
}
在登录的POST方法中,除了将Session中放入user对象外,跳转到next
以便回到登录前的页面。
Optional<String> next
表示next
是一个可选参数,return "redirect:".concat(next.orElse("/"));
表示跳转到next
表示的URL,如果next
为null
,则跳转到首页/
。
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public voidaddInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/posts/create");
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/admin/**");
}
}
可以让拦截器拦截一个特定的URL。也可以使用URL模式来配置拦截器,比如registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/admin/**");
表示所有以/admin/
开头的URL都需要经过LoginInterceptor
处理。
一般情况下会使用URL模式来配置拦截器,因为只需要一份代码就能给多个Controller实现额外的逻辑,这正是拦截器的优势所在。
登录发表评论 登录 注册
我了个去,这个教程真的是神作