[牛客网中级项目]第四课 注册 登录 浏览 安全性

[牛客网中级项目]第四课 注册 登录 浏览 安全性

目录

1. 注册

1.1 注册实现功能:

1. 用户名合法性检测
2. 密码长度要求
3. 密码Salt加密,密码强度检测
4. 用户邮件/短信激活

1.2 代码功能实现:

UserDAO部分

UserDAO里添加向数据库查询用户是否被注册的功能

    @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where name=#{name}"})
    User selectByName(String name);
UserService部分

UserService里添加register函数,用于实现检验用户名、密码是否为空,该用户是否已注册,密码生成(加盐算法)。返回给Controller的信息用Map存放,最后Controller生成统一的JSON格式返回给前端
检验用户名和密码:

        Map<String, Object> map = new HashMap<String, Object>();
        if (StringUtils.isBlank(username)) {
            map.put("msgname", "用户名不能为空");
            return map;
        }

        if (StringUtils.isBlank(password)) {
            map.put("msgpwd", "密码不能为空");
            return map;
        }

向数据库查询用户是否已经被注册:

        User user = userDAO.selectByName(username);

        if (user != null) {
            map.put("msgname", "用户名已经被注册");
            return map;
        }

密码生成:
密码生成采用MD5+盐的加密算法。普通的md5算法是不能通过解码来获取原来的字符串,如果需要验证密码是否正确,需要对验证密码也进行md5加密后与数据库中的密码进行比对才行。
在这里插入图片描述
但是安全起见我们需要给密码加salt(盐),salt本质上就是一段随机生成的字符串,然后和密码进行拼接,最后整体采用MD5加密算法加密。
我们设置一个工具类ToutiaoUtil
MD5算法如下:

public static String MD5(String key) {
        char hexDigits[] = {
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
        };
        try {
            byte[] btInput = key.getBytes();
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            logger.error("生成MD5失败", e);
            return null;
        }
    }

密码加盐:

// 密码强度
        user = new User();
        user.setName(username);
        //UUID库随机生成一段字符串然后截取前5位
        user.setSalt(UUID.randomUUID().toString().substring(0, 5));
        String head = String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000));
        user.setHeadUrl(head);
        user.setPassword(ToutiaoUtil.MD5(password+user.getSalt()));
        userDAO.addUser(user);
        //注册成功后台下发ticket 登录
        String ticket = addLoginTicket(user.getId());
        map.put("ticket", ticket);
        return map;

注册成功即用户拥有能证明自身身份的token,需要下发给服务器,之后服务器认证用户身份就凭借的是token,token是和用户id绑定在一起的,详细实现参照登录层。

LoginController部分

