在天码营博客系统中,当进行文章创建时,用户没有登录的情况下应该是不允许操作的。因此对于没有登录的用户,不应该让它看到创建文章的表单页面。
为了实现这样的需求,我们可以直接在Servlet中编写类似逻辑:
User user = (User) req.getSession().getAttribute("currentUser");
if (user == null) {
HttpServletResponse response = (HttpServletResponse) resp;
response.sendError(HttpServletResponse.SC_FORBIDDEN, "no user in session");
}
SC_FORBIDDEN
是HttpServletResponse
中定义的一个常量,它的值是403
,这里的sendError
函数相当于向浏览器返回一个403 Forbidden
的状态码以及权限错误信息。
事实上,这一类需求在Web应用中非常的常见,用户身份的认证以及权限控制是一个系统中极为重要的组成部分。例如普通用户不能随便地修改博主的文章。当我们的Web应用页面越来越多,需要类似权限控制的逻辑愈发复杂时,如果还在每一个Servlet中去编写这样的逻辑,不仅会出现大量重复的代码(可能很多页面的控制逻辑是相同的——例如用户已经登录才能访问),而且也违背了软件设计模式中的单一职责原则,Servlet既需要考虑处理HTTP请求,同时还需要考虑权限控制。
解决上面提到问题的办法其实也很简单——把权限控制逻辑从Servlet组件中提取出来,另外作为一种可复用的组件——不仅Servlet的权限控制可以使用,而且今后其它需要用到权限控制的地方同样可以用到。这样做的好处就是把Servlet的职责清晰化,并且和权限控制逻辑解耦。
Filter
是Servlet规范中非常有用的组件。Filter
被用于在Servlet/JSP等资源处理HTTP请求之前,对请求进行拦截处理,下图是Filter拦截HTTP请求的过程:
Filter在Servlet/JSP之前对HTTP请求进行拦截,可以同时存在多个Filter组成Filter链(Chain),在任意Filter中可以决定继续执行Filter链中的下一个Filter,还是把response
直接返回到浏览器。Filter链全部执行完成后,HTTP请求才会到达相应的Servlet/JSP中。以下是Servlet规范中Filter
接口的定义:
public interface Filter {
public void init(FilterConfig filterConfig) throws ServletException;
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
public void destroy();
}
和Servlet
类似,Filter
接口也需要实现生命周期中的init()
和destroy()
方法。它最重要的方法是doFilter()
,在该方法中可以拿到request
和response
对象并执行相应的逻辑。
下面我们把创建博文需要登录的逻辑放在Filter
中:
@WebFilter(filterName = "loginFilter", urlPatterns = "/create")
public class LoginFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
User user = (User) request.getSession().getAttribute("currentUser");
if (user == null) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "no user in session");
return;
}
filterChain.doFilter(req, resp);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
@WebFilter(filterName = "loginFilter", urlPatterns = "/create")
表示LoginFilter
应该被Servlet容器作为一个Filter加载,它拦截所有URL路径为/create
的请求(包括GET和POST请求)。如果需要拦截多个URL模式,则可以将多个URL模式以逗号分隔,放在大括号中urlPatterns={"/create", "/about"}
。403
;如果存在,调用chain.doFilter(req,resp)
继续执行Filter链中的下一个Filter。这样我们把处理HTTP请求的业务逻辑放在了Servlet中,把权限控制的逻辑放在了Filter中,二者各司其职并且都是可以被复用的组件。
在上一节描述的保护页面中,如果用户没有登录,直接通过URL(虽然无法从页面直接点击过去,但是可以在浏览器中直接输入)访问文章创建页面,那么它会得到一个Tomcat容器默认的错误提示页面,提示用户无权限访问。当用户看到这个错误提示页面,它可能会有多种选择——可能会回退到上一个页面,可能会关闭页面重新打开,无论哪种情况体验似乎都不友好。
其实这个过程可以优化为:如果用户尚未登录却访问了一个需要登录的页面,就先跳转到登录页面,用户登录成功后再跳转到他之前想要访问的目标页面,整个过程示意如下:
整个过程的重点就是在重定向到登录页面时在登录页面的url后面戴上了一个名字为next
的参数,这个参数代表着目标页面的url。一旦登录成功,处理登录请求的Servlet就会重定向到该url。
在Filter中只需要通过request.getRequestURI()
获取当前受保护页面的url,将其作为参数重定向:
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
User user = (User) request.getSession().getAttribute("currentUser");
if (user == null) {
HttpServletResponse response = (HttpServletResponse) resp;
response.sendRedirect(request.getContextPath() + "/login?next=" + request.getRequestURI());
return;
}
filterChain.doFilter(req, resp);
}
在UserLoginServlet
中需要获取next
参数并进行跳转:
//如果登录成功
String next = req.getParameter("next");
if (next == null || next.isEmpty()) {
resp.sendRedirect(request.getContextPath() + "/blogs");
} else {
resp.sendRedirect(next);
}
而在CreateBlogServlet
中,用户登陆判断的代码就不再需要了:
public class CreateBlogServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 这个if判断可以删除了
/*
if (request.getSession().getAttribute("currentUser") == null) {
response.sendRedirect(request.getContextPath() + "/login");
return;
}
*/
RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/create.jsp");
dispatcher.forward(request, response);
}
}
登录发表评论 登录 注册
老师你好,我想实现某人第一次进入create页面,由于没有登陆,跳转至login页面,登陆后自动跳转至create页面。
我在filter中的页面重定向里加了?next= request.getRequestURI()
但是,我在login页面登陆提交表单用的时doPost,用request.getParameter("next")没有办法get到next怎么办