本文章适合对Spring Security有一定了解的同学阅读。
1. 登陆流程
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxSP9EUT0EERORTUE1UNRRlY0A3MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZwpmL0QDO2MjMycTM5AjMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
项目中需要后端进行登陆–>生成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;
}
}