牛客网中级项目(三)注册登录登出&登录验证&权限验证&Interceptor拦截器&链路回调&devtools

    xiaoxiao2022-07-02  133

    注册

    用户注册时,controller层接收前端传来的注册信息(username和password)。 service层进行业务逻辑处理,定义register方法,使用Map来返回数据。 判断用户注册的名称,密码是否为空,再去UserDao中查找用户名是否已经注册,如果为空或者已经注册,则放到map集合中,return map。 如果用户注册信息正常,用户名未被注册,则将用户注册信息添加到数据库addUser(),并对原有的password进行加salt处理,并进行MD5加密。 再回到controller层,使用@ResponseBody,不返回页面。无论用户注册成功与否,都返回一个json串给用户。 使用阿里的fastjson工具,并在ToutiaoUtil中,自定义json方法和MD5加密。 Controller层

    @Controller public class LoginController { private static final Logger logger = LoggerFactory.getLogger(LoginController.class); @Autowired UserService userService; @RequestMapping(path = {"/reg/"}, method = {RequestMethod.GET, RequestMethod.POST}) //正常返回的是一个页面,注册成功返回一个JSON串 @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.isEmpty()) { return ToutiaoUtil.getJSONString(0, "注册成功"); } else { return ToutiaoUtil.getJSONString(1, map); } } catch (Exception e) { logger.error("注册异常" + e.getMessage()); return ToutiaoUtil.getJSONString(1, "注册异常"); } } }

    Service层

    @Service public class UserService { private static final Logger logger = LoggerFactory.getLogger(UserService.class); @Autowired private UserDAO userDAO; @Autowired private LoginTicketDAO loginTicketDAO; /* public void addUser(User user) { userDAO.addUser(user); }*/ public Map<String, Object> register(String username, String password) { 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; } // 密码强度 user = new User(); user.setName(username); //将原有的password+加上生成的salt进行加密 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); return map; } }

    Dao层

    package com.nowcoder.dao; import com.nowcoder.model.User; import org.apache.ibatis.annotations.*; @Mapper public interface UserDAO { String TABLE_NAME = "user"; String INSET_FIELDS = " name, password, salt, head_url "; String SELECT_FIELDS = " id, name, password, salt, head_url"; @Insert({"insert into ", TABLE_NAME, "(", INSET_FIELDS, ") values (#{name},#{password},#{salt},#{headUrl})"}) int addUser(User user); @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where id=#{id}"}) User selectById(int id); @Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where name=#{name}"}) User selectByName(String name); @Update({"update ", TABLE_NAME, " set password=#{password} where id=#{id}"}) void updatePassword(User user); @Delete({"delete from ", TABLE_NAME, " where id=#{id}"}) void deleteById(int id); }

    自定义工具类 工具类中的方法都是静态,方便调用

    package com.nowcoder.util; import com.alibaba.fastjson.JSONObject; import com.nowcoder.controller.LoginController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.security.MessageDigest; import java.util.Map; public class ToutiaoUtil { private static final Logger logger = LoggerFactory.getLogger(ToutiaoUtil.class); //运用fastjson工具,自定义一个json工具 //服务器返回前端,一般都有一个code,告诉用户是否正确返回 public static String getJSONString(int code) { JSONObject json = new JSONObject(); json.put("code", code); return json.toJSONString(); } public static String getJSONString(int code, String msg) { JSONObject json = new JSONObject(); json.put("code", code); json.put("msg", msg); return json.toJSONString(); } public static String getJSONString(int code, Map<String, Object> map) { JSONObject json = new JSONObject(); json.put("code", code); for (Map.Entry<String, Object> entry : map.entrySet()) { json.put(entry.getKey(), entry.getValue()); } return json.toJSONString(); } 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; } } }

    登录

    服务器通过ticket,识别用户。 服务器密码校验后,会生成一个ticket,这个ticket关联了userid,你登陆过了知道你是谁。这个ticket会发送给客户端。这个ticket会存放在cookie里面,相当于客户端存储ticket。 以后客户端和服务器进行交互时,都会利用ticket,服务器就知道你是谁了。

    创建login_ticket数据库

    CREATE TABLE `login_ticket` ( `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL, `ticket` VARCHAR(45) NOT NULL, <!-- 过期时间 --> `expired` DATETIME NOT NULL, `status` INT NULL DEFAULT 0, PRIMARY KEY (`id`), UNIQUE INDEX `ticket_UNIQUE` (`ticket` ASC));

    创建实体类LoginTicket

    package com.nowcoder.model; import java.util.Date; public class LoginTicket { private int id; private int userId; private Date expired; private int status;// 0有效,1无效 private String ticket; public String getTicket() { return ticket; } public void setTicket(String ticket) { this.ticket = ticket; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getUserId() { return userId; } public void setUserId(int userId) { this.userId = userId; } public Date getExpired() { return expired; } public void setExpired(Date expired) { this.expired = expired; } public int getStatus() { return status; } public void setStatus(int status) { this.status = status; } }

    LoginTicketDAO

    package com.nowcoder.dao; import com.nowcoder.model.LoginTicket; import com.nowcoder.model.User; import org.apache.ibatis.annotations.*; @Mapper public interface LoginTicketDAO { String TABLE_NAME = "login_ticket"; String INSERT_FIELDS = " user_id, expired, status, ticket "; String SELECT_FIELDS = " id, " + INSERT_FIELDS; @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}"}) //在方法参数的前面写上@Param("参数名"),表示给参数命名,名称就是括号中的内容 void updateStatus(@Param("ticket") String ticket, @Param("status") int status); }

    Service层

    @Service public class UserService { private static final Logger logger = LoggerFactory.getLogger(UserService.class); @Autowired private UserDAO userDAO; @Autowired private LoginTicketDAO loginTicketDAO; /* public void addUser(User user) { userDAO.addUser(user); }*/ public Map<String, Object> register(String username, String password) { 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; } // 密码强度 user = new User(); user.setName(username); //将原有的password+加上生成的salt进行加密 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); //登录 String ticket = addLoginTicket(user.getId()); map.put("ticket", ticket); return map; } public Map<String, Object> login(String username, String password) { 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; } if (!ToutiaoUtil.MD5(password+user.getSalt()).equals(user.getPassword())) { map.put("msgpwd", "密码不正确"); return map; } //ticket,登录成功 String ticket = addLoginTicket(user.getId()); map.put("ticket", ticket); return map; } //生成一个ticket private String addLoginTicket(int userId) { 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(); } public User getUser(int id) { return userDAO.selectById(id); } }

    Controller层

    @Controller public class LoginController { private static final Logger logger = LoggerFactory.getLogger(LoginController.class); @Autowired UserService userService; @RequestMapping(path = {"/reg/"}, method = {RequestMethod.GET, RequestMethod.POST}) //正常返回的是一个页面,注册成功返回一个JSON串 @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); //map中包含这个ticket,注册成功 if(map.containsKey("ticket")) { //创建cookie //Cookie cookie = new Cookie(String cookieName,String cookieValue); Cookie cookie = new Cookie("ticket", map.get("ticket").toString()); //设置cookie是全站有效的 cookie.setPath("/"); //设置持久化时间,否则关闭浏览器cookie就会被销毁 if (rememberme > 0) { cookie.setMaxAge(3600*24*5); } //向客户端发送cookie response.addCookie(cookie); return ToutiaoUtil.getJSONString(0, "注册成功"); } else { return ToutiaoUtil.getJSONString(1, map); } } catch (Exception e) { logger.error("注册异常" + e.getMessage()); return ToutiaoUtil.getJSONString(1, "注册异常"); } } @RequestMapping(path = {"/login/"}, method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody 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, "注册异常"); } } }

    登出

    服务端/客户端删除ticket,将status状态设为1即可。 Service层

    @Service public class UserService { private static final Logger logger = LoggerFactory.getLogger(UserService.class); @Autowired private LoginTicketDAO loginTicketDAO; //生成一个ticket private String addLoginTicket(int userId) { 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(); } public void logout(String ticket) { loginTicketDAO.updateStatus(ticket, 1); } }

    Controller层

    @Controller public class LoginController { private static final Logger logger = LoggerFactory.getLogger(LoginController.class); @Autowired UserService userService; @RequestMapping(path = {"/logout/"}, method = {RequestMethod.GET, RequestMethod.POST}) public String logout(@CookieValue("ticket") String ticket) { userService.logout(ticket); return "redirect:/"; } }

    Spring Boot拦截器的使用–登录验证

    相关知识:https://luchen0620.blog.csdn.net/article/details/90476715 自定义拦截器PassportInterceptor

    @Component 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 { //默认ticket为null String ticket = null; //判断客户端的cookie不为null if (httpServletRequest.getCookies() != null) { //request获得所有的cookie,并进行遍历 for (Cookie cookie : httpServletRequest.getCookies()) { //如果有一个cookie的name我们的是一样的,是我们想要的ticket if (cookie.getName().equals("ticket")) { //这样就能找到那个ticket ticket = cookie.getValue(); break; } } } //ticket不为null,表示用户可能登陆过 //不能完全相信ticket,也可能是伪造的,要先去查一下 if (ticket != null) { //验证ticket的有效性 LoginTicket loginTicket = loginTicketDAO.selectByTicket(ticket); if (loginTicket == null || loginTicket.getExpired().before(new Date()) || loginTicket.getStatus() != 0) { return true; } //我已经知道你是谁了,但是这是在进入Controller之前调用的 //如果希望在进入Controller之后还想引用你的这些信息,那就需要把你给记下来 //否则到了Controller之后,不知道你是谁,又要重新再做一遍 User user = userDAO.selectById(loginTicket.getUserId()); hostHolder.setUser(user); } return true; } @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()); } } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { hostHolder.clear(); } }

    注册拦截器 实现WebMvcConfigurer 接口中的 addInterceptors()添加自定义的拦截器

    package com.nowcoder.configuration; import com.nowcoder.interceptor.PassportInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Component //把写好的拦截器注册到MVC里面 public class ToutiaoWebConfiguration extends WebMvcConfigurerAdapter { @Autowired PassportInterceptor passportInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(passportInterceptor); super.addInterceptors(registry); } }

    HostHolder用来存储当前访问的用户

    package com.nowcoder.model; import org.springframework.stereotype.Component; @Component //HostHolder用来存这个用户的,users.set(), //通过users.get()方式提取出来 // 使用ThreadLocal,表示当前访问的用户是谁 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();; } }

    链路回调思想

    你请求给我,在所有的链路之前先看一下你是谁,知道你之后,就把信息放到一个大家都能访问到的地方HostHolder ,整个链路过来以后,如果想访问就访问HostHolder 。整个链路走完以后,把这个清掉。 参考:https://blog.csdn.net/qq_31617121/article/details/79134042 一、可以知道用户是这个用户。 注册成功后会进行自动登陆,对于登陆,在登陆操作中,在service层,进行逻辑判断,对上返回状态回到controller,对下dao去和数据库交互。在service登陆代码中,服务器会生成一个string类型的ticket,存入cookie中,key值是ticket,value值是ticket的值。通过response下发到浏览器。 下次在已经登陆的用户,进行其他点击后。在进入controller前,调用preHandle方法处理,它可以检查客户端提交的cookie中是否有服务器之前下发的ticket,如果有证明这个请求是已经登陆的用户了。把登陆的用户放到线程本地变量。在此时才进入controller,可以拿到具体的用户HostHolder类,这是线程本地变量,可以根据登陆的用户进行个性化渲染,比如关注用户的动态,个人收藏等。

    二、可以进行权限管理 比如,在浏览某些页面时,要保证用户登陆,或者用户是某个等级的。可以现在preHandler中判断,这个可以在设置一个特定范围的拦截器,如下的拦截器LoginRequiredInterceptor,在访问setting页面时才会进入到该拦截器,如果验证没有HostHolder,说明用户没登陆,就跳转到主页或者给出登陆页面。拦截器也先后执行顺序。在配置类中ToutiaoWebConfiguration中先后决定。

    三、可以自动登陆 我们平时在浏览网页的时候会碰到这样的情况,昨天登陆了某个网站,关机,第二天再登陆,自动跳转到我的用户了。这里是服务器会把浏览器的sessionId和服务器的数据库关联,在提交请求的时候服务器会设置拦截器去找sessionId,如果这个sessionId和我服务器上的ticket关联了起来,并且设置的过期时间没有过期,那在登陆主页前我就可以知道是某个用户,在controller层中可以进行渲染。 可以做到自动登陆的功能。

    未登录跳转–权限验证

    如果用户没有登录,则不能访问http://localhost:8080/setting这个页面,跳转到首页去。 通过登陆验证看HostHolder类中有没有线程本地变量user类,如果有,就知道是谁登陆的。如果没有,那没有权限访问/setting*相关的页面。通过PassportInterceptor的PreHandler()方法后,进入LoginRequiredInterceptor的PreHandler()方法,如果没有拿到user类,返回false。不能进入controller层,重定向到首页,并且约定pop=1,弹出登陆框。 创建登录拦截器LoginRequiredInterceptor

    @Component public class LoginRequiredInterceptor implements HandlerInterceptor { @Autowired private HostHolder hostHolder; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { if (hostHolder.getUser() == null) { httpServletResponse.sendRedirect("/?pop=1"); return false; } return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } }

    注册登录拦截器

    @Component //把写好的拦截器注册到MVC里面 public class ToutiaoWebConfiguration extends WebMvcConfigurerAdapter { @Autowired PassportInterceptor passportInterceptor; @Autowired LoginRequiredInterceptor loginRequiredInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(passportInterceptor); registry.addInterceptor(loginRequiredInterceptor).addPathPatterns("/setting*"); super.addInterceptors(registry); } }

    访问跳转

    @RequestMapping(path = {"/", "/index"}, method = {RequestMethod.GET, RequestMethod.POST}) public String index(Model model, @RequestParam(value = "pop", defaultValue = "0") int pop) { model.addAttribute("vos", getNews(0, 0, 10)); model.addAttribute("pop", pop); return "home"; }

    AJAX:异步数据交互

    页面不刷新体验更好传输数据更少APP/网站通用 评论有很多页,选择一页的时候,页面不刷新,URL不变,只返回变化的数据信息。

    Spring Boot Developer Tools

    spring为开发者提供了一个名为spring-boot-devtools的模块来使Spring Boot应用支持热部署,提高开发者的开发效率,无需手动重启Spring Boot应用。 动态加载更新的class 编译加载修改的静态文件 pom.xml引入

    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <version>2.1.5.RELEASE</version> </dependency>
    最新回复(0)