天天看点

Spring Security + JWT 前后端分离 调用外部登录接口 登录进阶1. 登陆流程2. 代码实现

本文章适合对Spring Security有一定了解的同学阅读。

1. 登陆流程

Spring Security + JWT 前后端分离 调用外部登录接口 登录进阶1. 登陆流程2. 代码实现

项目中需要后端进行登陆–>生成token–>鉴权等一系列操作,使用Spring Security完成。和网上大部分资料不同的是,这里的用户名和密码并不是存在数据库中,而是调用RPC接口实现登录。

2. 代码实现

首先我们需要引入Spring security依赖,以便于使用security给我们提供的一系列组件。

引入spring security依赖

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>
           

WebSecurityConfigurerAdapterConfigurerAdapter

这是Spring Security的核心,所以相关配置均在此设置。所有这里涉及到的类,下面都会给出代码。

import com.service.handler.*;
import com.service.impl.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * 2019/10/29 10:53 AM
 *
 * @author: zhouximin
 * @Description: Spring Security配置
 *
 **/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    SecurityAuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    @Autowired
    SecurityAuthenticationSuccessHandler authenticationSuccessHandler;
    @Autowired
    SecurityAuthenticationFailureHandler authenticationFailureHandler;
    @Autowired
    MyUserDetailsService userDetailsService;
    @Autowired
    LoginProvider loginProvider;

    @Bean
    LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        loginFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
        loginFilter.setFilterProcessesUrl("/login");
        //这句很关键,重用WebSecurityConfigurerAdapter配置的AuthenticationManager,不然要自己组装AuthenticationManager
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        return loginFilter;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 加入自定义的安全认证
        auth.authenticationProvider(loginProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
		//关闭csrf,便于测试 
        .and().csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .httpBasic().authenticationEntryPoint(authenticationEntryPoint)
                .and()
                .authorizeRequests()
                .anyRequest().authenticated()
				
				// 开启form表单登录
                .and()
                .formLogin()
                .permitAll();
        // 当鉴权失败时,不返回html页面,而是返回403状态码
        http.exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint());
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}


           

对于configure中参数部分解析

方法 说明
openidLogin() 用于基于 OpenId 的验证
headers() 将安全标头添加到响应,比如说简单的 XSS 保护
cors() 配置跨域资源共享( CORS )
sessionManagement() 允许配置会话管理
portMapper() 允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443
jee() 配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理
x509() 配置基于x509的认证
rememberMe 允许配置“记住我”的验证
authorizeRequests() 允许基于使用HttpServletRequest限制访问
requestCache() 允许配置请求缓存
exceptionHandling() 允许配置错误处理
securityContext() 在HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用
servletApi() 将HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用
csrf() 添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用
logout() 添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success”
anonymous() 允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”
formLogin() 指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面
oauth2Login() 根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证
requiresChannel() 配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射
httpBasic() 配置 Http Basic 验证
addFilterBefore() 在指定的Filter类之前添加过滤器
addFilterAt() 在指定的Filter类的位置添加过滤器
addFilterAfter() 在指定的Filter类的之后添加过滤器
and() 连接以上策略的连接器,用来组合安全策略。实际上就是"而且"的意思

表格内容来自:https://juejin.im/post/5da6adbce51d4524ad10d1d7

AuthenticationEntryPoint

用来解决匿名用户访问无权限资源时的异常

import com.alibaba.fastjson.JSON;
import com.model.response.SecurityResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 2019/10/29 2:58 PM
 *
 * @author: zhouximin
 * @Description:
 **/
@Component
public class SecurityAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException==null?"Unauthorized":authException.getMessage());
    }
}

           

AuthenticationFailureHandler

用于解决登录失败时的异常

import com.alibaba.fastjson.JSON;
import com.model.response.SecurityResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 2019/10/29 9:19 PM
 *
 * @author: zhouximin
 * @Description:
 **/
@Component
public class SecurityAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        SecurityResponseEntity responseEntity = new SecurityResponseEntity();
        responseEntity.setStatus("400");
        responseEntity.setMsg("验证失败!");
        httpServletResponse.setHeader("Content-type", "application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(responseEntity));
    }
}
           

AuthenticationSuccessHandler

登录成功时需要做的操作

import com.alibaba.fastjson.JSON;
import com.model.SecurityUserInfo;
import com.model.response.SecurityResponseEntity;
import com.service.UserService;
import com.utils.DateUtil;
import com.utils.JwtTokenUtil;
import com.nimbusds.jose.JOSEException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

/**
 * 2019/10/29 8:57 PM
 *
 * @author: zhouximin
 * @Description:
 **/
