讲一下 COOKIE 和 SESSION ? 
哔哩哔哩。。。
 如果 COOKIE 被禁用了怎么办? 
 可以使用 Token 来代替 COOKIE 进行用户认证。 
 那你看,既然 Token 就能实现功能,那还要 COOKIE 干什么呢? COOKIE 存在时间这么久,肯定是有它的道理的。 
 想了半天,不知道。但是感觉应该是 COOKIE 有一些更加强大的功能我不知道。 
 说到为什么有 COOKIE ?所有的 HTTP 相关资料都是这一句话。 
 因为 HTTP 协议是无状态的。我不知道大家是否真的理解无状态? 
 SMTP 协议,先发送 HELO 用来握手;接下来进入 AUTH 阶段,验证用户名密码;然后再进行数据传输。所以双方必须要时刻记住当前连接的状态。 
 HTTP 协议,每个请求都是完全独立的,服务器直接处理客户端的请求,而不需要去维护连接状态那样麻烦。 
 无状态,是指 HTTP 协议不需要维护各种复杂的通信状态,只是简单的请求与相应,不涉及状态的变更。从而使得 HTTP 协议更加简单。 
对于有状态协议而言,如果连接意外断开,那么如果连接意外断开,整个会话就会丢失,重新连接之后一般需要从头开始。对于无状态协议,即使连接断开了,会话状态也不会受到严重伤害。重新请求就是了。
无状态的缺点在于,单个请求需要将所有信息包含在一个请求中一次发送到服务端,这导致单个消息的结构比较复杂。
 因为 HTTP 协议是无状态的,所以许多早期的 Web 应用面临的最大问题就是如何维持状态。 
 网景公司提出了 COOKIE 的概念,以解决该问题。 
 所以 COOKIE 并不是 HTTP 协议的标准,而是浏览器为了解决 HTTP 无状态引发的问题而提出的解决方案。 
 当要发送 HTTP 请求时,浏览器会先检查是否有相应的 COOKIE ,有则自动添加在 request header 的 COOKIE 字段中。 
 这些是浏览器自动帮助我们做的,而且每一次 HTTP 请求浏览器都会自动帮我们做。所以如果 COOKIE 中的数据不是每个请求都需要发送给服务器,那无疑增加了网络开销。 
 在 Chrome 中打开控制台,选择 Application/Cookies ,然后就可以看到浏览器 Cookie 存储的域,点开就是该域存储的 Cookie 。 
  
  
 最开始 COOKIE 和 Token 几乎是没什么差别的,解决的问题就是如果使用 COOKIE ,这个字段浏览器可以帮我们维护,如果不使用 COOKIE 就需要我们手动在发起 HTTP 请求时维护。 
 后来,为了防止 XSS 攻击,引入了 HttpOnly 字段。 
 XSS :跨站脚本攻击。为网页植入恶意代码,使用户加载并执行攻击者恶意制造的网页程序。 
 假设我们自己维护的 TOKEN ,或者是没有设置 HTTP-ONLY 的 COOKIE ,我们可以通过代码访问,那恶意代码也可以,无法抵御 XSS 攻击。 
 通过设置 COOKIE 的 HTTP-ONLY ,通过 document.cookie 将无法再访问 COOKIE ,这样可以避免恶意代码访问 COOKIE ,提高安全性。 
现在感觉再去看官方文档,之前好多看不懂的地方也能看懂了,豁然开朗。
@RequestMapping("/login")
public Map<String, String> login(HttpSession session) {
    return Collections.singletonMap("token", session.getId());
} 
  这是官方文章中登陆的示例代码,这其实是一个 trick 登陆,之前也给大家讲过。因为有 Spring Security 的层层拦截,所以我们能保证,如果代码执行到了 login 方法,那一定是合法的请求,所以 login 中其实没有什么认证的逻辑。 
 之前一直不明白为什么要把 session.getId() 返回给浏览器作为 token ,现在自己实际演练一遍明白了。 
 建立一个返回 sessionId 的空 SpringBoot 项目。 
@RestController
@RequestMapping("session")
public class SessionController {
    private final HttpSession httpSession;
    public SessionController(HttpSession httpSession) {
        this.httpSession = httpSession;
    }
    @GetMapping
    public String session() {
        return httpSession.getId();
    }
} 
  我们发现 sessionId 其实就是 COOKIE ,也就是说,根据 COOKIE 找 SESSION 的过程,其实是浏览器存储了 Session 的 id ,服务器根据 id 找 SESSION 对象而已。 
  
  
 此处因为没有使用 Spring Session ,所以 COOKIE 名是 JSESSIONID , JSESSIONID 是 Tomcat 创建的。 Spring Session 创建的 COOKIE 名为 SESSION 。 
 所以,这样设计为了方便在 Token 和 COOKIE 两种认证方式之间相互切换,反正是相同的值,底层的逻辑不用变。 
 当然, COOKIE 也是有它的缺点的。 
 COOKIE 是浏览器自动添加到 HTTP 请求中的,所以有了 CSRF 攻击。 
 如果想深入学习 CSRF ,请参考 聊聊CSRF 。 
