Hjpestore 安全策略

8 Hjpetstore 安全策略

  • 不要将密码存成明文

  • 使用 Kaptcha

  • 使用 https (SSL 通道)

  • 密码算法及其参数的选择

  • SOA public api key

  • PCI (Payment Card Industry)

 

8.1 不要将密码存成明文

明文的密码,只要记下,写下或存储在计算机中,就不再是密码,它是明文。

企业应用中,密码在数据库需要采用 HASH 算法进行加密, 通常采用 MD5 加密算法。JAVA 通过 JCE 对密码领域提供全面支持

 

public static String md5(byte[] plainText) throws NoSuchAlgorithmException, UnsupportedEncodingException {

MessageDigest messageDigest = MessageDigest.getInstance("MD5 ");


String md5 = new BigInteger(1, messageDigest.digest(plainText)).toString(16);

if (md5.length() < 32) {

md5 = "0" + md5;

}


messageDigest.reset();


return md5;

} 
 

HASH 算法理论上保证不可逆性,即从一个 HASH 值,理论上很难,至少“很不容易”得到原文 。我们说”很不容易”,是因为黑客们会想尽一切可能的办法的,比如HASH 字典攻击:  他们将收集一个字典库来保存常用的密码的HASH 值。

另一个问题是,如果两个人使用相同的密码,如果皮皮鲁使用'haoboy5258' ,假设另外一个小伙也喜欢上这个密码,并且用在一个低安全性的系统。

'haoboy5258' HASH 值可以通过以下命令获得 (windows 的同学可以使用 online tools ):

pprun@pprun-laptop:~$ echo -n 'j2eej2ee' | md5sum
b6dcb0189da62b6b849903dcd57f84be - 
 

HASH 保证对于同一个输入一定会返回同一个输出,这也是HASH 的本意,用于保证内容的没有被串改 。

对于一个低安全的系统,黑客们很容易攻破并获得密码的HASH 值,有了它,通过查找HASH 字典,便可以得到相应的明文了。

这样一来打破了HASH 算法理论上保证不可逆性,即 通过 HASH 值不能知道明文。

为了使黑客更累些,通常的做法是在生成对应的 HASH 值时再带上一个该用户特有的属性,比如 create_time 或者,如果用户名是唯一的,可以用 username, 这个值就是我们通过所说的 salt , 就是在生成 hash 时,撒上把盐,呵呵。

 

public static String md5(byte[] plainText, byte[] salt) throws NoSuchAlgorithmException, UnsupportedEncodingException {

MessageDigest messageDigest = MessageDigest.getInstance("MD5");

messageDigest.update(salt);

String md5 = new BigInteger(1, messageDigest.digest(plainText)).toString(16);

if (md5.length() < 32) {

md5 = "0" + md5;

}


messageDigest.reset();


if (log.isDebugEnabled()) {

log.debug(new String(plainText, "UTF8") + "[salt=" + new String(salt, "UTF8") + "] 's MD5: " + md5);

}


return md5;

} 

 

这样一来,数据库中保存的是这个 md5 值作为密码,我们在用户 login 时比对的是 用这个值和用他们输入的用户和密码生成的 md5 值,如果相等,则是系统有效用户:

 

@Override

public User getUser(String username, String password) throws ServiceException {

String passwordMd5 = MessageDigestUtil.md5(password.getBytes("UTF8"), username.getBytes("UTF8"));


return this.userDao.getUser(username, passwordMd5);

} 

8.2 使用 Kaptcha

DoS ( Denial of Service) 恶劣的家伙们最常使用的方式之一, 注册,登录,搜索(可选)需要CAPTCHA( 全自动区分计算机和人类的图灵测试) 来应对这种无聊的攻击手段。

我们需要安全,但是我们不能丧失用户友好性。

使用开源的或者自制的 captcha 已经很常见了,我们希望这种captcha 不要把系统的真正用户惹烦了。

目前开源的大多数实现生成的 captcha 的可读性太差,以下所谓的TOP10 IT 公司他们的注册流程中采用的 captcha:

 



 

 

 

 

 

 

你能在10 秒内看清楚,并且猜对吗?我猜大部分人都有这样的经历,一遍,两遍,三遍... 总是“你所输入的字符与图片显示的不相符 …

还好 kaptcha 的作者意识到了这一点,以下是 kaptcha 生成的图片:

 


 

8.2.1 kaptcha 配置
8.2.1.1KaptchaServlet

web.xml 中配置 servlet

 

 <servlet>

<servlet-name>Kaptcha</servlet-name>

<servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>

<load-on-startup>2</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>Kaptcha</servlet-name>

<url-pattern>/kaptcha.jpg</url-pattern>

</servlet-mapping> 
8.2.1.2   KAPTCHA_SESSION_KEY

对于Spring MVC controller 可以以下代码进行Session 属性 KAPTCHA_SESSION_KEY 进行校验,

 

private boolean validateCaptcha(HttpServletRequest request) {

String kaptchaExpected = (String) request.getSession().getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);

//String kaptchaReceived = userForm.getKaptcha();

String kaptchaReceived = request.getParameter("kaptcha");


if (log.isDebugEnabled()) {

log.debug("Received kaptcha: '" + kaptchaReceived + "' is comparing with Expected kaptcha: '" + kaptchaExpected + "'...");

}


