关于web程序中的安全方面,想必大多数人都不甚了解,或者说感觉没有必要了解,身边开发网站的人主要就是注重后台的功能和前台的界面,不要说程序的安全问题,甚至后台数据库访问的问题可能都没有下大力气解决。但是这又是和我们密切相关的一个问题,每天看到网站哪个系统或者网站又出现安全问题都感觉离自己很遥远,其实这只是一个错觉,还是那句话——人生苦短,注意安全(某些人不要理解错了,说的就是你。。)。写这篇文章的时候,恰好想起来本屌丝考大学报志愿的时候,那时候北邮新开了一门专业叫信息安全,那个年代还不是很火,但是凭借本屌丝敏锐的洞察力(其实是情怀啦)一眼就看出来了该专业的前景,但是遗憾的是刚刚开办,还不招生。。。遗憾啊!!
保护我们的web程序可以通过声明和编程两种方式来完成,但是不管是哪种方式,都要满足web安全性的这4个方面:
在配置tomcat的用户和角色时要注意,每次重启tomcat的时候,你在之前配置的用户和角色都会消失导致恢复到tomcat的默认状态,这是因为在eclipse中初次配置tomcat服务器时,eclipse会将tomcat的配置文件拷贝到自己的workspace下的server文件夹,每次启动读取的配置文件都是从这里读取的,而且还会用这里的配置覆盖tomcat目录下的配置文件,所以为了避免出现这种麻烦,我们将配置好的文件拷贝到workspace下的server文件夹一份即可,如下:
HttpServlet
/myHttpServlet
GET
POST
admin
关于上述几个元素,解释如下:
BASIC
Admin only
使用这种Http验证方式,是将用户名和密码按照"用户名:密码"这种形式组合并且使用Base64算法进行编码传输到服务器,这种算法很弱,在网上随便找一个解码的网站都能知道你的用户名和密码。下面实现出现验证错误和没有授权时的截图:
DIGEST
Admin only
使用这种方式的请求和响应如下:
FORM
Admin only
/login.jsp
/error.jsp
<%@ page language="java" contentType="text/html; charset=GBK"
pageEncoding="GBk"%>
Login
在表单中,注意
action是j_security_check,用户名是j_username,密码是j_password,这三个字段都是由servlet容器来实现的,这里使用的是tomcat,在tomcat中处理这部分的类是org.apache.catalina.authenticator.FormAuthenticator,对应的代码在authenticate()方法中,如下:
boolean loginAction = (requestURI.startsWith(contextPath)) && (requestURI.endsWith("/j_security_check"));
if (!loginAction)
{
if ((request.getServletPath().length() == 0) && (request.getPathInfo() == null))
{
StringBuilder location = new StringBuilder(requestURI);
location.append('/');
if (request.getQueryString() != null)
{
location.append('?');
location.append(request.getQueryString());
}
response.sendRedirect(response.encodeRedirectURL(location.toString()));
return false;
}
session = request.getSessionInternal(true);
if (log.isDebugEnabled()) {
log.debug("Save request in session '" + session.getIdInternal() + "'");
}
try
{
saveRequest(request, session);
}
catch (IOException ioe)
{
log.debug("Request body too big to save during authentication");
response.sendError(403, sm.getString("authenticator.requestBodyTooBig"));
return false;
}
forwardToLoginPage(request, response, config);
return false;
}
request.getResponse().sendAcknowledgement();
Realm realm = this.context.getRealm();
if (this.characterEncoding != null) {
request.setCharacterEncoding(this.characterEncoding);
}
String username = request.getParameter("j_username");
String password = request.getParameter("j_password");
if (log.isDebugEnabled()) {
log.debug("Authenticating username '" + username + "'");
}
principal = realm.authenticate(username, password);
if (principal == null)
{
forwardToErrorPage(request, response, config);
return false;
}
if (log.isDebugEnabled()) {
log.debug("Authentication of '" + username + "' was successful");
}
if (session == null) {
session = request.getSessionInternal(false);
}
if (session == null)
{
if (this.containerLog.isDebugEnabled()) {
this.containerLog.debug("User took so long to log on the session expired");
}
if (this.landingPage == null)
{
response.sendError(408, sm.getString("authenticator.sessionExpired"));
}
else
{
String uri = request.getContextPath() + this.landingPage;
SavedRequest saved = new SavedRequest();
saved.setMethod("GET");
saved.setRequestURI(uri);
saved.setDecodedRequestURI(uri);
request.getSessionInternal(true).setNote("org.apache.catalina.authenticator.REQUEST", saved);
response.sendRedirect(response.encodeRedirectURL(uri));
}
return false;
}
@WebServlet(name="securityServlet", urlPatterns={"/securityServlet"})
@ServletSecurity(value=@HttpConstraint(rolesAllowed="user"),
httpMethodConstraints={@HttpMethodConstraint(value="GET", rolesAllowed="admin")})
public class SecurityServlet extends HttpServlet {}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if(req.authenticate(resp)){
System.out.println("success");
}
else {
System.out.println("fail");
}
System.out.println("AuthType: " + req.getAuthType());
System.out.println("RemoteUser: " + req.getRemoteUser());
System.out.println("isUserInRole: " + req.isUserInRole("admin"));
System.out.println("UserPrincipal: " + req.getUserPrincipal());
}
这里使用表单验证方式,所以使用的表单是我们自定义的。