SSM架构商城项目(二)

2.2. 用户-注册-业务层

消息摘要

当计算机之间通信时,假设X计算机向Y计算机发送数据,中间可能经过多层路由等等中继设备,当Y计算机想要核实接收到的数据确实是X所发出的,而没有在中途被篡改,可行的做法是当X计算机发出数据的同时,使用某种摘要算法,对原数据进行运算,得到一条摘要数据,且将摘要数据也发送给Y计算机,而当Y计算机收到数据时,也使用相同的摘要算法,得到摘要数据,并且,和接收到的摘要数据进行对比,如果运算得到的摘要数据和接收到的摘要数据是一致的,则视为数据没有被篡改。

通常使用的摘要算法是某种哈希算法,常用的有SHA系列和MD系列的算法,这些算法的特征有:

  • 无论原数据有多长,得到的摘要数据的长度是固定的,以MD5为例,默认得到32位长度的十六进制数;

  • 对于同样的原数据,一定可以得到相同的摘要数据;

  • 对于不同的原数据,有可能会得到相同的摘要数据;

在密码的应用中,其实,就是把原密码进行摘要运算,把得到的摘要数据作为加密后的密码,当用户注册时,会把加密后的密码存储下来,后续,当需要验证密码时(例如登录、修改密码等等功能),会把用户后续输入的原密码使用同样的算法计算得到摘要数据,与数据库存储的摘要数据进行对比即可。

常见的摘要算法都是公开的(包括其运算步骤和运算公式),所以,单纯的只使用摘要算法对原数据进行摘要,实现密码的加密,也存在不安全因素,尽管例如MD5这种运算是不可逆的运算,但是,可以通过穷举方式,记录大量的“原始密码”与“使用MD5加密得到的密码”的对应关系,实现密码的伪破解!

为了进一步加强密码的安全性,通常,会使用“盐”,“盐”是某个字符串,可以由设计者自由的将它应用在密码的加密过程中,例如:

  • 将原密码和盐拼接,然后将拼接的结果进行加密;

  • 将原密码加密,得到结果A,然后将结果A与盐拼接,得到结果B,最终使用结果B作为最终存储的密码;

  • 将原密码加密,得到结果A,然后将盐加密,得到结果B,然后将A与B拼接,再将拼接的结果进行加密……

所以,盐的具体使用方式没有固定的约定,但是,只要用了盐,肯定可以增强密码的安全性,理论上来说:

  • 盐越复杂,密码的安全性越高,甚至可以为每个用户分配一个随机的盐,例如UUID值,UUID是根据时间、硬件信息等运算得到的36位长度的随机字符串;

  • 盐的使用方式(例如不是单纯的拼接,可能是打散后再拼接……)越复杂,密码的安全性越高;

  • 使用摘要算法加密的次数越多,密码的安全性越高。

在实际的应用中,Spring框架提供了DigestUtils工具类,其中的静态方法String md5DigestAsHex(byte[])可以将原密码的字节数据加密得到32位长度的十六进制字符串,在加密时,使用这个方法即可。

在项目中实现加密

当前项目中使用随机盐,所以,首先在t_user数据表中必须添加新的字段salt CHAR(36),用于存储UUID盐,由于数据表发生了变化,所以,实体类User也应该添加新的属性,并为新的属性添加SET/GET方法,然后,在UserMapper.xml文件中,无论是<insert>还是<select>,都应该添加对salt字段数据的处理。

然后,在UserServiceImpl业务层实现类中,在插入数据之前,必须完成密码的加密:

/**
 * 获取随机的盐值
 * @return 随机的盐值
 */
private String getRandomSalt() {
    return UUID.randomUUID()
            .toString().toUpperCase();
}

/**
 * 获取加密后的密码
 * @param src 原始密码
 * @param salt 盐
 * @return 加密后的密码
 * @see #md5(String)
 */
private String getEncrpytedPassword(
        String src, String salt) {
    // 将原密码加密
    String s1 = md5(src);
    // 将盐加密
    String s2 = md5(salt);
    // 将2次加密结果拼接,再加密
    String s3 = s1 + s2;
    String result = md5(s3);
    // 将以上结果再加密5次
    for (int i = 0; i < 5; i++) {
        result = md5(result);
    }
    // 返回
    return result;
}

/**
 * 使用MD5算法对数据进行加密
 * @param src 原文
 * @return 密文
 */
private String md5(String src) {
    return DigestUtils
            .md5DigestAsHex(src.getBytes())
                .toUpperCase();
}

public void insert(User user)
    throws InsertDataException {
    // 加密密码
    String salt = getRandomSalt();
    String md5Password
        = getEncrpytedPassword(
            user.getPassword(), salt);
    user.setSalt(salt);
    user.setPassword(md5Password);
    // ... 原有其它代码 ...
}

2.3. 用户-注册-控制器层

关于处理注册请求

首先,设计请求:

请求路径:/user/handle_reg.do
请求类型:POST
请求参数:username(*),password(*),phone,email,gender(def:1)
响应方式:ResponseBody
是否拦截:否

