Java中创建URL的常见问题及解决方案

URL无处不在,不过似乎开发人员并没有真正地理解它们,因为我在Stack Overflow上经常看到有人在问如何正确的创建一个URL。想知道URL语法是如何工作的,可以看下Lunatech的这篇文章,非常不错 。

本文不会深入介绍URL的全部语法(如果你想全面了解URL的话,可以读下RFC 3986, RFC 1738, 以及上面提到的那篇文章,还有W3上面的文档), 这里我想讲的是常见的一些库在操作URL方面存在的错误,以及如何通过URL-builder来正确的使用它,这是我们发布的一个用于正确地创建URL的Java库。

问题1:Java的URLEncoder
这个类不仅名字取的很差,而且它的文档上来第一句话就不太对头。

Utility class for HTML form encoding.
你可能正纳闷为什么叫URLEncoder呢,看到这行就彻底无语了。

如果你读过Lunatech的那篇博文,现在你应该明白了,你没法通过这个类将一个URL串奇迹般地转化成一个安全,正确编码的URL对象,当然如果你没做足功课的话,这里有个小例子可以帮助你理解下。

假设你有个HTTP的服务端点http://foo.com/search,它接受一个查询参数p,p的值就是要查找的字符串。如果你搜索"You & I"这个串的话,你第一次创建的搜索的URL可能是这样:http://foo.com/search?q=You & I。这个当然没法工作,因为&是分隔查询参数name/value对的分隔符。如果你拿到这个错乱的URL串的话,你对它简直束手无策,因为首先你就没法正确的解析它。

那好,我们来使用下URLEncoder。URLEncoder.encode(“You & I”, “UTF-8”)是结果是You+%26+I。这个%26解码之后就是&,而+号在查询串中代表的就是空格,因此这个URL是能正常工作的。

现在假设你想使用你的查询串来拼接URL路径,而不是放到URL参数里面。很明显,http://foo.com/search/You & I是错误的。不幸的是,URLEncoder.encode()的结果也是错的。http://foo.com/search/You+%26+I解码后会得到/search/You+&+I,因为+号在URL路径中是不会解析成空格的。

URLEncoder或许能满足你的一些场景。但不幸的是,它这个过于通用的名字使得开发人员很容易误用它。因此最好的方法就是不要使用它,免得后面别的开发人员在你的基础上又使用了别的功能时犯错(除非,你真的是在进行"HTML表单编码")。

问题2:Groovy HttpBuilder以及Java的URI
HTTP Builder是Groovy的一个HTTP客户端库。

创建一个普通的GET请求非常简单:

new HTTPBuilder(“http://localhost:18080”).request(Method.GET) {
uri.path = “/foo”
}
这段代码会发送GET /foo HTTP/1.1到服务端(你可以运行nc -l -p 18080之后再执行这段代码验证下)。

我们来试一下包含空格的URL。

new HTTPBuilder(“http://localhost:18080”).request(Method.GET) {
uri.path = “/foo bar”
}
这个发送的是GET /foo%20bar HTTP/1.1,看起来还不错。

现在假设我们的路径中有一段就叫做foo/bar。这可不能简单地发送foo/bar就完了,因为这会被认为成路径中包含两段,foo和bar,那我们试下foo%2Fbar吧(把/替换成对应的编码)。

new HTTPBuilder(‘http://localhost:18080’).request(Method.GET) {
uri.path = ‘/foo%2Fbar’
}
这个发送的则是GET /foo%252Fbar HTTP/1.1。这可不太妙。%2F中的%被重复编码了,这样解码后拿到的路径是foo%2Fbar而不是foo/bar。这里其实真正要怪的是java.net.URI,因为这个HTTPBuilder里的URIBuilder类用的就是它。

上述代码中的配置闭包中暴露的uri属性的类型是URIBuilder。如果你通过uri.path = …来更新uri的path属性的话,它最终会调用URI的一个构造方法,这个方法对于传入的path属性是这么描述的:

如果提供了path参数,则将它追加到URL后面。path里面的字符,只要不是非保留,标点,转义及其它分类(译注:这几个分类在RFC 2396中有详细说明)的字符,同时又不是/或者@号的,都会进行编码。
这个做法意义不大,因为如果未编码前的文本包含特殊字符的话,它就无法生成一个正确编码的路径分段。换句话说,“我会对这个字符串进行编码,而编码之后它就是正确的”,这当然是个谬论,而URI正好是这个谬论的牺牲品。如果字符串已经正确编码了,那就没什么问题,如果不是的话,那就完蛋了,因为这个串没法解析。事实上,文档里说的不会对/号转义的意思是,它假设path串已经正确地编码了(就是说正确地使用/来分隔路径),同时又还没有正确地编码(除了/外的其它部分仍然需要进行编码)。