@Slf4j
@Component
public class SecurityAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Autowired
    UserService userService;
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        // 设置返回信息
        SecurityResponseEntity responseEntity = new SecurityResponseEntity();
        responseEntity.setStatus("200");
        responseEntity.setMsg("登陆成功!");
        // 获取登录用户信息
        SecurityUserInfo userDetails = (SecurityUserInfo) authentication.getPrincipal();
	    // 生成token
        Map<String, Object> jwtContent = new HashMap<>();
        jwtContent.put("user_id", userDetails.getUsername());
        jwtContent.put("exp", DateUtil.nHourAfterNow(2));
        String jwtToken = null;
        log.info("生成jwt token");
        try {
            jwtToken = JwtTokenUtil.creatTokenHS256(jwtContent);
            log.info("jwt token:{}", jwtToken);
        } catch (JOSEException e) {
            e.printStackTrace();
        }
        // 将生成的token加到cookie和请求header中
        String cookieValue = "Bearer_" + jwtToken;
        Cookie cookie = new Cookie("Authorization", URLEncoder.encode(cookieValue, "UTF-8"));
        
        log.info("cookie:{}", cookie);
        httpServletResponse.addCookie(cookie);
        responseEntity.setJwtToken(cookieValue);
        log.info("get cookie{}", httpServletRequest.getCookies());
        httpServletResponse.setHeader("Set-Cookie", "Authorization=" + cookieValue + ";");
        httpServletResponse.setHeader("Content-type", "application/json;charset=UTF-8");

        httpServletResponse.getWriter().write(JSON.toJSONString(responseEntity));
    }
}
           

UserDetailsService

一般的登录验证都是在这里,获取到库中存储的密码,和输入的密码做比对,但是我是调用外部接口判断登录,也拿不到库中的密码,所以这里没有做校验,只是为返回的userInfo赋username和权限属性。

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Set;

/**
 * 2019/10/29 3:25 PM
 *
 * @author: zhouximin
 * @Description:
 **/
@Component
@Slf4j
public class MyUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

        SecurityUserInfo userInfo = new SecurityUserInfo();
        userInfo.setUsername(s);
        Set authoritiesSet = new HashSet();
        GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN");
        authoritiesSet.add(authority);
        userInfo.setAuthorities(authoritiesSet);
        return userInfo;
    }
}
           

AuthenticationProvider

具体的用户名密码校验放在这里做。

import com.keep.gundam.service.impl.MyUserDetailsService;
import com.keep.spring.boot.dubbo.DubboReference;
import com.keep.user.account.model.User;
import com.keep.user.account.rpc.exception.KeepUserException;
import com.keep.user.account.rpc.service.v2.UserRpcServiceV2;
import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

/**
 * 2019/10/30 4:42 PM
 *
 * @author: zhouximin
 * @Description:
 **/
@Slf4j
@Component
public class LoginProvider implements AuthenticationProvider {
    @DubboReference(timeout = 2000,check = false)
    UserService userService;
    @Autowired
    MyUserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getPrincipal().toString();
        log.info("username{}",username);
        String password = authentication.getCredentials().toString();
        log.info("password{}",password);
        try {
            // 调用登录接口,成功的话返回用户信息,不成功则抛出异常
            User user = userService.login(username, password);
            if (user != null){
                log.info("username:{}",user.getUsername());
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                return new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
            }
        } catch (UserException e){
            throw new RuntimeException("用户名或密码错误");
        }
        return null;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}
           

UsernamePasswordAuthenticationFilter

import com.fasterxml.jackson.databind.ObjectMapper;
import com.model.AuthenticationBean;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;

/**
 * 2019/10/30 5:03 PM
 *
 * @author: zhouximin
 * @Description:
 **/

public class LoginFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        //attempt Authentication when Content-Type is json
        if(request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
                ||request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)){

            //use jackson to deserialize json
            ObjectMapper mapper = new ObjectMapper();
            UsernamePasswordAuthenticationToken authRequest = null;
            try (InputStream is = request.getInputStream()){
                AuthenticationBean authenticationBean = mapper.readValue(is,AuthenticationBean.class);
                authRequest = new UsernamePasswordAuthenticationToken(
                        authenticationBean.getUsername(), authenticationBean.getPassword());
            }catch (IOException e) {
                e.printStackTrace();
                authRequest = new UsernamePasswordAuthenticationToken(
                        "", "");
            }finally {
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            }
        }

        //transmit it to UsernamePasswordAuthenticationFilter
        else {
            return super.attemptAuthentication(request, response);
        }
    }
}

           

JwtTokenUtil

生成JwtToken的工具类,这里可以使用任意生成token的方法,能生成也能验证身份即可

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;

import java.text.ParseException;
import java.util.*;
import java.util.Date;
import java.util.Map;

/**
 * 2019/10/29 3:12 PM
 *
 * @author: zhouximin
 * @Description:
 **/
@Slf4j
public class JwtTokenUtil {
    /**
     * 1.创建一个32-byte的密匙
     */

    private static final byte[] secret = "weffffgukveeloginsdsawdwadsadsfrethrgqwsadsarthyhewwqfewhgefwfds".getBytes();


    //生成一个token
    public static String creatTokenHS256(Map<String,Object> payloadMap) throws JOSEException {
        //3.先建立一个头部Header
        /**
         * JWSHeader参数:1.加密算法法则,2.类型,3.。。。。。。。
         * 一般只需要传入加密算法法则就可以。
         * 这里则采用HS256
         *
         * JWSAlgorithm类里面有所有的加密算法法则,直接调用。
         */
        JWSHeader jwsHeader = new JWSHeader(JWSAlgorithm.HS256);

        //建立一个载荷Payload
        Payload payload = new Payload(new JSONObject(payloadMap));

        //将头部和载荷结合在一起
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);