创建com.company.store.entity.ResponseResult类,用于表示响应结果,当控制器中的方法返回这个类的对象时,加上Jackson框架的处理,会返回这个类对应的JSON字符串!在这个类中,至少应该添加Integer state = 200;String message属性。

创建com.company.store.controller.BaseController,声明为抽象类,作为当前项目中所有控制器类的基类。

创建com.company.store.controller.UserController,继承自BaseController,添加@Controller@RequestMapping("/user")注解。

在类中添加业务层对象作为属性@Autowired private IUserService userService;

请检查spring-mvc.xml中组件扫描的包是否正确!

然后,在UserController中添加处理请求的方法:

@RequestMapping("/handle_reg.do")
@ResponseBody
public ResponseResult handleReg(
    @RequestParam("username") String username,
    @RequestParam("password") String password,
    String phone,
    String email,
    @RequestParam(value="gender", required=false, defaultValue="1") Integer gender) {
    // 将5个参数封装到User对象中

    // 调用业务层对象的User reg(User user)方法

    // 返回ResponseResult对象
}

由于业务层的许多方法都是会抛出异常来表示业务错误的,为了便于统一处理,所以,在BaseController中添加处理异常的方法:

@ExceptionHandler(ServiceException.class)
@ResponseBody
public ResponseResult handleException(Exception e) {
    // 判断异常的类型
    if (e instanceof UsernameConflictException) {
        return new ResponseResult(401, e);
    } else if (e instanceof InsertDataException) {
        return new ResponseResult(501, e);
    } else {
        return new ResponseResult(600, e);
    }
}

注:以上代码需要ResponseResult中自定义构造方法!

关于显示注册页面

2.4. 用户-注册-前端页面

将共享的pages.zip解压,然后将5个子级文件夹复制到项目的webapp下,即可直接访问相关html文件,例如:http://localhost:8080/TeduStore/web/register.html

接下来,应该编辑注册页面register.html,实现点击注册按钮后,能将用户输入的数据提交到服务端,并获取服务端返回的结果,进行处理!

$('#bt-register').click(function(){
    var url = "../user/handle_reg.do";
    var username = $("#uname").val();
    var password = $("#upwd").val();
    var phone = $("#phone").val();
    var email = $("#email").val();
    var data = "username=" + username 
            + "&password=" + password 
            + "&phone=" + phone 
            + "&email=" + email;
    $.ajax({
        "url": url,
        "data": data,
        "type": "post",
        "dataType": "json",
        "success": function(json) {
            if (json.state == 200) {
                alert("注册成功!");
            } else if (json.state == 401) {
                alert("注册失败!" + json.message);
            } else if (json.state == 501) {
                alert("严重错误!" + json.message);
            } else {
                alert("莫名其妙!!!");
            }
        }
    });
});

3. 用户-登录

3.1. 用户-登录-持久层

关于“登录”功能的处理,需要能够根据用户名查询用户信息,并且,查询到结果的话,结果中至少需要包括密码、盐、用户id、用户名……

该查询功能在此前完成“注册”时已经实现!所以,无须再开发!

3.2. 用户-登录-业务层

先创建UserNotFoundExceptionPasswordNotMatchException这2个异常类。

IUserService中声明抽象方法:

User login(String username, String password) throws UserNotFoundException, PasswordNotMatchException;

然后,在UserServiceImpl中实现以上方法:

public User login(String username, String password)  throws UserNotFoundException, PasswordNotMatchException {
    // 根据用户名查询用户信息
    // 判断是否查询到用户信息
    // 是:用户名有匹配的数据,即用户名正确,则获取查询结果中的盐
    // -- 对用户输入的密码执行加密
    // -- 判断以上加密密码与数据库的是否匹配
    // -- 是:登录成功,返回查询到的对象
    // -- 否:密码错误,抛出异常:PasswordNotMatchException
    // 否:没有与用户名匹配的数据,即用户名错误,抛出异常:UserNotFoundException
}

3.3. 用户-登录-控制器层

3.4. 用户-登录-前端页面

其它

1. 关于密码加密

加密规则是自定义的,使用哪种加密算法、如何使用盐、加密多少次,都可以自由设计。

2. 关于访问权限

在设计属性、方法时,都应该尽量使用更加严格的权限,也就是:能用private就不要用默认权限,能用默认权限就不要使用protected,能用protected就不要用public,如果一定要使用更加宽松的访问权限,应该是有必须要这样用的原因的!

3. 什么时候需要自行添加构造方法

  • 为了限制对象的创建过程,例如在某些设计模式中。

  • 为了快速创建对象,在创建过程中就为其中的某些属性直接赋值。

3. 哪些数据需要放在Session中

Session是占用服务器内存空间的,所以,能不放在Session中的,就不要放到Session中!

  • 登录后,在Session中存放用户的唯一标识,通常是用户的id;

  • 常用数据也可以放在Session中,例如许多页面都会显示当前登录的用户的用户名,则用户名也可以放在Session中;

  • 处理数据时所必须的数据,且不适合使用其它方式进行存储的。

猜你喜欢

转载自www.cnblogs.com/wood-life/p/10290746.html