美文网首页
iOS Developer的全栈之路 - cookie

iOS Developer的全栈之路 - cookie

作者: 西西的一天 | 来源:发表于2019-11-18 20:49 被阅读0次

    由于Http协议是无状态的,也就是说来一个客户端发送两次请求,服务器是无法辨别这两次请求是否为同一个客户端发出的。为了让服务器知道请求的状态,有两种常见的方式:

    cookie

    服务器在response中可以设置cookie,客户端在收到response后,会读取其中的cookie,并在后序的请求中都带着这个cookie。cookie是存在于request/response的header中的。

    cookie是将状态存在于客户端,对于浏览器来说,如果cookie中带有一些敏感信息,便存在安全隐患。

    session

    Session则是将状态存在服务器端,而在cookie中仅存储一个sessionID。Tomcat 的 Session 管理器提供了多种持久化方案来存储 Session,通常会采用Redis作为高性能存储方案。并将Redis进行集群部署,提高可用性。

    在本小结中,我们先来看看cookie的使用方式,cookie在http的报文中是存在于header中的,为了更好的理解,我们先来看看客户端和服务器的通信,这个过程分两步完成:

    1. 与服务器建立Socket连接
    2. 生成请求数据并通过Socket发送出去

    建立socket连接的意思是,客户端向服务器发出 TCP 连接请求,经过TCP三次握手,建立TCP连接。此后便可以通过此连接发送数据、接收数据。HTTP是基于TCP/IP协议的,HTTP协议就是发送/接收数据格式的一种定义。

    下图便是http request的数据格式:


    request.png

    当接收的数据后便会对请求进行处理,生成返回的数据包,http response 的数据格式如下图所示


    response.png

    那么在SpringBoot中如何使用cookie呢?SpringBoot内嵌了Tomcat服务器,在接收到请求数据后,便将数据封装成HttpServletRequest对象,处理后再生成HttpServletResponse对象,它们帮我们处理了socket连接,以及将原始报文转换为Java对象的操作。
    那么,想要操作cookie,需要首先获得HttpServletRequestHttpServletResponse对象,我们看一段简单的controller,它提供了一个login的endpoint:

    @RestController
    @RequestMapping("/user")
    @Slf4j
    public class PassportController {
    
        @Autowired
        private PassportService passportService;
    
        @PostMapping("/login")
        public JSONResult login(@Valid @RequestBody LoginRequest loginRequest, BindingResult bindingResult) {
            if (bindingResult.hasErrors()) {
                List<ObjectError> errors = bindingResult.getAllErrors();
                log.info("errors: {}", errors);
                return JSONResult.error("parameter error");
            }
    
            Users user = passportService.login(loginRequest);
            if (user == null) {
                return JSONResult.error("username or password is invalid");
            }
            return JSONResult.success(user);
        }
    }
    

    通过postman就可以访问这个endpoint

    post http://localhost:8088/user/login
    body { "username": "xiaoming", "password": "123456" }
    

    我们发现login方法没有给我提供获取HttpServletRequestHttpServletResponse对象的机会。贴心的@PostMapping为我们给方法自动注入了这两个参数,我们只需要在方法入参里添加这两个参数即可:

    @PostMapping("/register")
    public JSONResult register(@Valid @RequestBody RegisterRequest registerRequest, BindingResult bindingResult, HttpServletRequest request, HttpServletResponse response) { ... }
    

    cookie通常是服务端首先生成,并设置于response中,可以回看上面的第二张response的截图,在header中有Set-Cookie的字段,那么现在我们来在response中设置一下,HttpServletResponse这个Interface提供了一个方法名为addCookie,出入一个cookie对象即可。javax.servlet.http包中定义了Cookie类,它提供了一个构造方法:

    public Cookie(String name, String value) {
      validation.validate(name);
      this.name = name;
      this.value = value;
    }
    

    通过代码中的注释,可以看出name,value就是一个键值对,name需要符合RFC 2109协议,除了这个核心键值对,还可通过其他的set方法添加一些其他的属性,比如:

    public void setPath() { ... }
    public void setMaxAge(int expiry) { ... }
    public void setDomain(String pattern) { ... }
    

    OK,我们可以开始给response添加cookie啦,这里使用了一个工具类来实现,也只是对上面各个属性设置的一个封装

    @RestController
    @RequestMapping("/user")
    @Slf4j
    public class PassportController {
    
        @Autowired
        private PassportService passportService;
    
        @PostMapping("/login")
        public JSONResult login(@Valid @RequestBody LoginRequest loginRequest, BindingResult bindingResult, HttpServletRequest request, HttpServletResponse response) {
            if (bindingResult.hasErrors()) {
                List<ObjectError> errors = bindingResult.getAllErrors();
                log.info("errors: {}", errors);
                return JSONResult.error("parameter error");
            }
    
            Users user = passportService.login(loginRequest);
            if (user == null) {
                return JSONResult.error("username or password is invalid");
            }
            CookieUtils.setCookie(request, response, "user", JsonUtils.objectToJson(user), true);
            return JSONResult.success(user);
        }
    }
    

    这里通过CookieUtils.setCookie来设置这个cookie,为了演示客户端的确拿到这个cookie,写了一个简单的React App,通过npx create-react-app foodie-frontend创建一个React项目,再使用npm install axios --save安装一个http三方库,通过React Hooks添加一个启动发请求的功能,在App.js中添加如下代码

    function App() {
      useEffect(() => {
        const login = async () => {
          try {
            axios.defaults.withCredentials = true;
            const result = await axios.post("http://localhost:8088/user/login", {username: "xiaoming", password: "123456"})
            console.log(result.data);
          } catch (e) {
            console.log(e);
          }
        }
        login()
      });
    ...
    }
    

    使用npm run start来启动这个前端工程,打开chrome的控制台,就可以看到这个cookie啦:

    chrome.png
    之后在client每次的请求中都会带着这个cookie,验证也很容易,在其他方法中添加如下代码即可看到客户端发来的cookie
    String userCookie = CookieUtils.getCookieValue(request, "user", "utf-8");
    Users cookie = JsonUtils.jsonToPojo(userCookie, Users.class);
    log.info("user cookie: {}", cookie);
    

    Tips

    通过浏览器发送给服务端会遇到跨域问题,跨域是什么可以百度一下,如何解决呢,在服务端添加如下代码:

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
    import org.springframework.web.filter.CorsFilter;
    
    @Configuration
    public class CorsConfig {
        public CorsConfig() { }
    
        @Bean
        public CorsFilter corsFilter() {
            CorsConfiguration config = new CorsConfiguration();
            config.addAllowedOrigin("http://localhost:3000");
            config.addAllowedOrigin("*");
    
            config.setAllowCredentials(true);
            config.addAllowedMethod("*");
            config.addAllowedHeader("*");
    
            UrlBasedCorsConfigurationSource corsSource = new UrlBasedCorsConfigurationSource();
            corsSource.registerCorsConfiguration("/**", config);
    
            return new CorsFilter(corsSource);
        }
    }
    

    其中config.addAllowedOrigin便用于设置允许那些client发来请求

    相关文章

      网友评论

          本文标题:iOS Developer的全栈之路 - cookie

          本文链接:https://www.haomeiwen.com/subject/nzmmictx.html