        //建立一个密匙

        JWSSigner jwsSigner = new MACSigner(secret);

        //签名
        jwsObject.sign(jwsSigner);
        //生成token
        return jwsObject.serialize();
    }

    //解析token
    public static Map<String,Object> validHS256(String token) throws ParseException, JOSEException {
//        解析token
        JWSObject jwsObject = JWSObject.parse(token);
        //建立一个解锁密匙
        JWSVerifier jwsVerifier = new MACVerifier(secret);
        return verify(jwsObject, jwsVerifier);
    }


    //验证token信息
    private static Map<String,Object> verify(JWSObject jwsObject,JWSVerifier jwsVerifier) throws JOSEException {
        Map<String, Object> resultMap = new HashMap<>();
        //获取到载荷
        Payload payload=jwsObject.getPayload();
        //判断token
        if (jwsObject.verify(jwsVerifier)) {
            resultMap.put("Result", 0);
            //载荷的数据解析成json对象。
            JSONObject jsonObject = payload.toJSONObject();
            resultMap.put("data", jsonObject);
            //判断token是否过期
            if (jsonObject.containsKey("exp")) {
                Long expTime = Long.valueOf(jsonObject.get("exp").toString());
                Long nowTime = new Date().getTime() / 1000;
                //判断是否过期
                if (nowTime > expTime) {
                    //已经过期
                    resultMap.clear();
                    resultMap.put("Result", 2);
                }
            }
        }else {
            resultMap.put("Result", 1);
        }
        return resultMap;
    }

    /**
     * 创建加密key
     */
    public static RSAKey getKey() throws JOSEException {
        RSAKeyGenerator rsaKeyGenerator = new RSAKeyGenerator(2048);
        RSAKey rsaJWK = rsaKeyGenerator.generate();
        return rsaJWK;
    }

    //进行token加密
    public static String creatTokenRS256(Map<String,Object> payloadMap,RSAKey rsaJWK) throws JOSEException {

        //私密钥匙
        JWSSigner signer = new RSASSASigner(rsaJWK);

        JWSObject jwsObject = new JWSObject(
                new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(rsaJWK.getKeyID()).build(),
                new Payload(new JSONObject(payloadMap))
        );
        //进行加密
        jwsObject.sign(signer);

        String token= jwsObject.serialize();
        return token;
    }

    //验证token
    public static Map<String,Object> validRS256(String token,RSAKey rsaJWK) throws ParseException, JOSEException {
        //获取到公钥
        RSAKey rsaKey = rsaJWK.toPublicJWK();
        JWSObject jwsObject = JWSObject.parse(token);
        JWSVerifier jwsVerifier = new RSASSAVerifier(rsaKey);
        //验证数据
        return verify(jwsObject, jwsVerifier);
    }

}

           

JwtAuthenticationTokenFilter

主要用于验证token

import com.model.dto.UserDTO;
import com.service.UserService;
import com.service.impl.MyUserDetailsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 2019/10/29 3:21 PM
 *
 * @author: zhouximin
 * @Description:
 **/
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    MyUserDetailsService userDetailsService;
    @Autowired
    LoginProvider loginProvider;
    @Autowired
    UserService userService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
    	// 验证token并返回对应的用户信息,自行实现即可
        UserDTO user = userService.check(request);
        if (user != null) {
            String mobile = user.getMobile();

            if (mobile != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(mobile);

                if (userDetails != null) {
                    UsernamePasswordAuthenticationToken authentication =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}


           

CorsFilter

前后端分离的项目不可避免的会遇见跨域相关问题,这里就是为了跨域所做的

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;
import java.util.List;


/**
 * 2019/10/30 8:55 PM
 *
 * @author: zhouximin
 * @Description:
 **/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AjaxCorsFilter extends CorsFilter {
    public AjaxCorsFilter() {
        super(configurationSource());
    }

    private static UrlBasedCorsConfigurationSource configurationSource() {
        CorsConfiguration corsConfig = new CorsConfiguration();
        List<String> allowedHeaders = Arrays.asList("x-auth-token", "content-type", "X-Requested-With", "XMLHttpRequest", "Authorization");
        List<String> exposedHeaders = Arrays.asList("x-auth-token", "content-type", "X-Requested-With", "XMLHttpRequest", "Authorization");
        List<String> allowedMethods = Arrays.asList("POST", "GET", "DELETE", "PUT", "OPTIONS");
        //不使用*
        List<String> allowedOrigins = Arrays.asList("http://.....");
        corsConfig.setAllowedHeaders(allowedHeaders);
        corsConfig.setAllowedMethods(allowedMethods);
        corsConfig.setAllowedOrigins(allowedOrigins);
        corsConfig.setExposedHeaders(exposedHeaders);
        corsConfig.setMaxAge(36000L);
        corsConfig.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfig);
        return source;
    }
}