前后端分离跨域与cookie

前后端分离基本认识

  • 前后端分离,一般是不会用cookie的,基本上都是提交用户凭证,后端返回一个token,前端每次请求,都应当将这个token的请求头发送给后台,让后台确认是哪个用户在操作。

前后端分离请求跨域问题

  • 前后端分离,一般都涉及到跨域的问题,即当前浏览器发起该次请求的url所在的域与当前页面所在的域(协议、ip、端口任一不一致,视为跨域)。前端vue项目一般有两种解决方式。
    • 一个是在开发的时候,配置一个devServer代理,这个代理其实就是开发时vue项目启动时监听指定端口的服务,这个服务会像网关一样,比如当遇到以/api开头的,就会将原本发给该服务的请求转发到指定的服务器,就相当于后台请求后台,这个时候是没有浏览器的同源策略的限制的,所以不涉及到跨域,但这也只仅限开发的时候,这样去用。
    • 另一个可以直接在axios中,配置请求的地址,这个时候,可以使用cors处理跨域问题。浏览器在发起一个请求前,发现此次请求涉及到跨域,那么就要按照跨域的处理来玩,比如说,如果是个简单请求,那么直接把请求发给后台,后台需要返回cors所规定的一些与跨域相关的响应头,以告知浏览器如何正确的处理这次跨域请求(也就是说,从这里就可以看出来,浏览器是会把跨域请求给发出去的,请求也确确实实到了后台服务器,至于后台有没有处理这个请求,或者说有没有进controller里面的方法,那就要看springmvc它是如何决策的,或者springmvc是否能支持修改能不能到controller方法)。如果后台,没有设置跨域相关的响应头,那么浏览器会认为,这次的跨域请求没有获得后台服务的允许,就会把错误打印在控制台上。如果后台返回了跨域相关的相应头,那么浏览器就会把服务器响应的数据,给到js处理。这也就是说,跨域问题仅仅是浏览器的问题,浏览器是为了保证安全问题。当然如果是复杂请求,在发送真实请求前,会发送一个预检请求,只有预检请求通过了,浏览器才会发送真实的请求,那么springmvc肯定也有处理预检请求的逻辑。
    • 在项目打包上线时,可以使用nginx,因为请求都是浏览器发给nginx,一个路径专门到vue项目,一个路径专门转发到后台,对于浏览器来说,都是直接发给nginx这一个服务,所以自然就不存在跨域问题了。

cookie的基本认识

那么现在就说到这个cookie的问题,它的本质就是一个请求头(Cookie)和响应头(Set-Cookie),只不过这个头会被默认携带给后台。

  • 具体来说:只要你的浏览器没有禁用cookie,它会“自动”携带本域名下的所有cookie到后台,不区分端口(这是浏览器的默认行为,浏览器的规范),cookie最开始是后台返回给前端的,然后浏览器会保存下来,然后每次发送请求前,浏览器都会检查当前要发送请求的域名有没有保存下来的cookie,有的话就把它带过去,没有的话,那就不带。cookie默认情况下,关了浏览器就删了。后台也可以设置删除cookie的响应给前端,设置某个cookie的maxAge=0,那浏览器收到这个响应,就把这个cookie给删了,当然,你也可以手动用js删除指定的某个cookie

跨域 + cookie

那么,现在再来看下,一个新的问题,刚刚说到cookie在前后端不分离的情况下,cookie默认就是刚刚说的这样玩的,但是如果和跨域问题一起出现的话,又会怎样呢?浏览器它还会携带cookie吗?能携带,但是需要正确的配置它!

WebMvcConfigurer

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry
        		// 处理的请求匹配路径
                .addMapping("/**")
                // 预检请求能够被客户端缓存多久
                .maxAge(3600)
                // 是否允许客户端携带凭证,会设置到Access-Control-Allow-Credentials跨域响应头中
                .allowCredentials(true)
                // 允许的域,会设置到Access-Control-Allow-Origin跨域响应头中
                .allowedOrigins("*")
                // 允许的请求方式,可以参考DefaultCorsProcessor和CorsConfiguration
                .allowedMethods("*")
                // 允许浏览器请求携带的请求头
                .allowedHeaders("x-token")
                // 允许浏览器获取的响应头
                .exposedHeaders("token","Authorization")
        ;
    }
}

CorsRegistration

跨域的配置可以看下这个类,里面的解释很清楚,处理逻辑,可以参考DefaultCorsProcessor这个类

public class CorsRegistration {

	private final String pathPattern;

	private final CorsConfiguration config;


	public CorsRegistration(String pathPattern) {
		this.pathPattern = pathPattern;
		// Same implicit default values as the @CrossOrigin annotation + allows simple methods
		this.config = new CorsConfiguration().applyPermitDefaultValues();
	}


	/**
	 * The list of allowed origins that be specific origins, e.g.
	 * {@code "https://domain1.com"}, or {@code "*"} for all origins.
	 * <p>A matched origin is listed in the {@code Access-Control-Allow-Origin}
	 * response header of preflight actual CORS requests.
	 * <p>By default, all origins are allowed.
	 * <p><strong>Note:</strong> CORS checks use values from "Forwarded"
	 * (<a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>),
	 * "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers,
	 * if present, in order to reflect the client-originated address.
	 * Consider using the {@code ForwardedHeaderFilter} in order to choose from a
	 * central place whether to extract and use, or to discard such headers.
	 * See the Spring Framework reference for more on this filter.
	 */
	public CorsRegistration allowedOrigins(String... origins) {
		this.config.setAllowedOrigins(Arrays.asList(origins));
		return this;
	}


