Vue + SpringBoot 小问题记录

下午事情不是特别多的时候,闲下来看看spring security,配合着iview-admin搞了个小系统,过段时间释放出源码来。当前在登录这块遇到点小问题,记录一下。

先介绍下这个系统“架构”,前端Vue,后端springboot,前后端分离,登录的时候将后端生成的token放入header中,key为Authorization,然后每次请求的时候带上以供服务器验证。

零、跨域问题

跨域是什么就不介绍了,前后端分离肯定要考虑跨域问题,跨域也有多种解决方案,比较常用的两种:一种是在服务端允许部分或者全部Origin,另一种是在前端配置,前端配置又分成两种方案,注意这里之所以说前端也有两种方案是因为我前端打包工具用的是webpack,webpack里面有个组件proxyTable,关于proxyTable就不细说了,请自行谷百,毕竟我是个后端汪。注意proxyTable是在本地调试的时候使用的哦!前端的另一种配置是在Nginx,这种一般在生产中使用。

1. Nginx跨域配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
location /api/ {
set $allow_headers 'Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
set $allow_method 'GET, POST, PUT, DELETE, OPTIONS';
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' $allow_method;
add_header 'Access-Control-Allow-Headers' $allow_headers;
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' $request_method;
add_header 'Access-Control-Allow-Headers' $allow_headers;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' $request_method;
add_header 'Access-Control-Allow-Headers' $allow_headers;
}
if ($request_method = 'PUT') {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' $request_method;
add_header 'Access-Control-Allow-Headers' $allow_headers;
}
if ($request_method = 'DELETE') {
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' $request_method;
add_header 'Access-Control-Allow-Headers' $allow_headers;
}
port_in_redirect on;
proxy_pass http://127.0.0.1:8080/your_context_path/;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

2. SpringBoot对跨域的支持

作为后端汪,为了图方便我肯定选择在后端配置支持跨域,熟悉也简单。

只需要声明一个bean即可

1
2
3
4
5
6
7
8
9
10
11
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders("*")
.allowedOrigins("*").allowedMethods("*");
}
};
}

可以查看allowedXXX()的实现,Java Doc上有很明确的说明,”*”表示支持所有。

3. Spring security对跨域的支持

为什么还要配置security对跨域的支持呢?因为我的登录是是交给Spring security完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 由于使用的是JWT,我们这里不需要csrf
.csrf().disable()
.cors().and()
.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "您还没有登录");
}).accessDeniedHandler((request, response, accessDeniedException) -> {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "您没有访问权限");
}).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 允许所有用户访问
.and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/api/ping", "/api/captcha").permitAll()
// 对除了上述规则外的请求进行验证,兜底的,不能放在最前面
.anyRequest().authenticated()
.and().addFilterAfter(jwtLoginFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}

注意,主要是 cors()这个方法起作用。另外,我想对antMatchers(HttpMethod.OPTIONS).permitAll()进行下说明,前端通过POST方式访问后端的REST接口时,发现两条请求记录,一条请求的Request Method为Options,另一条请求的Reuest Method为Post。OPTIONS方法有几个作用,请参看:https://www.cnblogs.com/virtual/p/3720750.html,此处配置是为了所有的OPTIONS方法直接通过authentication。

一、axios无法获取服务端返回的自定义header

为什么会有这个问题呢?也就是说我为什么要去读取reponse的header呢?前面我已经做过说明,是为了登录请求后拿到header中的token。可是打印了所有的header就是没有key为Authorization的header。

原因

在使用CORS方式跨域时,浏览器只会返回以下默认头部header:

1
2
3
4
5
Content-Language
Content-Type
Expires
Last-Modified
Pragma

如果你想在客户端app中获取自定义的header信息,需要在服务器端header中添加Access-Control-Expose-Headers

解决方案

解决方法就是在上面SpringBoot对跨域支持中声明的Bean中添加一条配置,如下:

1
2
3
4
5
6
7
8
9
10
11
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedHeaders("*")
.exposedHeaders("Authorization")
.allowedOrigins("*").allowedMethods("*");
}
};
}

注意,exposedHeaders()不能使用”*“

参考:

  1. http://www.uxuew.cn/vuejs/7135.html
  2. https://www.jianshu.com/p/95b2caf7e0da
我知道是不会有人点的,但万一有人想不开呢!