java web 邮箱找回密码

邮箱激活

背景:几乎每个网站或论坛之类的用户注册后都需要通过发送邮件到邮箱激活用户。
设计:
激活步骤:
  1. 发送激活邮件

  2. 用户查收邮件

  3. 用户点击链接,跳转至成功页面(修改激活状态),激活成功。

  4. 加密能防止伪造攻击,一次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);
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,656评论 18 139
  • 22年12月更新:个人网站关停,如果仍旧对旧教程有兴趣参考 Github 的markdown内容[https://...
    tangyefei阅读 35,182评论 22 257
  • width: 65%;border: 1px solid #ddd;outline: 1300px solid #...
    邵胜奥阅读 4,820评论 0 1
  • 不是一个最好的人 也不是 一个最坏的人 在每个人的内心都有这样的自己 富有,善良,真诚 每时每刻都希望能坦然的面对...
    柒云草阅读 705评论 0 1
  • 你怎么那么美,美得令人陶醉 走路柔柔带风,让我迈不开腿 你怎么那么美,胜过娇艳玫瑰 优雅吐气如兰,飘来淡淡香味 你...
    叫我哆啦美阅读 839评论 12 15