文档章节

APDPlat中的用户密码安全策略

杨尚川
 杨尚川
发布于 2014/03/11 13:32
字数 2331
阅读 726
收藏 16

互联网时代,安全是永恒的主题,威胁无处不在,哪怕是在企业内网。

 

APDPlat充分考虑到了安全的问题:

 

首先,在浏览器中对用户密码加入复杂字符({用户信息})之后进行加密(Secure Hash Algorithm,SHA-512,as defined in FIPS 180-2),在服务器端加入用户名和复杂字符之后再次加密,提高破解复杂度;

 

其次,在浏览器和服务器之间采用安全通道(HTTPS)传输用户信息,避免信息泄露。

 

再次安全易用相互矛盾,不同的应用需要的平衡点不一样,APDPlat充分考虑到了这个问题,提供了可配置的用户密码安全策略,以满足不同的需求。

 

下面详细介绍相关的设计和实现:

 

首先,在浏览器对用户的登陆密码进行加密,使用的sha512.js来源于http://pajhome.org.uk/crypt/md5/

 

j_password=hex_sha512(j_password+'{用户信息}');

 

在服务器端对新增用户修改密码重置密码时候的密码进行加密,使用类org.apdplat.module.security.service.PasswordEncoder的encode方法:

 

/**
 * 用户密码双重加密:
 * 1、使用SHA-512算法,salt为user.getMetaData(),即:用户信息
 * 2、使用SHA-256算法,salt为saltSource.getSalt(user),即:用户名+APDPlat应用级产品开发平台的作者是杨尚川,联系方式(邮件:)(QQ:281032878)
 * @author 杨尚川
 */
public class PasswordEncoder {
    public static String encode(String password,User user){
        password = new ShaPasswordEncoder(512).encodePassword(password,user.getMetaData());
        SaltSource saltSource = SpringContextUtils.getBean("saltSource");
        return new ShaPasswordEncoder(256).encodePassword(password,saltSource.getSalt(user));
    }
    public static void main(String[] args){
        User user = new User();
        user.setUsername("admin");
        user.setPassword("admin");
        String password = new ShaPasswordEncoder(512).encodePassword(user.getPassword(),user.getMetaData());
        System.out.println("Step 1 use SHA-512: "+password+" length:"+password.length());
        SaltSource saltSource = new APDPlatSaltSource();
        password = new ShaPasswordEncoder(256).encodePassword(password,saltSource.getSalt(user));
        System.out.println("Step 2 use SHA-256: "+password+" length:"+password.length());
    }
}

 

/**
 * 用户salt服务,salt为:
 * 用户名+APDPlat应用级产品开发平台的作者是杨尚川,联系方式(邮件:ysc@apdplat.org)(QQ:281032878)
 * @author 杨尚川
 */
@Service("saltSource")
public class APDPlatSaltSource implements SaltSource{
    @Override
    public Object getSalt(UserDetails user) {
        //变化的用户名+固定的字符串
        String text = user.getUsername()+"APDPlat应用级产品开发平台的作者是杨尚川,联系方式(邮件:ysc@apdplat.org)(QQ:281032878)";
        return text;
    }
}

 

用户在登陆的时候,浏览器进行一次加密,服务器进行二次加密,服务器加密的spring security配置如下:

 

<s:authentication-manager alias="authenticationManager">
	<s:authentication-provider   user-service-ref="userDetailsServiceImpl" >
		<s:password-encoder  hash="sha-256">
			<s:salt-source ref="saltSource"/>
		</s:password-encoder>
	</s:authentication-provider>
</s:authentication-manager>

  

其次,使用HTTPS安全通道,在配置文件config.local.properties中指定channel.type的值为https

 

#指定数据传输通道,可选值为http或https
channel.type=https
http.port=8080
https.port=8443

 

再次,提供了可配置的用户密码安全策略,在配置文件config.local.properties中指定user.password.strategy的值,可在安全易用之间进行平衡,这里的值为相应策略类的spring bean name:

 

#用户密码安全策略
user.password.strategy=passwordLengthStrategy;passwordComplexityStrategy

 

接下来看看跟用户密码安全策略相关的设计与实现:

 

用户密码安全策略接口

 

/**
 * 用户密码安全策略
 * @author 杨尚川
 */
