前言
对于网站来说,用户能否访问一个页面取决于TA拥有的权限,比如收藏等页面,就需要用户已经登陆,再比如删除或修改上传的图片,一定是只能删除自己上传的,不能是别人的,等等。前面一种情况,我最开始是在每个需要登陆的页面都加上一段验证代码,不断地复制粘贴,修改起来也很麻烦,直到我学了Filter,然后把项目重构了一下,从整个web项目的层面进行了一次过滤验证。至于后面一种情况,我就根本没有验证,虽然说仅仅通过页面上的元素来操作不会出现这种情况,但是如果是真实的网站,有些恶意用户会直接通过url来访问后端进行一些数据库的操作,就算是采用POST方法,在审查元素的情况下,大家都可以随机更改一部分页面的代码,注入恶意JS,导致一些不应该的操作,比如将穿给后端的uid改成别人的uid从而删除别人的数据。因此即使通过了一层鉴定,在服务器端的方法层面也应该再进行一次权限鉴定。
实现
1. 项目层面
利用Filter接口对访问进行过滤,可以有多个过滤器,即filterChain,按照在配置文件中的顺序依次进行过滤处理,直到最后到达url对应的文件。
- web.xml文件中的配置
<filter>
<filter-name>loginFilter</filter-name>
<filter-class>filter.LoginFilter</filter-class>
</filter>
<!--需要登陆的页面的url-->
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/collection.jsp</url-pattern>
</filter-mapping>
- 实现HttpServlet,用来给其它具体的filter继承,就不用每次都写init的destroy这些方法了
package filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public abstract class HttpFilter implements Filter {
/**
* 用于保存 FilterConfig 对象.
*/
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
init();
}
/**
* 供子类继承的初始化方法. 可以通过 getFilterConfig() 获取 FilterConfig 对象.
*/
protected void init() {}
/**
* 直接返回 init(ServletConfig) 的 FilterConfig 对象
*/
public FilterConfig getFilterConfig() {
return filterConfig;
}
@overrride
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
doFilter(request, response, chain);
}
/**
* 抽象方法, 为 Http 请求定制. 必须实现的方法.
* @param request
* @param response
* @param filterChain
* @throws IOException
* @throws ServletException
*/
public abstract void doFilter(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws IOException, ServletException;
/**
* 空的 destroy 方法。
*/
@Override
public void destroy() {}
}
- LoginFilter
package filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class LoginFilter extends HttpFilter {
@Override
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpSession session = request.getSession();
String toPath = request.getRequestURI();
if(session.getAttribute("userDetails") == null){
System.out.println("没有到"+toPath+"的权限,要先登录");
session.setAttribute("toPath",toPath);
response.sendRedirect(request.getContextPath()+"/login.jsp");
}
else {
filterChain.doFilter(request,response);
}
}
}
2. 方法层面
① 所需参数
每个servlet都需要一定的参数,若直接通过url访问,可能会没有所需参数,导致异常,所以在每个servlet的方法开头都要先保证参数的完整,若不完整则直接跳转并返回。
比如注册的servlet,
String username = request.getParameter("username");
String password = request.getParameter("md5Password");
String email = request.getParameter("email");
if(!Require.requireStringNotEmpty(username,password,email)){
response.sendRedirect(request.getContextPath()+"/register.jsp");
return;
}
public static boolean requireStringNotEmpty(String ... strings){
for(String string :strings){
if(string == null || string.length() == 0) return false;
}
return true;
}
这个方法的实现采用了动态参数
② 身份验证
刚开始我都是从前端传uid给后端来决定哪一名用户在操作,但是这很不安全,所以我就在后端验证传过来的uid是否是session中储存的uid即当前登录的uid,但是写完之后我才意识到,其实更好的办法是直接在后端通过session获取uid,不要从前端传给后端,因为其实前端的uid在正常情况下就是通过session来获取的,在非正常情况下更是没有意义的。比如说,收藏某张图片,如果从后端获取uid,就根本不用验证,只有在少数情况下,比如删除某张上传的图片,还需要判断当前uid是否是该图片的作者。
总而言之,在后端开发中,需要的信息就是能在后端获取就不要靠前端传回来,因为前端的数据是可能被修改的。
当然,在前后端分离的前端开发中,能从前端获取的数据,就不要再请求一次后端,这样效率很低,比如vue+springboot项目中,登录过后就把用户的一些信息存在全局状态中。