	/**
	 * Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"}, etc.
	 * The special value {@code "*"} allows all methods.
	 * <p>By default "simple" methods, i.e. {@code GET}, {@code HEAD}, and
	 * {@code POST} are allowed.
	 */
	public CorsRegistration allowedMethods(String... methods) {
		this.config.setAllowedMethods(Arrays.asList(methods));
		return this;
	}

	/**
	 * Set the list of headers that a preflight request can list as allowed
	 * for use during an actual request. The special value {@code "*"} may be
	 * used to allow all headers.
	 * <p>A header name is not required to be listed if it is one of:
	 * {@code Cache-Control}, {@code Content-Language}, {@code Expires},
	 * {@code Last-Modified}, or {@code Pragma} as per the CORS spec.
	 * <p>By default all headers are allowed.
	 */
	public CorsRegistration allowedHeaders(String... headers) {
		this.config.setAllowedHeaders(Arrays.asList(headers));
		return this;
	}

	/**
	 * Set the list of response headers other than "simple" headers, i.e.
	 * {@code Cache-Control}, {@code Content-Language}, {@code Content-Type},
	 * {@code Expires}, {@code Last-Modified}, or {@code Pragma}, that an
	 * actual response might have and can be exposed.
	 * <p>Note that {@code "*"} is not supported on this property.
	 * <p>By default this is not set.
	 */
	public CorsRegistration exposedHeaders(String... headers) {
		this.config.setExposedHeaders(Arrays.asList(headers));
		return this;
	}

	/**
	 * Whether the browser should send credentials, such as cookies along with
	 * cross domain requests, to the annotated endpoint. The configured value is
	 * set on the {@code Access-Control-Allow-Credentials} response header of
	 * preflight requests.
	 * <p><strong>NOTE:</strong> Be aware that this option establishes a high
	 * level of trust with the configured domains and also increases the surface
	 * attack of the web application by exposing sensitive user-specific
	 * information such as cookies and CSRF tokens.
	 * <p>By default this is not set in which case the
	 * {@code Access-Control-Allow-Credentials} header is also not set and
	 * credentials are therefore not allowed.
	 */
	public CorsRegistration allowCredentials(boolean allowCredentials) {
		this.config.setAllowCredentials(allowCredentials);
		return this;
	}

	/**
	 * Configure how long in seconds the response from a pre-flight request
	 * can be cached by clients.
	 * <p>By default this is set to 1800 seconds (30 minutes).
	 */
	public CorsRegistration maxAge(long maxAge) {
		this.config.setMaxAge(maxAge);
		return this;
	}

	protected String getPathPattern() {
		return this.pathPattern;
	}

	protected CorsConfiguration getCorsConfiguration() {
		return this.config;
	}

}

AdminController

@RestController
@RequestMapping("admin")
public class AdminController {

    @PostMapping("login")
    public Result<UserEntity> login(@RequestBody LoginDto loginDto, HttpSession session, HttpServletResponse response) {
        UserEntity userEntity = new UserEntity();
        userEntity.setUsername(loginDto.getUsername());
        // 写出session,其实就是写出了一个名为Set-Cookie的响应头JSESSIONID=xxx给了前端
        session.setAttribute("user", userEntity);

        response.setHeader("token","123456");
        response.setHeader("Authorization", "halo");
        // 写出这个响应头给前端,但是浏览器不会把这个响应头给到js
        response.setHeader("auth", "auth123");
        return Result.ok(userEntity);
    }

    @GetMapping("getUserInfo")
    public Result<UserEntity> getUserInfo(HttpServletRequest request) {
    	// 如果之前已经存在session(浏览器需要携带上次设置的cookie过了),那就获取(如果浏览器都没带,那就没得商量了),如果不存在,也不创建新的session
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object user = session.getAttribute("user");
            return Result.ok(user);
        } else {
            System.out.println("啥也没有");
            return Result.ok(null);
        }

    }

}

request.js

import axios from 'axios'

const instance = axios.create({
    baseURL: 'http://localhost:8083',
    timeout: 60000,
    withCredentials: true /* 需要设置这个选项,axios发送请求时,才会携带cookie, 否则不会携带 */
})

// Add a request interceptor
instance.interceptors.request.use(function (config) {
    // Do something before request is sent
    return config;
  }, function (error) {
    // Do something with request error
    return Promise.reject(error);
  });

// Add a response interceptor
instance.interceptors.response.use(function (response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    console.log('收到响应',response);
    return response.data.data;
  }, function (error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    return Promise.reject(error);
  });

export default instance

loginApi.js

import request from '@/utils/request'

export function login(data) {
    return request({
        url:'/admin/login',
        method: 'post',
        data
    })
}

export function getUserInfo() {
    return request({
        url:'/admin/getUserInfo',
        method: 'get'
    })
}

App.vue

<template>
  <div class="container">
    <el-button @click="doLogin">登录</el-button> {{ userInfo }}
    <el-button @click="doGetUserInfo">获取用户信息</el-button>
  </div>
</template>

<script>

import {login,getUserInfo } from '@/api/loginApi'


export default {
  components: {
    TalkItem
  },
  data() {
    return {
      userInfo: {}
    }
  },
  methods: {
    async doLogin() {
      let result = await login({username:'zzhua',password:'123456'})
      this.userInfo = result
      console.log('登录成功',result);
    },
    async doGetUserInfo() {
      let userInfo = await getUserInfo()
      console.log('获取用户信息',userInfo);
    }
  }
}
</script>
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值