public interface PasswordStrategy {
    /**
     * 检查用户的密码是否符合安全策略
     * @param password 用户密码
     * @throws PasswordInvalidException 不合法的原因包含在异常里面
     */
    public void check(String password) throws PasswordInvalidException;
}

 

APDPlat默认提供了2个安全策略,分别是密码长度安全策略密码复杂性安全策略

 

1、密码长度安全策略:

 

/**
 * 密码长度安全策略
 * 密码长度必须大于等于6
 * @author 杨尚川
 */
@Service
public class PasswordLengthStrategy implements PasswordStrategy{
    private static final APDPlatLogger LOG = APDPlatLoggerFactory.getAPDPlatLogger(PasswordLengthStrategy.class);

    @Override
    public void check(String password) throws PasswordInvalidException {
        if(StringUtils.isBlank(password) || password.length() < 6){
            String message = "密码长度必须大于等于6";
            LOG.error(message);
            throw new PasswordInvalidException(message);
        }
        LOG.info("密码符合安全策略");
    }
}

 

2、密码复杂性安全策略

 

/**
 * 密码复杂性安全策略:
 * 1、密码不能为空
 * 2、密码不能全是数字
 * 3、密码不能全是字符
 * @author 杨尚川
 */
@Service
public class PasswordComplexityStrategy implements PasswordStrategy{
    private static final APDPlatLogger LOG = APDPlatLoggerFactory.getAPDPlatLogger(PasswordComplexityStrategy.class);

    @Override
    public void check(String password) throws PasswordInvalidException {
        if(StringUtils.isBlank(password)){
            String message = "密码不能为空";
            LOG.error(message);
            throw new PasswordInvalidException(message);            
        }
        if(StringUtils.isNumeric(password)){
            String message = "密码不能全是数字";
            LOG.error(message);
            throw new PasswordInvalidException(message);            
        }
        if(StringUtils.isAlpha(password)){
            String message = "密码不能全是字符";
            LOG.error(message);
            throw new PasswordInvalidException(message);            
        }
        LOG.info("密码符合安全策略");
    }
}

 

有了不同的策略之后,还需要一个类来执行配置文件指定的策略

 

/**
 * 密码安全策略执行者
 * 根据配置项user.password.strategy
 * 指定的spring bean name
 * 分别执行指定的策略
 * @author 杨尚川
 */
@Service
public class PasswordStrategyExecuter implements PasswordStrategy, ApplicationListener {
    private static final APDPlatLogger LOG = APDPlatLoggerFactory.getAPDPlatLogger(ApplicationListener.class);
    private final List<PasswordStrategy> passwordStrategys = new LinkedList<>();

    @Override
    public void check(String password) throws PasswordInvalidException {
        for(PasswordStrategy passwordStrategy : passwordStrategys){
            passwordStrategy.check(password);
        }
    }
    
    @Override
    public void onApplicationEvent(ApplicationEvent event){
        if(event instanceof ContextRefreshedEvent){
            LOG.info("spring容器初始化完成,开始解析PasswordStrategy");
            String strategy = PropertyHolder.getProperty("user.password.strategy");
            if(StringUtils.isBlank(strategy)){
                LOG.info("未配置user.password.strategy");
                return;
            }
            LOG.info("user.password.strategy:"+strategy);
            String[] strategys = strategy.trim().split(";");
            for(String item : strategys){
                PasswordStrategy passwordStrategy = SpringContextUtils.getBean(item.trim());
                if(passwordStrategy != null){
                    passwordStrategys.add(passwordStrategy);
                    LOG.info("找到PasswordStrategy:"+passwordStrategy);
                }else{
                    LOG.info("未找到PasswordStrategy:"+passwordStrategy);
                }
            }
        }
    }
}

 

有了执行安全策略的服务之后,需要在新增用户修改密码重置密码的地方验证用户密码的安全用户服务类中进行验证APDPlat_Core/src/main/java/org/apdplat/module/security/service/UserService.java:

 

首先,注入密码安全策略执行者:

 

@Resource(name="passwordStrategyExecuter")
private PasswordStrategyExecuter passwordStrategyExecuter;

 

其次,在新增用户的时候验证密码是否符合安全策略:

 