if (kaptchaReceived == null || !kaptchaReceived.equalsIgnoreCase(kaptchaExpected)) {

log.error("Received kaptcha: '" + kaptchaReceived + "' is comparing with Expected kaptcha: '" + kaptchaExpected + "'...");

return false;

}


return true;

} 

8.3 使用 https (SSL 通道)

用户敏感信息提交页面需要 https (SSL 通道) ,没有 ssl 的表单提交没有任何安全可言,如果你的系统在用户提交敏感信息时没有使用 https ,请立即file 一个 p0 bug

事实上采用 https 是非常简单的,因为没有那个 web 服务器不支持这一协议的。

  1. 在页面中hardcode https url 跳转,然后在处理完成后hardcode http url 跳回

  2. 采用 filter 或者 interceptor 将所有需要跳转的请求统一在同一个地方处理,然后,

  • 在整个session 中继续使用https

  • 或者在请求完成时 hardcode 跳转回 http url, 如果工作流不是很复杂的话,也同样可以使用 filter 或者 interceptor 完成返回的逻辑

 

public class HttpsUrlRewritingInterceptor extends HandlerInterceptorAdapter {


private int sslPort;


public void setSslPort(int sslPort) {

this.sslPort = sslPort;

}


@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

if ("https".equals(request.getScheme()) == false) {

// if it is already in https, bypass

StringBuilder sslUrl = new StringBuilder();

sslUrl.append("https://").append(request.getServerName()).append(":").append(sslPort).append(request.getRequestURI());


response.sendRedirect(sslUrl.toString());

}


return true;

}

} 

8.4  密码算法及其参数的选择

密码算法或其参数的选择本着够用就行,但要支持可扩展。科学是无止境的,同样,黑客利用技术的手段也一样。再有,过去可靠的算法,并不意味着目前或将来一定可靠。如同 王小云 破解 MD5 SHA-1 算法引起美国人恐慌一样。

密码本身是性能的杀手,是人类”自找麻烦“,就象我们给家里安防盗门,公司的配保险柜一样。

我们的选择应该本着够用就行,但一定要保留扩展的余地,这会涉及到

  • 密码算法

  • 算法参数

  • 数据库字段长度的定义

 

例如:目前 RSA 算法 key 的长度为 1024 已经足矣就会一般性的攻击了。

 

private static final String ALGORITHM = "RSA";

private static final int keyLength = 1024;


public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {

KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);

keyGen.initialize(keyLength);

KeyPair keyPair = keyGen.generateKeyPair();

return keyPair;

} 

8.5 SOA public api key

基于SOA 架构,如果 web service 面象的是大众,是一个 public API ,安全策略将是怎样呢?

SOA 公共API 的访问,用户需要通过注册获取一个 api  key ,然后 web serive 调用时作我为请求参数传入。 Google/Yahoo/Amazon public web service 的方式那样。

我们或许并不想因为这个“自找麻烦”的安全问题失去潜在的用户,但同时我们也要保证系统的安全以及可控性,比如非 VIP 用户, 每天 < 1000 requests

这个public api key ,根据需求,可以是 md5, sha-1( 或许这个不应该在考虑之列了,因为王小云的原因),sha-256, sha-512, 等等:

  • 用户提交一个注册请求

  • 系统返回一个 api key 给用户,可以通过 email 或者online 的方式

  • 用户在访问 web service 时,作为参数传入这个api key

 

hjpetstore 提供一个 REST 服务:通过关键字,查找宠物店里的出售的宠物列表:

 

@RequestMapping(value = "/products/{keyword}", method = RequestMethod.GET)

public ModelAndView getProductsByKeyword(

@RequestParam("apikey") String apiKey ,

@RequestParam("page") int page,

@RequestParam("max") int max,

@PathVariable("keyword") String keyword) {


if (log.isDebugEnabled()) {

log.debug("HjpetstoreController is processing request for keyword: " + keyword);

}

Products products = hjpetstoreService.searchProductList(apiKey, keyword, page, max);


ModelAndView mav = new ModelAndView("products");

mav.addObject(products);

return mav;

} 

 

@RequestParam("apikey") String apiKey 限定了 apiKey request parameter 必须存在(default) ,否则 Spring MVC 3 DispatcherServlet 根本不会 match 这个方法用来处理请求。

业务逻辑的处理,将检查这个唯一性的 api-key 是否对应系统中的一个用户:

 

 

 boolean isUserExistingForKey = userDao.isUserExistingForApiKey(apiKey);


if (isUserExistingForKey == false) {

throw new ServiceException("Your apikey is not valid");

}

... 

8.6  PCI (Payment Card Industry)

对于高安全系统,例如PCI (Payment Card Industry 支付卡行业数据安全标准) ,安全专家少不了。

如果你身处这样的一个高安全系统,首先恭喜你!

但同时,你要知道身上的责任,公司的信用是建立在系统的安全之上的。同时信用卡安全事故折射这一现实的世界 --对于这样的系统,安全不再是“自找麻烦”。反而有时要把自己当成“黑客”,通过写测试模拟各种攻击场景。

作为工程师,我们必须与安全专家紧密合作,不能有任何冒险的心理。此外,所有安全数据必须跟普通的数据隔离,否则将遭到 PCI 委员会的严厉处罚,一个月的处罚金足以 pay 一个安全顾问一年。

Hjpetstore 已经预备了这一方案,虽然对卡片及用户数据没有与普通数据分隔开,但是,SecurityService 设计成一个 REST 组件,可以单独部署。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值