Controller层在收到UserService层的Map信息后,进行判断。如果包含有ticket表登录成功,将ticket保存在cookie里并response返回,向前端返回注册成功的JSON格式的信息。
代码如下:

    @RequestMapping(path = {"/reg/"}, method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public String reg(Model model, @RequestParam("username") String username,
                      @RequestParam("password") String password,
                      @RequestParam(value="rember", defaultValue = "0") int rememberme,
                      HttpServletResponse response) {
        try {
            Map<String, Object> map = userService.register(username, password);
            if (map.containsKey("ticket")) {
                Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
                //全站有效
                cookie.setPath("/");
                //选'remberme'cookie生存期为5天
                if (rememberme > 0) {
                    cookie.setMaxAge(3600*24*5);
                }
                response.addCookie(cookie);
                return ToutiaoUtil.getJSONString(0, "注册成功");
            } else {
                return ToutiaoUtil.getJSONString(1, map);
            }

        } catch (Exception e) {
            //出现异常返回前端msg
            logger.error("注册异常" + e.getMessage());
            return ToutiaoUtil.getJSONString(1, "注册异常");
        }

2. 登录

2.1 登录实现功能:

1. 服务器密码校验/三方校验回调,token登记
1.1 服务器端token关联userid
1.2 客户端存储token
2. 服务端/客户端token有效期设置

注:token可以是sessionid或者是cookie里的一个Key

2.2 代码功能实现:

LoginTicketDAO部分

创建LoginTicketDAO类来实现以下功能:用户登录时添加ticket、浏览网页时查找ticket、以及登出时更新ticket。

    @Insert({"insert into ", TABLE_NAME, "(", INSERT_FIELDS,
            ") values (#{userId},#{expired},#{status},#{ticket})"})
    int addTicket(LoginTicket ticket);

    @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where ticket=#{ticket}"})
    LoginTicket selectByTicket(String ticket);

    @Update({"update ", TABLE_NAME, " set status=#{status} where ticket=#{ticket}"})
    void updateStatus(@Param("ticket") String ticket, @Param("status") int status);
UserService部分

UserService部分添加login函数,主要的功能有验证用户名和密码是否为空,用户名是否存在,验证密码是否正确。登录成功后token要关联userid
首先要在model新建一个LoginTicket类,类的成员变量有id,用户ID userid,有效期expired,状态status(0表示有效,1表示无效)
登录成功向数据库添加ticket:

LoginTicket ticket = new LoginTicket();
        ticket.setUserId(userId);
        Date date = new Date();
        date.setTime(date.getTime() + 1000*3600*24);
        ticket.setExpired(date);
        ticket.setStatus(0);
        ticket.setTicket(UUID.randomUUID().toString().replaceAll("-", ""));
        loginTicketDAO.addTicket(ticket);
        return ticket.getTicket();

LoginController部分

与注册类似,将ticket保存到cookie中

    public String login(Model model, @RequestParam("username") String username,
                      @RequestParam("password") String password,
                      @RequestParam(value="rember", defaultValue = "0") int rememberme) {
        try {
            Map<String, Object> map = userService.register(username, password);
            if (map.containsKey("ticket")) {
                Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
                cookie.setPath("/");
                if (rememberme > 0) {
                    cookie.setMaxAge(3600*24*5);
                }
                return ToutiaoUtil.getJSONString(0, "注册成功");
            } else {
                return ToutiaoUtil.getJSONString(1, map);
            }

        } catch (Exception e) {
            logger.error("注册异常" + e.getMessage());
            return ToutiaoUtil.getJSONString(1, "注册异常");
        }

3. 浏览页面(重点)

3.1注册实现功能:

1. 客户端:带token的HTTP请求

客户端用户登录浏览网页

2. 服务端:

服务端根据token认证用户并进行权限控制

1.根据token获取用户id
2.根据用户id获取用户的具体信息
3.用户和界面访问权限处理
4.渲染界面/跳转页面

3.2代码功能实现:

拦截器(Interceptor)

拦截器
Spring MVC允许通过处理程序拦截器拦截web请求。处理程序拦截器必须实现HandleInterceptor包含的三个方法:
1.preHandle():在提交HTTP请求之后,到达Controller之前,返回布尔值,true表示继续处理程序执行链,false表示停止执行。
2.postHandle():Controller之后,渲染页面之前
3.afterCompletion():在整个请求结束后进行收尾。
新建PassportInterceptor实现HandlerInterceptor三个方法

public class PassportInterceptor implements HandlerInterceptor {

    @Autowired
    private LoginTicketDAO loginTicketDAO;

    @Autowired
    private UserDAO userDAO;

    @Autowired
    private HostHolder hostHolder;
    
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,Object o) throws Exception{
        String ticket = null;
        //用户发送请求后,从request中查找cookie里的ticket
        if(httpServletRequest.getCookies()!=null) {
            for(Cookie cookie:httpServletRequest.getCookies()){
                if(cookie.getName().equals("ticket")){
                    ticket = cookie.getValue();
                    break;
                }
            }
        }
        //存在有ticket但是也不能相信(可能伪造ticket),要查数据库
        if(ticket != null) {
            LoginTicket loginTicket = loginTicketDAO.selectByTicket(ticket);
        }
        //数据库查询不到,ticket有效期失效,ticket的status失效
        if(loginTicket == null || loginTicket.getExpired().before(new Date()) || loginTicket.getStatus()!=0) {
            return true;
        }
         //根据ticket确定用户身份,hostHolder保存用户
         User user = userDAO.selectById(loginTicket.getUserId());
         hostHolder.setUser(user);
    }
    
    //渲染页面前,modelAndView把user添加到前端
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        if (modelAndView != null && hostHolder.getUser() != null) {
            modelAndView.addObject("user", hostHolder.getUser());
        }
    }
    //清除hostHolder该用户进程
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        hostHolder.clear();
    }

新建一个hostHolder类来保存该线程用户

@Component
public class HostHolder {
    private static ThreadLocal<User> users = new ThreadLocal<User>();

    public User getUser() {
        return users.get();
    }

    public void setUser(User user) {
        users.set(user);
    }

    public void clear() {
        users.remove();
    }
}
未登录跳转

我们还要对未登录用户进行权限控制,限制访问某些页面,设置LoginRequireInterceptor

@Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        if (hostHolder.getUser() == null) {
            httpServletResponse.sendRedirect("/?pop=1");
            return false;
        }
        return true;
    }
注册拦截器

将整个链路挂起,注册拦截器,新建configuration里ToutiaoWebConfiguration继承WebMvcConfigurerAdapter

@Autowired
PassportInterceptor passportInterceptor;

@Autowired
LoginRequiredInterceptor loginRequiredInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(passportInterceptor);
    //指定loginRequiredInterceptor的作用范围
    registry.addInterceptor(loginRequiredInterceptor).addPathPatterns("/setting*");
    super.addInterceptors(registry);
}

4.用户数据安全性

多种渠道:

1.HTTP注册页
2.公钥加密私钥解密,支付宝h5页面的支付密码加密
3.用户密码salt防止破解(CSDN,网易邮箱未加密密码泄露)
4.token有效期
5.单一平台的单点登录,登录IP异常检验
6.用户状态的权限判断
7.添加验证码机制,防止爆破和批量注册

5.AJAX异步数据交互

比如:评论翻页,不刷新页面,只加载评论并更新评论区

好处:

1.页面不刷新
2.体验更好
3.传输数据更少
4.APP/网站通用

扩展:统一的数据格式:{code:0,msg:",data:"}
例子:牛客投递登录框,点赞登录框

6.SpringBoot Dev Tools

添加springbootdevtools 依赖,可以不重启整个项目,而只修改运行某个文件

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值