//先对用户的密码策略进行验证
try{
	passwordStrategyExecuter.check(model.getPassword());
}catch(PasswordInvalidException e){
	throw new RuntimeException(e.getMessage());
}
LOG.debug("加密用户密码");
model.setPassword(PasswordEncoder.encode(model.getPassword(), model));

 

再次,在修改密码的时候验证密码是否符合安全策略:

 

for(Property property : properties){
	if("password".equals(property.getName().trim())){
		//先对用户的密码策略进行验证
		try{
			passwordStrategyExecuter.check(property.getValue().toString());
		}catch(PasswordInvalidException e){
			throw new RuntimeException(e.getMessage());
		}
		property.setValue(PasswordEncoder.encode(property.getValue().toString(),user));
		break;
	}
}

 

//先对用户的密码策略进行验证
try{
	passwordStrategyExecuter.check(newPassword);
}catch(PasswordInvalidException e){
	result.put("success", false);
	result.put("message", e.getMessage());
	LOG.error(e.getMessage());
	return result;
}            
oldPassword=PasswordEncoder.encode(oldPassword.trim(),user);
if(oldPassword.equals(user.getPassword())){
	user.setPassword(PasswordEncoder.encode(newPassword.trim(),user));
	serviceFacade.update(user);
	message = "修改成功";
	result.put("success", true);
	result.put("message", message);
	LOG.info(message);
}else{
	message = "修改失败,旧密码错误";
	result.put("success", false);
	result.put("message", message);
	LOG.error(message);
}

 

最后,在重置密码的时候验证密码是否符合安全策略:

 

//先对用户的密码策略进行验证
try{
	passwordStrategyExecuter.check(password);
}catch(PasswordInvalidException e){    
	LOG.error(e.getMessage());
	return e.getMessage();
}
int success = 0;
for(int id : ids){
	User user = serviceFacade.retrieve(User.class, id);
	if(user == null){
		LOG.error("ID为 "+id+" 的用户不存在,无法为其重置密码");
		continue;
	}
	if(PropertyHolder.getBooleanProperty("demo") && "admin".equals(user.getUsername())){
		LOG.error("演示版本不能重置admin用户的密码");
		continue;
	}
	//设置新密码
	user.setPassword(PasswordEncoder.encode(password, user));
	//同步到数据库
	serviceFacade.update(user);
	success++;
}

 

后记(说明APDPlat中密码安全策略的重要性): 

 

假设用户密码明文为:123456,我们使用http://pajhome.org.uk/crypt/md5/的MD5计算功能进行密文的计算,使用http://www.cmd5.com/的解密功能进行明文的计算。

1、明文123456计算得到密文为:e10adc3949ba59abbe56e057f20f883e,然后对密文进行解密,立马得到明文:123456

2、我们加大密码长度,在密码后面填充3个0,把密码变为123456000,计算得到密文为3bc2fbdd89ef79f3dbfbaf1f2132baa1,然后对密文进行解密,解密页面提示:已查到,这是一条付费记录,密文类型:md5。说明还是能解密。

3、我们加大密码复杂度,在每两个密码之间填充更多的复杂字符,把密码变为{~$(!APDPlat)1(是)2(一个)3(应用级产品)4(开发平台)5(帮助您)6(快速开发企业级应用程序)$~},计算得到密文为:59f1cade9738167f3b4070e29da5af2e,然后对密文进行解密,解密页面提示:未查到,已加入本站后台解密。要想破解这么复杂的密码不是容易的事,何况APDPlat采用了比MD5更安全的SHA-512加密算法。

 

从上面的分析可以看到,保证密码安全并不容易,为了防止用户密码在网络传输中被监听泄露,我们使用HTTPS安全通道,为了避免用户的明文密码在网络上传输,先在客户端进行了加密,当然了,在客户端的加密规则很容易就暴露了,我们需要在服务器端再进行一次加密,也就是把客户端加密过后的密文当做明文,这样最终的用户密码密文就很难再破解出来了。

 

