就以前本人主持开发的金融产品所遇到的安全问题,设计部分请参见:http://www.cnblogs.com/shenliang123/p/3835072.htmljavascript
这里就部分web安全防御就简单的交流:html
(1)跨站脚本攻击(XSS):java
XSS攻击,一般指黑客经过“html注入” 篡改了网页,插入了恶意的脚本,从而在用户浏览网页的时候,控制用户浏览器的一种攻击。web
最多见的XSS攻击就是经过读取浏览器的Cookie对象,从而发起“cookie劫持”,当前用户的登陆凭证存储于服务器的session中,而在浏览器中是以cookie的形式进行存储的,cookie被劫持后,意味着攻击者能够不经过密码而直接登陆系统。咱们也能够直接在浏览器中输入脚本javascript:alert(document.cookie)来获取当前cookie值。ajax
目前防止“cookie劫持”的方法大体有:a. 输入检查,使用filter来过滤敏感的关键字;b. 将cookie与用户ip地址进行绑定;c. 为cookie植入HttpOnly标识。sql
本系统采用的是第3种方式:为cookie植入HttpOnly标识。一旦这个HttpOnly被设置,你在浏览器的 document对象中就看不到Cookie了,而浏览器在浏览的时候不受任何影响,由于Cookie会被放在浏览器头中发送出去(包括ajax的时 候),应用程序也通常不会在js里操做这些敏感Cookie的,对于一些敏感的Cookie咱们采用HttpOnly,对于一些须要在应用程序中用js操做的cookie咱们就不予设置,这样就保障了Cookie信息的安全也保证了应用。数据库
具体代码实现:在web服务器tomcat的配置文件server.xml中添加:apache
<Context docBase="E:\tomcat\apache-tomcat-6.0.24/webapps/netcredit" path="/netcredit" reloadable="false" useHttpOnly="true"/>
或者在项目的web.xml 配置以下:数组
<session-config> <cookie-config> <http-only>true</http-only> </cookie-config> </session-config>
图1-1 浏览器cookie截图浏览器
(2)跨站点请求伪造(CSRF):
本系统采用了对抗CSRF最有效的防护方法:验证码。
服务器端的安全相对于客户端来讲显的更加的重要,由于服务器端的某个安全漏洞可能带来的是致命的危险。所以咱们对服务器端的安全更为的慎重和重视。
(1)SQL注入攻击:
Sql注入的的两个关键条件:第一个是用户可以控制输入;第二个是本来程序要执行的代码,拼接了用户输入的数据。
根据上面两个关键条件,系统为防止sql注入使用了如下方法:
第一:使用预编译语句,这也是防护sql注入最有效的方法,彻底摒弃代码的直接拼接所带来的危险。
public List<T> findByPage(final String hql, final Object[] values, final int offset, final int pageSize) { List<T> list = getHibernateTemplate().executeFind(new HibernateCallback(){ public Object doInHibernate(Session session) throws HibernateException, SQLException{ Query query=session.createQuery(hql); for (int i = 0 ; i < values.length ; i++){ query.setParameter( i, values[i]); } if(!(offset==0 && pageSize==0)){ query.setFirstResult(offset).setMaxResults(pageSize); } List<T> result = query.list(); return result; } }); return list; }
第二:关闭web服务器的错误回显功能,这样能够防止攻击者对系统进行攻击后,经过回显的详细错误信息对攻击内容进行调整,对攻击者提供极大的便利。咱们在项目的web.xml文件中添加如下示例代码:
<error-page> <error-code>400</error-code> <location>/error400.jsp</location> </error-page>
第三:数据库自身使用最小权限原则,系统程序不使用最高权限的root对数据库进行链接,而是使用能知足系统需求的最小权限帐户进行数据库链接,并且多个数据库之间使用不一样的帐户,保证每一个数据库都有独立对应的帐户。
(2)文件上传漏洞:
文件上传漏洞是指用户上传了一个可执行的脚本文件,并经过脚本文件得到了执行服务器端命令的能力,这样将会致使严重的后果。而本系统内涉及到大量的图片格式文件上传,所以对于上传问题的处理很是谨慎,并尽量的达到安全标准。
本系统主要经过对上传文件详细的格式验证:
第一步:经过后缀名来简单判断文件的格式。
public static boolean isPicture(String pInput) throws Exception{ // 得到文件后缀名 String tmpName = pInput.substring(pInput.lastIndexOf(".") + 1, pInput.length()); // 声明图片后缀名数组 String imgeArray [] = { "jpg", "png", "jpeg" }; // 遍历名称数组 for(int i = 0; i<imgeArray.length;i++){ // 判断符合所有类型的场合 if(imgeArray [i].equals(tmpName.toLowerCase())){ return true; } } return false; }
第二步:经过读取文件的前两个字符进行对比,例如png格式图片的前两个字符为8950,而jpg格式的图片前两个字符为ffd8。
public static String bytesToHexString(byte[] src) { StringBuilder stringBuilder = new StringBuilder(); if (src == null || src.length <= 0) { return null; } for (int i = 0; i < src.length; i++) { int v = src[i] & 0xFF;//byte to int String hv = Integer.toHexString(v); if (hv.length() < 2) { stringBuilder.append(0); } stringBuilder.append(hv); } return stringBuilder.toString(); }
第三步:若是上传的为图片,则获取相应的高度和宽度,若是存在相应的宽度和高度则可认为上传的是图片。
public static boolean isImage(File imageFile) { if (!imageFile.exists()) { return false; } Image img = null; try { img = ImageIO.read(imageFile); if (img == null || img.getWidth(null) <= 0 || img.getHeight(null) <= 0) { return false; } return true; } catch (Exception e) { return false; } finally { img = null; } }
若是以上验证都成功经过,本系统在对文件进行存储时会将文件名进行重命名处理,而且设置相应的web服务器,默认不显示目录。由于文件上传若是须要执行代码,则须要用户可以访问到这个文件,所以使用随机数改写了文件名,将极大的增长攻击的成本,甚至根本没法成功实施攻击。
//对文件的名称进行重命名 StringBuilder sb = new StringBuilder().append(new Date().getTime()).append(".").append(fileMsg[1]);
(3)认证与会话管理:
认证明际上就是一个验证凭证的过程,所以咱们对登陆密码有着严格的规定:密码要求长度在8位以上而且包含字母,数字,符号两种以上的组合。并将密码加密后再进行数据库的存储。为防止一些暴力破解手段,又出于用户体验的考虑,本系统采用用户登陆时默认不进行验证码的输入,但密码三次输入失败后须要进行验证码的输入,连续五次输入错误后,该用户的ip地址将被封,能够在一段时间后自动解封或者后台管理员手动解封。涉及到系统资金有关的操做,例如投标,咱们还须要用户设置并提供支付密码,而提现等操做咱们还配备短信验证码功能。以此来加强验证的可靠性。
当用户登陆完成后,在服务器端会建立一个新的会话(Session),会话中保存着用户的状态和相关信息,服务器端维护全部在线用户的Session,而浏览器则是将SessionId加密后保存在cookie中,这里就出现了前面提到了“cookie劫持“问题,本系统经过将cookie中植入HttpOnly成功解决该问题。本系统还给Session设置了一个有效时间,来保证在有效时间后Session将自动销毁,以防止Session长链接所带来的安全隐患。在web.xml文件中添加如下代码:
<session-config> <session-timeout>30</session-timeout> </session-config>
(4)访问控制:
访问控制实际上就是创建用户和权限之间的对应关系,本系统采用的是“基于角色的访问控制”,根据相应功能模块的划分相应的权限,并将相应的权限分配给不一样的角色,再将角色分配给用户,用户就拥有了该角色所持有的权限。具体的设计与实现分析请见1.2 Annotation和Struts2拦截器实现基于角色的垂直权限管理。
如下是本系统的垂直权限管理模块的物理模型设计:
图1-2 权限管理模块物理模型
管理员与角色之间是多对多的关系,这样能够实现为一个管理员分配多个角色进行管理,而角色与权限之间也是多对多的关系,权限是根据功能模块进行划分的,使得角色能够进行细粒度的权限分配。
首先声明一个注解类,该注解类是自定义的,根据咱们须要的实现的功能进行相应注解的定义,使得在以后须要注释的方法上使用相应注解,代码以下:
@Retention(RetentionPolicy.RUNTIME) //注解的存活时间,整个运行时都存活 @Target(ElementType.METHOD) //此注解表示只能在方法上面有效 public @interface Permission { /** 模块名 **/ String model(); /** 权限值 **/ String privilegeValue(); /*用于区分当前页面是dialog仍是navTab*/ String targetType(); } 而后定义相应的拦截器类,并在struts2的配置文件struts.xml中进行拦截器的配置,为相应须要的类进行拦截验证,定义拦截器部分的核心代码以下: /** * 校验控制在方法上的拦截 */ @SuppressWarnings("unchecked") public boolean validate(Admin admin, ActionInvocation invocation, List<Role> roleList, Map session) { String methodName= "execute"; //定义默认的访问方法 Method currentMethod = null; methodName = invocation.getProxy().getMethod(); //经过actionProxy,获得当前正在执行的方法名 try { //利用反射,经过方法名称获得具体的方法 currentMethod = invocation.getProxy().getAction().getClass().getMethod(methodName, new Class[] {}); } catch (Exception e) { e.printStackTrace(); } if (currentMethod != null && currentMethod.isAnnotationPresent(Permission.class)) { //获得方法上的Permission注解 Permission permission = currentMethod.getAnnotation(Permission.class); //经过Permission注解构造出系统权限 Systemprivilege privilege = new Systemprivilege(new SystemprivilegeId(permission.privilegeValue(), permission.model())); session.put("targetType", permission.targetType()); //迭代用户所具备的具体权限,若是包含,返回true for (Role role : roleList) { RolePrivilege rolePrivilege = new RolePrivilege(privilege, role); if (role.getRolePrivileges().contains(rolePrivilege)) { return true; } } return false; } return true; }
本系统须要向权限表中添加相应的功能模块,为了有一个比较细的粒度,模块将根据功能进行划分,而权限则根据按钮来进行细分:
图1-3 数据库截图
在定义了相应的权限后,咱们须要在相应的执行方法体上面进行注释,使得拦截器进行拦截验证时能根据注释获取该执行方法体的模块和权限类型:
@Permission(model="recharge", privilegeValue="recharge" ,targetType="dialog")
首先添加相应的员工:
而后新增相应的角色,填写角色名称并勾取相应的权限:
而后新增相应的用户,勾选该用户相应的角色,能够选择多个角色,而且选择相应用户对应的员工: