电商平台搭建--用户功能模块开发(三)
欢迎大家来访,本篇博文会完成最后的用户模块功能
再来看一下这张图
经过前两篇博文的介绍,我们已经完成了登录、注册、获取用户登录信息、获取用户详细信息、退出登录等核心功能,那么在本篇博文中,我们将完成重置密码、修改个人信息。
一、用户模块-修改个人信息功能的实现
修改个人信息,我们需要从数据库中查询数据和修改存放在数据库中的用户数据,先来看Service层
// 更新用户信息
public ServerResponse<User> updateInformation(User user){
// username不能被更新
int resultCount = userMapper.checkEmailByUserId(user.getEmail(), user.getId());
if(resultCount > 0){
return ServerResponse.createByErrorMessage("email已经存在,请更换email再尝试");
}
User updateUserInfo = new User();
updateUserInfo.setId(user.getId());
updateUserInfo.setEmail(user.getEmail());
updateUserInfo.setPhone(user.getPhone());
updateUserInfo.setQuestion(user.getQuestion());
updateUserInfo.setAnswer(user.getAnswer());
int updateUserInfoCount = userMapper.updateByPrimaryKeySelective(updateUserInfo);
if(updateUserInfoCount > 0){
return ServerResponse.createBySuccess("更新个人信息成功", updateUserInfo);
}
return ServerResponse.createByErrorMessage("更新个人信息失败");
}
像往常一样,我们声明方法的返回类型为User并传入user对象。在更新用户信息的时候应该首先判断用户邮箱是否已经存在,这时候还要注意,用户的用户名是不能被更新的(至少当前项目是这样)。userMapper的checkEmailByUserId方法会根据用户的id来查询对应的email,如果email不存在便更新用户信息。这里更新用户信息主要用的就是JavaBean,在J2EE中叫做POJO层,不管叫什么,其实就是JavaBean。使用JavaBean首先要new出一个JavaBean的实例,然后通过setter方法分别更新用户信息的相应字段值,更新完毕以后,我们需要将这些信息插入到数据库中,于是我们使用userMapper的updateByPrimaryKeySelective方法进行数据插入更新,这个方法是MyBatis自动生成的方法,我们只需要向方法里面传递需要更新信息的参数即可,值得一提的是,使用这个方法更新不会出现记录更新错误的情况,因为该方法只更新选中的记录,不会更新相同的其他用户的记录。最后就是校验数据是否插入成功,如果updateUserInfoCount > 0,则更新成功,返回用户更新以后的新的信息,否则更新失败。
再来看Controller层
/**
* 更新个人用户信息
* @param user
* @param session
* @return
*/
@RequestMapping(value = "update_information.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<User> updateInformation(User user, HttpSession session){
User currentUser = (User) session.getAttribute(Const.CURRENT_USER);
if(currentUser == null){
return ServerResponse.createByErrorMessage("用户未登录");
}
user.setId(currentUser.getId());
user.setUsername(currentUser.getUsername());
ServerResponse<User> updateUserInfo = iUserService.updateInformation(user);
if(updateUserInfo.isSuccess()){
updateUserInfo.getData().setUsername(currentUser.getUsername());
session.setAttribute(Const.CURRENT_USER, updateUserInfo.getData());
}
return updateUserInfo;
}
更新用户信息这一操作应该发生在已经登录成功的用户中,所以在更新之前应该首先判断用户是否处于登录状态,也就是session中是否存储了当前用户的信息。和Service层一样,如果用户登录了,那么我们就使用user对象进行id的更新,同时用户名不能被更新,为了保证用户名的准确性,我们应该再重新更新一下当前用户名。更新完毕以后,调用Service中我们写好的updateInformation方法,然后通过if判断是否更新成功。如果该请求成功执行,则更新用户修改个人信息的最后时间以及设置session中保存的用户信息,至此,修改用户信息就实现完成了。
二、用户模块-修改密码功能的实现
修改密码是用户模块中最复杂的功能,想要实现一个安全系数高的功能,我们需要知道在开发中经常碰见的安全问题。第一个是横向越权,指用户在同级别权限中,通过使用数据分析工具,登录自己的账号来访问其他用户信息的现象;第二个是纵向越权,指用户通过一定的工具对我们写的接口进行分析,通过分析结果使普通用户的权限提升至管理员范围。不论什么样的漏洞,这对我们的项目都是不利的。
一般的修改密码功能,无论是记住密码还是忘记密码,都会给用户传递一个重置密码的问题和其对应的重置密码问题答案,只有当问题和答案匹配时,才能进行修改密码的操作。往往会在注册的时候当做用户的必填项要求用户进行填写,如果不填写此项用户也可以在个人中心修改个人信息的时候将重置密码问题和重置密码问题答案进行填入。对于横向越权和纵向越权,解决方案是在编写SQL的时候加上用户的id,这样一来不管攻击者如何分析,分析出来的只有当前id下的信息,就解决了横向越权和纵向越权的漏洞。在重置密码的时候,还有一个问题值得考虑:当用户需要重置密码的时候,不管是哪种方式的重置密码都不可能一直等待着用户修改,如果在用户点击修改密码的时候恰好有事出去了,这个时候就不能一直等待着用户,所以要给重置密码加一个有效期Token令牌,来判断当前用户是否在操作。具体如何实现,我们看代码
(1)、忘记密码的重置密码
Service层
// 忘记密码的重置密码
public ServerResponse<String> forgetResetPassword(String username, String passwordNew, String forgetToken){
if(StringUtils.isBlank(forgetToken)){
return ServerResponse.createByErrorMessage("参数错误,token需要传递");
}
ServerResponse<String> validResponse = this.checkValid(username, Const.USERNAME);
if(validResponse.isSuccess()){
return ServerResponse.createByErrorMessage("用户不存在");
}
String token = TokenCache.getKey(TokenCache.TOKEN_PREFIX+username);
if(StringUtils.isBlank(token)){
return ServerResponse.createByErrorMessage("token无效或已过期");
}
if(StringUtils.equals(forgetToken, token)){
String md5Password = MD5Util.MD5EncodeUtf8(passwordNew);
int resultCount = userMapper.updatePasswordByUsername(username, md5Password);
if(resultCount > 0){
return ServerResponse.createBySuceessMessage("密码修改成功");
}
}else{
return ServerResponse.createByErrorMessage("token错误,请重新获取重置密码的token");
}
return ServerResponse.createBySuceessMessage("密码修改失败");
}
这里面需要传递一个token,把token的有效期设置为30分钟或者其它,表示当过了30分钟以后token过期,此时应该重新获取重置密码的token,防止恶意修改。
再来看获取重置密码问题的Service层
// 获取修改密码的问题
public ServerResponse<String> forgetGetQuestion(String username){
ServerResponse<String> validResponse = this.checkValid(username, Const.USERNAME);
if(validResponse.isSuccess()){
return ServerResponse.createByErrorMessage("用户名不存在");
}
String question = userMapper.selectQuestionByUsername(username);
if(StringUtils.isNotBlank(question)){
return ServerResponse.createBySuccess(question);
}
return ServerResponse.createByErrorMessage("找回密码的问题是空的");
}
这个里面通过userMapper的selectQuestionByUsername方法来解决横向越权问题,这个方法会根据当前登录的用户名从数据库中查找对应的问题,防止其它用户恶意获取当前用户的问题。
接着看获取重置密码问题答案的Service层
// 获取修改密码问题的答案
public ServerResponse<String> forgetGetAnswer(String username, String password, String answer){
int resultCount = userMapper.checkAnswer(username, password, answer);
if(resultCount > 0){
String forgetToken = UUID.randomUUID().toString();
TokenCache.setKey(TokenCache.TOKEN_PREFIX+username, forgetToken);
return ServerResponse.createBySuccess(forgetToken);
}
return ServerResponse.createByErrorMessage("问题的答案错误");
}
该方法会传递一个token给前台,这也是解决横向越权的一方面。这里我们注意userMapper的checkAnswer方法,该方法会绑定三个参数,这也是为了防止纵向越权。因为网站管理员也有密码提示问题和答案,这时候应该绑定该问题是当前登录用户的并且问题的答案与之相对应才可以,否则不让前端修改。
通过观察这三个Service层,不难发现,在逻辑执行开始,都需要验证是否存在该用户,这也是防止SQL注入一种方式,大家注意。
再来看忘记密码的重置密码的Controller层
/**
* 忘记密码的重置密码
* @param username
* @param passwordNew
* @param forgetToken
* @return
*/
@RequestMapping(value = "forget_reset_password.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<String> forgetResetPassword(String username, String passwordNew, String forgetToken){
return iUserService.forgetResetPassword(username, passwordNew, forgetToken);
}
这里面只需要重置密码就可以,在Service层里已经处理完逻辑,所以在Controller中直接返回处理结果即可。
(2)、记住密码的重置密码
先来看Service层
// 记住密码的重置密码
public ServerResponse<String> resetPassword(String passwordOld, String passwordNew, User user){
// 防止横向越权,保证该密码是对应该用户的(问题)
int resultCount = userMapper.checkPassword(MD5Util.MD5EncodeUtf8(passwordOld), user.getId());
if(resultCount == 0){
return ServerResponse.createByErrorMessage("旧密码错误");
}
user.setPassword(MD5Util.MD5EncodeUtf8(passwordNew));
int updateCount = userMapper.updateByPrimaryKeySelective(user);
if(updateCount > 0){
return ServerResponse.createBySuceessMessage("密码更新成功");
}
return ServerResponse.createByErrorMessage("密码更新失败");
}
因为用户记住了当前密码,且是登录状态,所以在修改密码之前先验证用户记住的密码是否正确,如果正确在进行密码的修改。在验证旧密码是否正确的时候,应该通过用户的id去查询,这样就能保证旧密码是对应当前用户的,同时也是为了防止横向越权现象的发生。
再来看Controller层
/**
* 记住密码的重置密码
* @param passwordOld
* @param passwordNew
* @param session
* @return
*/
@RequestMapping(value = "reset_password.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<String> resetPassword(String passwordOld, String passwordNew, HttpSession session){
User user = (User)session.getAttribute(Const.CURRENT_USER);
if(user == null){
return ServerResponse.createByErrorMessage("用户未登录");
}
return iUserService.resetPassword(passwordOld, passwordNew, user);
}
首先应该做的,就是判断用户是否处于登录状态。如果处于登录状态直接返回在Service层中的处理结果,否则提示用户未登录,强制登录。
写到这里,用户模块的所有功能就全部实现完毕了。
在这里,给大家推荐一个方便的接口测试工具,谷歌的RestletClient插件。网上有很多丰富的教程,在这里就不一一介绍了。
用户模块也是项目的第一大模块,所以我们封装了很多在项目中通用的组件,这也是为了方便我们项目的开发。在后期开发中,我会加快开发速度,希望大家跟上。
再次祝大家新年快了,希望大家关注我,博文还会持续更新,Fighting!
上一篇: win7改win10
下一篇: span宽度的设置