总结一下,用户密码主要面临两方面的威胁:一是在用户登陆的时候,需要将密码提交给服务器以验证身份,用户的密码有可能在网络传输过程中被监听导致泄露,用户在输入密码的时候也有可能被旁边的人看到,用户输入的密码也有可能被计算机中的病毒木马窃取;二是存储在服务器端数据库中的用户密码有可能被网站工作人员或黑客获取到,如果存储的用户密码未加密而是明文存储,那么就相当危险了,就算是加密了,如果采用的算法有缺陷(王小云为首的研发团队破译了MD5和SHA-1)且密码过于简单,也有可能被破解。

 

参考资料:

1、http://www.cmd5.com/

2、http://pajhome.org.uk/crypt/md5/

3、http://zh.wikipedia.org/wiki/%E7%8E%8B%E5%B0%8F%E9%9B%B2

 

APDPlat托管在Github

 

© 著作权归作者所有

杨尚川

杨尚川

粉丝 1103
博文 220
码字总数 1624053
作品 12
东城
架构师
私信 提问
APDPlat如何自动建库建表并初始化数据?

APDPlat共支持10种数据库:DB2、DERBY、H2、HSQL、INFORMIX、MYSQL、ORACLE、POSTGRESQL、SQLSERVER、SYBASE。 数据库的默认配置信息在文件APDPlatCore/src/main/resources/org/apdplat/db.p...

杨尚川
2014/02/08
360
0
基于word分词提供的文本相似度算法来实现通用的网页相似度检测

实现代码:基于word分词提供的文本相似度算法来实现通用的网页相似度检测 运行结果: 检查的博文数:128 1、检查博文:192本软件著作用词分析(五)用词最复杂99级,相似度分值:Simple=0.96...

杨尚川
2015/05/28
1K
0
应用级产品开发平台 - APDPlat

APDPlat快速体验 APDPlat入门指南 APDPlat专题文章 APDPlat是Application Product Development Platform(应用级产品开发平台)的缩写。 APDPlat提供了应用容器、多模块架构、代码生成、安装...

杨尚川
2012/10/30
6.7K
0
APDPlat的系统启动和关闭流程剖析

APDPlat接管了Spring的启动关闭权,为各种运行其上的开源框架和类库的无缝集成提供了支持。 当然,大家都知道,一个JAVA EE Web应用的入口点是web.xml,APDPlat当然也不例外,我们看看APDPl...

杨尚川
2014/02/03
414
0
APDPlat拓展搜索之集成ElasticSearch

APDPlat充分利用Compass的OSEM和ORM integration特性,提供了简单易用且功能强大的内置搜索特性。 APDPlat的内置搜索,在设计简洁优雅的同时,还具备了强大的实时搜索能力,用户只需用注解的...

杨尚川
2014/02/01
292
2

没有更多内容

加载失败,请刷新页面

加载更多

CRM、DMP、CDP都是什么?有什么区别?

Markter对CRM系统(Customer Relationship Management System,客户关系管理系统),营销自动化等概念都已经比较熟悉,也许DMP(Data Management Platform,数据管理平台)也多多少少有些了解。...

怡海软件-CRM
10分钟前
3
0
中台是什么,到底要解决什么问题?

故事的开始 这个最早由阿里在2015年提出的“大中台,小前台”战略中延伸出来的概念,最近在国内大热。阿里、腾讯、百度、京东、美团、滴滴等一众互联网巨头,从去年到今年,接连开始组织架构...

喵二狸
21分钟前
2
0
Linux Centos 7 - MySQL 5.7离线安装

内部网络通过离线包的方式进行安装。 一、下载 下载地址:https://dev.mysql.com/downloads/mysql/ 进入页面后,点击右侧链接。 下载对应版本。 通过xftp6等工具上传到服务器上。 二、安装和...

华山猛男
21分钟前
2
0
EventBus 3 全解

EventBus 3 全解 [TOC] 使用 一个基于观察者模式的事件发布/订阅框架. 用于模块间通信和解耦, 使用方便,性能高. 基本使用 1. gradle导入依赖库 implementation 'org.greenrobot:eventbus:3....

马湖村第九后羿
24分钟前
3
0
HTTP 协议

什么是HTTP协议? HTTP是hypertext transport protocol的缩写,即超文本传输协议。 是用于万维网服务器与本地浏览器之间传输超文本的传送协议。可以使浏览器更加高效,使网络传输减少。能够保...

彩色泡泡糖
34分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部