如果HTTPBuilder不使用URI类的这个存在缺陷的功能就好了,当然了,如果URI自己本身没问题的话就更好了。

正确的做法
我们写了这个url-builder,它能帮助开发人员方便的拼接各种类型的URL。它遵循了篇首那几个参考资料中的编码规范,同时它还提供了流式的API。下面这个使用示例几乎可以涵盖所有的使用场景了:

UrlBuilder.forHost(“http”, “foo.com”)
.pathSegment(“with spaces”)
.pathSegments(“path”, “with”, “varArgs”)
.pathSegment("&=?/")
.queryParam(“fancy + name”, “fancy?=value”)
.matrixParam(“matrix”, “param?”)
.fragment("#?=")
.toUrlString()
结果是: http://foo.com/with%20spaces/path/with/varArgs/&=%3F%2F;matrix=param%3F?fancy%20%2B%20name=fancy?%3Dvalue#%23?=

这个例子演示了URL各个部分的不同的编码规则,比如说在路径中未编码的&=是允许的,而?/则是需要编码的,但在查询参数中=是需要编码的,但?号则不需要,因为这里已经是查询串的部分了(译注:查询串是从一个?号开始的,因此后面可以包含?号)。

更多示例请参考这里的测试用例以及UrlBuilder类。

原创文章转载请注明出处:Java中创建URL的常见问题及解决方案 英文原文链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: CSRF(Cross-Site Request Forgery)跨站请求伪造是一种常见的Web攻击方式,通过这种攻击方式,攻击者可以利用受害者的登录态发起一些恶意请求,例如在用户不知情的情况下转账、修改个人信息等。Java Web应用可以通过过滤器(Filter)来解决CSRF问题。 在Java Web应用,我们可以通过在Web.xml配置文件配置一个过滤器,该过滤器可以对所有请求进行过滤,并在请求添加CSRF Token信息。CSRF Token是一段随机生成的字符串,用于验证请求的合法性。 在Java,我们可以通过以下步骤来实现基于过滤器的CSRF防护: 1. 创建一个过滤器类,实现javax.servlet.Filter接口,重写doFilter方法,在该方法添加CSRF Token信息,并验证Token的合法性。 2. 在Web.xml配置文件配置该过滤器,并设置过滤器的拦截路径。 3. 在需要防护的请求添加CSRF Token信息,并在服务端验证Token的合法性。 通过以上步骤,我们可以在Java Web应用实现基于过滤器的CSRF防护,保障应用的安全性。 ### 回答2: CSRF(跨站请求伪造)是一种常见的Web应用程序安全漏洞,攻击者利用受害者已经在其他网站上进行过身份验证的事实,欺骗受害者在未经意识的情况下执行恶意操作。 Java通过过滤器可以有效地解决CSRF问题。 在Java,可以通过自定义过滤器来实现对CSRF攻击的防护。以下是一种可能的实现方式: 1. 在应用程序的web.xml文件配置过滤器,以便拦截所有请求。例如: ```xml <filter> <filter-name>CSRFFilter</filter-name> <filter-class>com.example.CSRFFilter</filter-class> </filter> <filter-mapping> <filter-name>CSRFFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ``` 2. 创建一个实现了javax.servlet.Filter接口的过滤器类。在过滤器,可以实现一些有效的CSRF防护措施,例如: - 在用户会话生成一个CSRF令牌,并将其存储在会话。 - 将CSRF令牌添加到每个表单或非GET请求的请求参数。 - 在每个响应将CSRF令牌作为Cookie发送到客户端。 ```java public class CSRFFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 检查CSRF令牌的有效性 String csrfToken = (String) httpRequest.getSession().getAttribute("csrfToken"); String requestCsrfToken = httpRequest.getParameter("csrfToken"); if (csrfToken != null && requestCsrfToken != null && csrfToken.equals(requestCsrfToken)) { // CSRF令牌有效,继续处理请求 chain.doFilter(request, response); } else { // CSRF令牌无效,返回错误页面或重定向到其他页面 httpResponse.sendRedirect("/error"); } } // 其他Filter接口方法的实现... } ``` 3. 在应用程序的表单或其他请求,将CSRF令牌添加到请求参数。例如,在生成HTML表单时,可以通过以下方式添加CSRF令牌: ```html <form action="/submit" method="post"> <input type="hidden" name="csrfToken" value="${sessionScope.csrfToken}" /> <!-- 其他表单字段... --> <input type="submit" value="提交" /> </form> ``` 通过上述步骤,当用户提交表单或其他非GET请求时,过滤器将检查CSRF令牌的有效性,如果令牌无效,则可以采取适当的措施,例如拒绝请求或重定向到错误页面,从而有效地防止CSRF攻击。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值