JavaScriptServlet分析
一、介绍
前面已经 介绍了 listener 、 filter , 整个jar 包后端就剩下一个 JavaScriptServlet 了
二、JavaScriptServlet分析
JavaScriptServlet 继承了 HttpServlet 类, 主要重写了 init(), doGet() , doPost() 方法,下面主要分析这3个方法:
2.1 init() 方法
主要就是赋值, 并打印log
@Override
public void init(ServletConfig theServletConfig) {
servletConfig = theServletConfig;
//打印log
CsrfGuardServletContextListener.printConfigIfConfigured(servletConfig.getServletContext(),
"Printing properties after Javascript servlet, note, the javascript properties have now been initialized: ");
}
2.2 doGet() 方法
方法 doGet() 的主要流程就是:
- 先获取 请求 转过来的url , 并和 配置的url 进行正则匹配, 如果 不同,那就报错
- 接着 比较域名, 如果域名不同也报错, 这里的domain 设置的值, 从 context-parm -> config 配置-> default value 顺序获取
- 将 请求url 后缀 加到 白名单
- 返回 csrfguard.js 内容
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 获取来访者的地址
String refererHeader = request.getHeader("referer");
boolean hasError = false;
// 从配置文件里面获取 org.owasp.csrfguard.JavascriptServlet.refererPattern 对应的 来访者匹配的正则
Pattern javascriptRefererPattern = CsrfGuard.getInstance().getJavascriptRefererPattern();
// 如果 refererHeader 不为null 并且 和设置的不匹配,那就 打印log ,设置response 的状态为 404
if(refererHeader != null && !javascriptRefererPattern.matcher(refererHeader).matches()) {
CsrfGuard.getInstance().getLogger().log(LogLevel.Error, "Referer domain " + refererHeader + " does not match regex: " + javascriptRefererPattern.pattern());
response.sendError(404);
hasError = true;
}
// 如果 refererHeader 不为null,并且 需要匹配 domain
// 这里的domain 设置的值, 从 context-parm -> config 配置-> default value 顺序获取
if (refererHeader != null && CsrfGuard.getInstance().isJavascriptRefererMatchDomain()) {
//this is something like http://something.com/path or https://something.com/path
String url = request.getRequestURL().toString();
// 对两个url 从 第8位开始截取, 并比较,如果不相同那就 设置 状态404
String requestProtocolAndDomain = CsrfGuardUtils.httpProtocolAndDomain(url);
String refererProtocolAndDomain = CsrfGuardUtils.httpProtocolAndDomain(refererHeader);
if (!refererProtocolAndDomain.equals(requestProtocolAndDomain)) {
CsrfGuard.getInstance().getLogger().log(LogLevel.Error, "Referer domain " + refererHeader + " does not match request domain: " + url);
hasError = true;
response.sendError(404);
}
}
// 如果没有error , 获取 从 contextPath 开始的路径, 放到 javascriptUris里面 ,后面这里作为白名单
// csrfguard.js 文件内容
if (!hasError) {
//save this path so javascript is whitelisted
String javascriptPath = request.getContextPath() + request.getServletPath();
/**
理论上这里应该只有一个url ,但是不排除配置了多个的可能,所以javascriptUris 是一个Set 集合
**/
if (javascriptUris.size() < 100) {
javascriptUris.add(javascriptPath);
}
writeJavaScript(request, response);
}
}
2.3 writeJavaScript() 方法
方法writeJavaScript() 主要是 :
- 根据 rotate 或者 token-per-page 是否配置 ,进行设置 Header t头部信息
- 将 csrfguard.js 内容里面的 一些 替换到 ,比较 当前的 domain 等等
private void writeJavaScript(HttpServletRequest request, HttpServletResponse response) throws IOException {
HttpSession session = request.getSession(true);
CsrfGuard csrfGuard = CsrfGuard.getInstance();
/** cannot cache if rotate or token-per-page is enabled **/
if (csrfGuard.isRotateEnabled() || csrfGuard.isTokenPerPageEnabled()) {
response.setHeader("Cache-Control", "no-cache, no-store");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
} else {
response.setHeader("Cache-Control", CsrfGuard.getInstance().getJavascriptCacheControl());
}
response.setContentType("text/javascript");
/** build dynamic javascript **/
String code = CsrfGuard.getInstance().getJavascriptTemplateCode();
code = code.replace(TOKEN_NAME_IDENTIFIER, CsrfGuardUtils.defaultString(csrfGuard.getTokenName()));
code = code.replace(TOKEN_VALUE_IDENTIFIER, CsrfGuardUtils.defaultString((String) session.getAttribute(csrfGuard.getSessionKey())));
code = code.replace(INJECT_INTO_FORMS_IDENTIFIER, Boolean.toString(csrfGuard.isJavascriptInjectIntoForms()));
code = code.replace(INJECT_GET_FORMS_IDENTIFIER, Boolean.toString(csrfGuard.isJavascriptInjectGetForms()));
code = code.replace(INJECT_FORM_ATTRIBUTES_IDENTIFIER, Boolean.toString(csrfGuard.isJavascriptInjectFormAttributes()));
code = code.replace(INJECT_INTO_ATTRIBUTES_IDENTIFIER, Boolean.toString(csrfGuard.isJavascriptInjectIntoAttributes()));
code = code.replace(INJECT_INTO_XHR_IDENTIFIER, String.valueOf(csrfGuard.isAjaxEnabled()));
code = code.replace(TOKENS_PER_PAGE_IDENTIFIER, String.valueOf(csrfGuard.isTokenPerPageEnabled()));
code = code.replace(DOMAIN_ORIGIN_IDENTIFIER, CsrfGuardUtils.defaultString(parseDomain(request.getRequestURL())));
code = code.replace(DOMAIN_STRICT_IDENTIFIER, Boolean.toString(csrfGuard.isJavascriptDomainStrict()));
code = code.replace(CONTEXT_PATH_IDENTIFIER, CsrfGuardUtils.defaultString(request.getContextPath()));
code = code.replace(SERVLET_PATH_IDENTIFIER, CsrfGuardUtils.defaultString(request.getContextPath() + request.getServletPath()));
code = code.replace(X_REQUESTED_WITH_IDENTIFIER, CsrfGuardUtils.defaultString(csrfGuard.getJavascriptXrequestedWith()));
/** write dynamic javascript **/
OutputStream output = null;
PrintWriter writer = null;
try {
output = response.getOutputStream();
writer = new PrintWriter(output);
writer.write(code);
writer.flush();
} finally {
Writers.close(writer);
Streams.close(output);
}
}
2.4 doPost() 方法
doPost() 主要是校验用的, 主要逻辑是:
- 首先获取 头部信息 FETCH-CSRF-TOKEN ,判断是否有值,如果 有, 那就 进行 设置 tokenName + tokenValue 并返回
- 如果没有,判断是否开启了tokenPerPage ,如果不是, 报错404, 如果是, 设置对应的token 值
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
CsrfGuard csrfGuard = CsrfGuard.getInstance();
// 获取 头部里面的信息 FETCH-CSRF-TOKEN 对应的值,这个值是 通过 js 设置的,为1 说明只需要 token
// 如果为null,如果开启了tokenPerPage ,那就将所有的page -> token 对应关系拼接转为String 返回
String isFetchCsrfToken = request.getHeader("FETCH-CSRF-TOKEN");
// 如果都不为空, 设置 tokenName + tokenValue 并返回
if (csrfGuard != null && isFetchCsrfToken != null){
fetchCsrfToken(request, response);
} else {
// 如果 isFetchCsrfToken 为null, 判断是否是 tokenPerPage ,如果不是, 报错404, 如果是, 设置对应的token 值
if (csrfGuard != null && csrfGuard.isTokenPerPageEnabled()) {
writePageTokens(request, response);
} else {
response.sendError(404);
}
}
}
2.5 fetchCsrfToken() 方法
private void fetchCsrfToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 获取session
HttpSession session = request.getSession(true);
@SuppressWarnings("unchecked")
CsrfGuard csrfGuard = CsrfGuard.getInstance();
// 从 配置里面获取 tokenName
String token_name = csrfGuard.getTokenName();
// 从session里面获取token value
String token_value = (String) session.getAttribute(csrfGuard.getSessionKey());
// 进行name + token 拼接
String token_pair = token_name + ":" + token_value;
/** setup headers **/
response.setContentType("text/plain");
/** write dynamic javascript **/
OutputStream output = null;
PrintWriter writer = null;
// 放到response 里面返回
try {
output = response.getOutputStream();
writer = new PrintWriter(output);
writer.write(token_pair);
writer.flush();
} finally {
Writers.close(writer);
Streams.close(output);
}
}
2.6 writePageTokens() 方法
private void writePageTokens(HttpServletRequest request, HttpServletResponse response) throws IOException {
HttpSession session = request.getSession(true);
@SuppressWarnings("unchecked")
// 从 session 里面获取 pageTokenKey 的集合 Owasp_CsrfGuard_Pages_Tokens_Key
Map<String, String> pageTokens = (Map<String, String>) session.getAttribute(CsrfGuard.PAGE_TOKENS_KEY);
// 这里获取pageTokensString 的所有值
String pageTokensString = (pageTokens != null ? parsePageTokens(pageTokens) : Strings.EMPTY);
/** setup headers **/
response.setContentType("text/plain");
response.setContentLength(pageTokensString.length());
/** write dynamic javascript **/
OutputStream output = null;
PrintWriter writer = null;
// 返回
try {
output = response.getOutputStream();
writer = new PrintWriter(output);
writer.write(pageTokensString);
writer.flush();
} finally {
Writers.close(writer);
Streams.close(output);
}
}
三、小结
本章主要对 JavaScriptServlet里面的 doGet(),doPost() 以及涉及到的方法 进行了 分析, 相对还是比较简单的,下一章 分析一下最后一部分,也是 核心部分 csrfguard.js.