开发中遇到的关于跨域请求的问题
SpringBoot 跨域配置
这是一段跨域配置的代码,有一些问题需要注意。
1@Configuration
2public class CorsConfig implements WebMvcConfigurer {
3 @Override
4 public void addCorsMappings(CorsRegistry registry) {
5 registry.addMapping("/**")
6 .allowedOriginPatterns("http://127.0.0.1:*", "https://127.0.0.1:*", "http://localhost:*", "https://localhost:*")
7 .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
8 .allowCredentials(true)
9 .allowedHeaders("*")
10 .maxAge(3600);
11 }
12}
第一,127.0.0.1
和 localhost
需要同时指定!通过测试,如果只指定其中一个,无法进行另一个的请求。
第二,当项目里使用的 springboot 的版本是 2.1.2.RELEASE 时,使用 allowedOrigins("*")
没有问题,但是当版本改为 2.4.4 后会提示报错。
当 allowCredentials 为 true 时,allowedOrigins 不能包含特殊值 “*",因为它不能在 “Access-Control-Allow-Origin” 响应头中设置。
要允许一组起源的凭证,明确地列出它们,或者考虑使用 allowedOriginPatterns 代替。
Options 请求跨域问题
在浏览器内请求跨域时,浏览器会先进行一次 preflight request(预检请求),以确认目标域是否允许跨域,如果允许,再进行实际操作中的请求。
在接口请求中我们总会自定义请求头做 token,但是浏览器预检请求中的 OPTIONS 只会携带自定义的 token 字段但不会带相应的值,导致跨域以及报错。
所以要处理跨域以及在 SpringSecurity 的认证过滤器链中需要过滤掉 OPTIONS。
如果使用的是 Interceptor,则可以在定义的拦截器中将 OPTIONS 方法的请求放行即可。
1@Component
2public class EnterInterceptor implements HandlerInterceptor {
3 @Override
4 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
5 if (request.getMethod().equals("OPTIONS")) {
6 return true;
7 }
8 if (AuthUtil.checkToken(request)) {
9 return true;
10 } else {
11 response.sendRedirect("/api/error/401/" + URLEncoder.encode("token验证失败", "UTF-8"));
12 return false;
13 }
14 }
15}
除此之外,为节省资源,可以通过配置缓存时间以防止浏览器频繁进行预检请求。
在 CORS 中,当浏览器发送跨域请求时,服务器可以返回一个 Access-Control-Max-Age
响应头,它指定了在这个响应被缓存多久。这意味着,在接下来的一段时间内,浏览器不需要向服务器再发送预检请求,而可以直接发送实际请求。
还是如下这段跨域配置代码:
1@Override
2public void addCorsMappings(CorsRegistry registry) {
3 registry.addMapping("/**")
4 .allowedOriginPatterns("http://127.0.0.1:*", "https://127.0.0.1:*", "http://localhost:*", "https://localhost:*")
5 .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
6 .allowCredentials(true)
7 .allowedHeaders("*")
8 .maxAge(3600);
9}
在上面的代码片段中,maxAge(3600)
指定了响应在缓存中的最长时间为 3600 秒(1 小时),在这段时间内,浏览器将不再发送预检请求,从而提高了跨域请求的性能和效率。然而,需要注意的是,设置 maxAge 过长可能会导致安全风险,因为在这段时间内,任何网站都可以使用这个响应进行访问。因此,应该根据实际需求设置一个适当的值。
请求头字段 Authorization
请求头字段 Authorization 是用于在客户端向服务器发送请求时进行身份验证的一种方式。它通常用于发送包含访问令牌(Access Token)的请求,以便服务器可以验证客户端是否有权限执行该请求。
当使用 Authorization 头字段进行身份验证时,其值通常是以 Bearer 或 Basic 为前缀的字符串,例如 Bearer xxxxxxxx
或 Basic username:password
(其中 username 和 password 是 base64 编码后的用户名和密码字符串)。服务器可以从该头字段中提取身份验证信息并进行验证,以确保请求者有权访问所请求的资源或执行所请求的操作。
但是为什么要添加 Bearer / Basic 呢?
Bearer 和 Basic 是 Authorization 头字段中常用的两种身份验证方式:
Bearer 身份验证方式通常用于 OAuth 2.0 协议中,其特点是使用一个访问令牌(Access Token)作为凭据进行身份验证,客户端在请求时需要将该令牌放在 Authorization 头字段中的 Bearer 后面,例如:Authorization: Bearer xxxxxxxx。服务器在接收到请求后,可以根据该令牌验证请求者的身份和权限,并对其进行授权。
Basic 身份验证方式则比较简单,在请求时需要将用户名和密码用冒号连接后进行 base64 编码,并将编码后的字符串放在 Authorization 头字段中的 Basic 后面,例如:Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=。服务器在接收到请求后,可以将 base64 解码后的用户名和密码与其保存的凭据进行比较,以验证请求者的身份和权限。
在 HTTP 协议中,Authorization 头字段的格式是固定的,它由一个身份验证方式和一个凭据组成,两者之间用空格分隔。因此,在使用 Authorization 头字段进行身份验证时,需要在身份验证方式和凭据之间加上相应的前缀,以便服务器可以正确解析和验证请求。
所以,如果后端采用了 OAuth 之类的框架,则需要正确配置 Authorization 头字段。如果后端没有采用类似框架,比如是用了 JWT 加密的 token,那可以不用 Authorization 头字段,而采用自定义的字段,比如 auth,之后在代码中自行获取 auth 字段值即可:
1public static Boolean checkToken(HttpServletRequest request) {
2 String token = request.getHeader("auth");
3 if (token == null) { // token不存在
4 log.error("token 不存在");
5 return false;
6 }
7 Claims claims = JwtUtil.getClaimsByToken(token);
8 if (claims == null || JwtUtil.isTokenExpired(claims.getExpiration())) { // token 错误或过期
9 log.error("token 错误或过期");
10 return false;
11 }
12 log.info("token 验证通过");
13 return true;
14}