邮箱激活
背景:几乎每个网站或论坛之类的用户注册后都需要通过发送邮件到邮箱激活用户。
设计:
激活步骤:
-
发送激活邮件
-
用户查收邮件
-
用户点击链接,跳转至成功页面(修改激活状态),激活成功。
-
加密能防止伪造攻击,一次url只能验证一次,并且绑定了用户。生成url: 可以用UUID生成随机密钥。
数字签名 = MD5(用户名+'′+过期时间+‘’+密钥key)
数据库字段(用户名(主键),密钥key,过期时间)
url参数(用户名,数字签名) ,密钥key的生成:在每一个用户找回密码时候为这个用户生成一个密钥key ,
详细步骤
点击找回密码按钮 > 跳到找回密码页面 > 输入手机号或者邮箱找回 > 后台验证根据邮箱或者密码是否能在数据库中找到对应的user(信息),如果找不到,证明手机号或者邮箱伪造的,如果找得到 > 把生成的uuid 作为token(证明本次用户的唯一标示)当成键user做为值放入缓存中,然后拼接链接,同时放入要发送的邮箱中, http://localhost:8080/foundpassword?token=f3a29a4b795a4f77be98ea931cf9884d 发送邮箱 > 当用户点击邮箱链接的时候,后台获取token, 首先判断token 是否为空,如果存在去缓存中获取对应的user 防止伪造token,
如果都没有问题,请求转发到重置密码页面把token放到表单隐藏域中,当用户提交表单的时候,对比两次密码是否一致,然后再次验证toke是否伪造,如果都是ok的话,根据token值删除缓存,同时更新用户密码,然后重定向到登陆页面.
代码
package com.kaishengit.service.impl;
/**
* Created by wanggs on 2017/9/26.
*/
@Service
public class UserServiceImpl implements UserService {
private static Logger logger = (Logger) LoggerFactory.getLogger(UserService.class);
@Autowired
private SimpleCache simpleCache;
// 从配置文件中获取盐值
@Value("${user.password.salt}")
private String salt;
@Value("${email.smpt}")
protected String smpt;
@Value("${email.port}")
public String port;
@Value("${email.username}")
private String username;
@Value("${email.password}")
private String password;
@Value("${email.frommail}")
private String formmail;
@Autowired
private UserMapper userMapper;
// 发送激活用户账号的邮件,写进去时间,如果没人访问就过期,有人访问就延期时间
private static Cache<String, String> cache = CacheBuilder
.newBuilder()
.expireAfterWrite(6, TimeUnit.HOURS)
.build();
// 找回密码token
private static Cache<String, String> passwordCache = CacheBuilder.newBuilder()
.expireAfterWrite(30, TimeUnit.MINUTES)
.build();
// 限制操作pinlv
private static Cache<String, String> activeCache = CacheBuilder.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS)
.build();
@Override
public User findByName(String username) {
return userMapper.findByUsername(username);
}
@Override
public User findByEmail(String email) {
return userMapper.finByEmail(email);
}
/**
* 注册用户
*
* @param user
* @throws UserExistException
*/
@Override
public void saveNewUser(User user) throws UserExistException {
User u = userMapper.findByUsername(user.getUsername());
if (u != null) {
throw new UserExistException("用户名已经存在");
}
user.setPassword(DigestUtils.md5Hex(user.getPassword() + salt));
user.setState(User.USERSTATE_UNACTIVE);
user.setAvatar(User.DEFAULT_AVATAR_NAME);
logger.info("盐值:{}" + salt);
logger.info("加密后密码是: {}", user.getPassword());
userMapper.saveNewUser(user);
// 创建线程发激活邮件
getThread(user);
}
/**
* 使用线程发送激活邮件
*
* @param user
*/
private void getThread(User user) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 发送激活邮件
String uuid = UUID.randomUUID().toString().replace("-", "");
String url = "http://localhost/user/active?_=" + uuid;
// 放入缓存等待6小时
cache.put(uuid, user.getUsername());
String html = "<h3>Dear " + user.getUsername() + ":</h3>请点击<a href='" + url + "'>该链接</a>去激活你的账号. <br> 凯盛软件";
Email email = getEmail(smpt, port, username, password, formmail);
logger.info("邮箱:{}" + email);
EmailUtil.sendHtmlEmail(email, user.getEmail(), "用户激活邮件", html);
}
});
thread.start();
}
/**
* 验证token
*
* @param token
*/
@Override
public void activeUser(String token) throws ServiceException {
String userName = cache.getIfPresent(token);
logger.info("token: {}" + token);
if (token == null) {
throw new ServiceException("token无效或者过期");
} else {
User user = userMapper.findByUsername(userName);
if (user == null) {
throw new ServiceException("账号不存在");
} else {
// 设置激活
user.setState(Statu.Active.getValue());
// 更新用户
userMapper.update(user);
// 删除缓存中的键对应的值
cache.invalidate(token);
}
}
}
/**
* 用户找回密码
*
* @param sessionId 客户端的sessionID,限制客户端的操作频率
* @param type 找回密码方式 email | phone
* @param value 电子邮件地址 | 手机号码
*/
@Override
public void funndpassword(String sessionId, String type, String value) {
if (activeCache.getIfPresent(sessionId) == null) {
if ("phone".equals(type)) {
// TODO 根据手机号找
}
if ("email".equals(type)) {
// 根据邮箱找
User user = userMapper.finByEmail(value);
if (user != null) {
// 开启线程发邮件
getThread(value, user);
}
}
activeCache.put(sessionId, "xxxx");
} else {
throw new ServiceException("操作频率过快,稍后再试");
}
}
/**
* 使用线程发送找回密码
*
* @param value
* @param user
*/
private void getThread(String value, User user) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String uuid = UUID.randomUUID().toString().replace("-", "");
passwordCache.put(uuid, user.getUsername());
String url = "http://localhost/foundpassword/newpassword?token=" + uuid;
String html = user.getUsername() + "<<br>请点击该<a href='" + url + "'>链接</a>进行找回密码操作,链接在30分钟内有效";
Email email = getEmail(smpt, port, username, password, formmail);
logger.info("邮箱:{}" + email);
EmailUtil.sendHtmlEmail(email, value, "密码找回邮件", html);
}
});
thread.start();
}
@Override
public void update(User user) {
userMapper.update(user);
}
/**
* 找回密码
*
* @param token
* @return
*/
@Override
public User foundPasswordGetUserByToken(String token) throws ServiceException {
String username = passwordCache.getIfPresent(token);
if (StringUtils.isBlank(username)) {
throw new ServiceException("token过期或错误");
} else {
User user = userMapper.findByUsername(username);
if (user == null) {
throw new ServiceException("未找到对应账号");
} else {
return user;
}
}
}
/**
* 重置密码
*
* @param user
* @param toke
*/
@Override
public void resetPassword(User user, String toke) {
String username = passwordCache.getIfPresent(toke);
if (StringUtils.isBlank(toke) && StringUtils.isBlank(username)) {
throw new ServiceException("token过期,或者错误");
} else {
User u = userMapper.findByUsername(username);
if (u != null) {
u.setPassword(DigestUtils.md5Hex(user.getPassword() + salt));
userMapper.update(u);
// 删除token
passwordCache.invalidate(toke);
logger.info("{} 重置了密码", u.getUsername());
}
}
}
/**
* 更改邮箱
*
* @param user
*/
@Override
public void updateEmail(User user, FormParam param) {
if (StringUtils.isBlank(user.getUsername()) && StringUtils.isBlank(user.getEmail())) {
throw new ServiceException("帐号或者密码不能为空");
}
user.setEmail(param.getEmail());
userMapper.update(user);
}
/**
* 修改密码
*
* @param param
*/
@Override
public void updatePassword(User user, FormParam param) {
if (param == null) {
throw new ServiceException("参数不能为空");
}
String oldpassword = DigestUtils.md5Hex(param.getOldpassword() + salt);
System.out.println("原始密码:" + oldpassword);
System.out.println("密码:" + user.getPassword());
if (!user.getPassword().equals(oldpassword)) {
throw new ServiceException("原始密码错误");
}
// 密码加密
user.setPassword(DigestUtils.md5Hex(param.getNewpassword() + salt));
userMapper.update(user);
}
@Override
public void updateAvatar(User user, FormParam param) {
if (StringUtils.isBlank(param.getFileKey())) {
throw new ServiceException("七牛云参数为空");
}
user.setAvatar(param.getFileKey());
userMapper.update(user);
}
@Override
public AjaxResult getAjaxResult(FormParam param, String action, User user, String password) {
if ("avatar".equals(action)) {
try {
this.updateAvatar(user, param);
return new AjaxResult(AjaxResult.SUCCESS);
} catch (ServiceException e) {
return new AjaxResult(AjaxResult.ERROR, e.getMessage());
}
}
if ("profile".equals(action)) {
try {
this.updateEmail(user, param);
return new AjaxResult(AjaxResult.SUCCESS);
} catch (ServiceException e) {
return new AjaxResult(AjaxResult.ERROR, e.getMessage());
}
}
if ("password".equals(action)) {
try {
this.updatePassword(user, param);
return new AjaxResult(AjaxResult.SUCCESS);
} catch (ServiceException e) {
return new AjaxResult(AjaxResult.ERROR, e.getMessage());
}
}
return new AjaxResult(AjaxResult.ERROR, "参数有误");
}
/**
* 根据id查找对象
*
* @param userid
* @return
*/
private static Map<String,Object> map = Maps.newHashMap();
@Override
public User findById(Integer userid) {
User user = (User)map.get("user:" + userid);
if (user == null) {
user = userMapper.findById(userid);
map.put("user:" + userid, user);
}else {
logger.debug("load user from cache");
}
return user;
}
/**
* 封装Email对象
*
* @param smpt
* @param port
* @param username
* @param password
* @param formmail
* @return
*/
private Email getEmail(String smpt, String port, String username, String password, String formmail) {
return new Email(smpt, port, username, password, formmail);
}
}
网友评论