本图片来自博客: 浅谈CSRF攻击方式
  
  
 当恶意网站请求正常服务接口的时候,浏览器检查有 COOKIE 存在,直接就把 COOKIE 带上发过去了。 
 在用户不知情的情况下,其他网站伪造了客户的请求,所以后台认证用户,不能单单用 COOKIE 。 
  
  
 这是我之前的配置,也不懂什么是 CSRF 啊,直接就禁用了。 
 很简单,直接 .csrf() 就配好了。 
@Component
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /**
         * 配置Spring Security
         * 最开始觉得这个挺难的,其实这个配置别把它当成代码看
         * 直接把它当成句子看,用and连接,就明白了
         * 学习了CSRF,感觉应该启用,防止跨站请求伪造
         * 前台会多存一个CSRF的认证字段
         */
        http
                // 启用CSRF
                .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                // 使用Basic认证方式进行验证进行验证
                .and().httpBasic()
                // 要求SpringSecurity对后台的任何请求进行认证保护
                .and().authorizeRequests().antMatchers("/host/status").permitAll().anyRequest().authenticated()
                // 关闭Security的Session,使用Spring Session
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
                // 设置frameOptions为sameOrigin,否则看不见h2控制台
                .and().headers().frameOptions().sameOrigin();
    }
} 
  启用 CSRF 后,前台会有两个 COOKIE 。分别是 SESSION 和 XSRF-TOKEN 。 
  
  
 原理图如下:服务器将 token 发到了客户端的 COOKIE 中,这个 COOKIE 的 XSRF-TOKEN 不是别的功能,就是用于服务端将令牌发送给浏览器。 
  
  
 如果用户是通过我们的 Angular 应用进行访问的, Angular 默认启用 CSRF 安全,直接将 COOKIE 中的 XSRF-TOKEN 字段为我们添加到首部中,发起请求的时候, Angular 应用发送的请求中首部是带有 X-XSRF-TOKEN 的。 
  
  
 如果是恶意网站伪造的应用,只会有浏览器自带的 COOKIE ,就像这个 POSTMAN 一样,只带着 COOKIE 去访问,是被禁止的。 
  
  
 这里要注意的是, CSRF 只会防御对资源有修改的操作。 
 常用的 REST 规范, GET 、 POST 、 PUT 、 PATCH 、 DELETE 。只有 GET 是不对资源进行修改的。 
 所以, CSRF 不能防御 GET 方法请求。使用 GET 方法时,只使用 COOKIE ,得到了正确的数据,说明 CSRF 没有对 GET 方法进行防御。 
 启用了 CSRF 后,就一定要遵守规范,如果非要把安全性要求极高的接口用 GET 方法暴露, Spring 也很无奈。 
  
  
 跑一遍单元测试,果然和我们预想的一样。启用 CSRF 后,出错的单元测试都是对资源进行修改的方法,说明我们总结的结论是正确的。 
  
  
  
  
 在 perform 方法中,点一个 with ,调用 csrf() 方法即可。 
 Spring Boot Test 默认没有这个包,需要手动引入依赖。 
方法包路径:
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
依赖:
<!-- Spring Security Test -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>5.1.6.RELEASE</version>
    <scope>test</scope>
</dependency> 
  在移动端,往往使用 JWT 的方式进行了用户认证。 
 JWT 是无状态的,所以服务器端无需存储认证信息,减轻服务器的压力,只要保证签发的 JWT 没有被篡改过且合法就好了。 
 这个我也进行了深入的思考,记得上次面试结束时想面试官请教问题,我们为什么要学 TCP ,学 HTTPS 签名,学习底层原理啊?主要原因是开源中间件并不能解决所有问题,有的时候需要我们自主研发。 
 我觉得说得太正确了,如果我们只是一个小公司 996 的程序员,我们可能遇不到并发,遇不到高可用,也遇不到分布式和微服务。但是如果访问量非常大的时候,我们不能仅仅依赖于国外的中间件。 
 就像 Spring Cloud 一样,我们视 Spring Cloud 为业界楷模,微服务的标杆,可是阿里的态度呢?因为国外没有双十一,没有六一八,所以阿里/京东不敢在一次这么大的业务场景去使用国外的中间件。如果扛不住访问,那损失将是数以千亿计。 
秋招面试,虽然挂在了三面,但是通过和面试官的交流,受益良多。
天天写增删改查、业务,那我们可能会被一个能力强培训过几个月的实习生替代。真正能体现工程师水平的,是思考的深度。
多思考,多总结,工程师,不止于框架。