# 引言
Spring Security
它是Spring
家族中的一个安全框架,主要用于保护我们的应用程序,它提供了许多安全特性,如身份验证、授权、加密、会话管理、访问控制等。本文将介绍Spring Security
的 6.3.1 版本的安全配置。
# Spring Security 简介
Spring Security
的最新版本是 6.3.1,本文将介绍 6.3.1 版本的安全配置。
Spring Security
的主要特性:- 认证与授权:提供多种认证方式和权限管理功能。
- 攻击防护:防御常见的攻击,如跨站请求伪造
(CSRF)
、点击劫持等。 - 灵活配置:通过
Java
配置、XML
配置等多种方式进行灵活配置。 - 集成支持:支持与多种身份验证协议(如
OAuth2
、JWT
)和第三方服务的集成。
# Spring Security 依赖
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-security</artifactId> | |
</dependency> |
# 环境准备
- 工具和版本
JDK
:11
或更高版本Spring Boot
: 最新稳定版本Maven/Gradle
: 用于项目构建和依赖管理IDE
: 如IntelliJ IDEA
或Eclipse
- 创建
Spring Boot
项目- 使用
Spring Initializr
创建一个新的Spring Boot
项目。 - 选择
"Spring Security"
作为依赖项。 - 下载并导入项目到你的
IDE
。
- 使用
# 安全配置
# 实现 UserDetails 接口
首先我们需要先配置自己的
UserBean
, 将自己的User
实体类实现UserDetails
接口,并在SecurityConfig
中配置UserDetailsService
,UserDetails
接口提供了获取用户信息的方法,包括用户名、密码、权限等。
package com.fairiy.magic.magicbackend.bean; | |
import lombok.Data; | |
import lombok.Getter; | |
import lombok.Setter; | |
import lombok.ToString; | |
import org.springframework.security.core.GrantedAuthority; | |
import org.springframework.security.core.userdetails.UserDetails; | |
import java.sql.Date; | |
import java.util.List; | |
/** | |
* @Author: LightRain | |
* @Description: User 实体类,实现 UserDeails 接口 | |
* @DateTime: 2023-04-01 16:37 | |
* @Version:1.0 | |
**/ | |
@Data | |
public class User implements UserDetails { | |
// id | |
private int id; | |
// 用户名 | |
private String username; | |
// 密码 | |
private String password; | |
// 头像地址 | |
private String avatar_address; | |
// 性别 | |
private String gender; | |
// 角色 | |
private String role; | |
// 状态 | |
private String user_status; | |
// 有效时间 | |
private Date valid_time; | |
// 更新时间 | |
private Date update_time; | |
// 创建时间 | |
private Date create_time; | |
// 是否删除(0 - 未删,1 - 已删) | |
private int is_deleted; | |
private List<GrantedAuthority> authorities; | |
@Override | |
public String getUsername() { | |
return username; | |
} | |
@Override | |
public boolean isAccountNonExpired() { | |
return true; | |
} | |
@Override | |
public boolean isAccountNonLocked() { | |
return true; | |
} | |
@Override | |
public boolean isCredentialsNonExpired() { | |
return true; | |
} | |
@Override | |
public boolean isEnabled() { | |
return true; | |
} | |
} |
# 实现 UserDetailsService 接口
下面我们来实现登录权限的效验,来编写
SecurityServiceImpl
实现类,实现UserDetailsService
接口中的loadUserByUsername
方法,并在SecurityConfig
中配置AuthenticationManager
。
package com.fairiy.magic.magicbackend.service; | |
import com.fairiy.magic.magicbackend.bean.User; | |
import com.fairiy.magic.magicbackend.mapper.UserMapper; | |
import org.springframework.security.core.authority.AuthorityUtils; | |
import org.springframework.security.core.userdetails.UserDetails; | |
import org.springframework.security.core.userdetails.UserDetailsService; | |
import org.springframework.stereotype.Service; | |
/** | |
* Created by LightRain on 2024/7/30 下午 23:25. | |
* UserDetailsService 接口用于实现用户认证,使用 SpringSecurity 来处理授权请求 | |
*/ | |
@Service | |
public class SecurityServiceImpl implements UserDetailsService { | |
/** | |
* 注入 UserMapper | |
*/ | |
private final UserMapper userMapper; | |
public SecurityServiceImpl(UserMapper userMapper) { | |
this.userMapper = userMapper; | |
} | |
@Override | |
public UserDetails loadUserByUsername(String username) { | |
User user = userMapper.getUserName(username); | |
if (user != null && user.getUsername() != null && user.getPassword() != null) { | |
return new org.springframework.security.core.userdetails.User(username, user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList("")); | |
} | |
// 验证失败 | |
return new org.springframework.security.core.userdetails.User("t", "$2a$10$kiS2bbJHUiDINf466LuMae2kpoC0/iGvbmqNR7w7eiQhaEUlgw6Nq", AuthorityUtils.commaSeparatedStringToAuthorityList("")); | |
} | |
} |
# 配置 SecurityConfig 安全类
配置
SecurityConfig
类,这是6.3.1
版本的最新配置格式,旧版方法均已被弃用,新版配置如下:
package com.fairiy.magic.magicbackend.config; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.security.config.annotation.web.builders.HttpSecurity; | |
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | |
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | |
import org.springframework.security.crypto.password.PasswordEncoder; | |
import org.springframework.security.web.SecurityFilterChain; | |
import org.springframework.web.cors.CorsConfiguration; | |
import org.springframework.web.cors.CorsConfigurationSource; | |
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; | |
import java.util.Arrays; | |
import java.util.List; | |
/** | |
* @Author: LightRain | |
* @Description: Security 安全配置 | |
* @DateTime: 2024/7/30 下午 7:53 | |
* @Version:1.0 | |
**/ | |
@EnableWebSecurity | |
@Configuration | |
public class SecurityConfig { | |
/** | |
* @Author: LightRain | |
* @Date: 2024/7/30 下午 11:44 | |
* @Param: [httpSecurity] | |
* @Return: org.springframework.security.web.SecurityFilterChain | |
* @Description: SpringSecurity 配置 | |
* @Since 21 | |
*/ | |
@Bean | |
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { | |
// 默认开启 CSRF 防护 | |
httpSecurity | |
// 配置 CORS 从源策略 | |
.cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfigurationSource())) | |
// 开启表单登录 | |
.formLogin(formLogin -> { | |
// 登录页面,允许所有人访问 | |
formLogin.loginPage("/login").permitAll(); | |
// 登录成功后将请求转发到 successForwardUrl | |
formLogin.successForwardUrl("/login/success") | |
// 登录失败后转发到 failureForwardUrl | |
.failureForwardUrl("/login/failure").permitAll(); | |
}) | |
// 开启记住我 | |
.rememberMe(rememberMe -> rememberMe.tokenValiditySeconds(60 * 60 * 24 * 7)) | |
// 配置权限 | |
.authorizeHttpRequests(authorize -> { | |
// 配置白名单,允许访问 /public/**, 其他请求都需要认证 | |
authorize.requestMatchers("/public/**", "/login/**").permitAll().anyRequest().authenticated(); | |
}) | |
// 配置 session 管理 | |
.sessionManagement(sessionManagement -> { | |
//session 失效时跳转的 url | |
sessionManagement.invalidSessionUrl("/session/invalid"); | |
}) | |
// 配置异常处理 | |
.exceptionHandling(exceptionHandling -> { | |
// 身份验证入口点 | |
exceptionHandling.authenticationEntryPoint(new OAAuthenticationEntryPoint()); | |
// 未授权时跳转的 url | |
// exceptionHandling.accessDeniedHandler(new OAAccessDeniedHandler()); | |
}) | |
// 配置退出登录 | |
.logout(logout -> { | |
// 退出登录 url | |
logout.logoutUrl("/logout") | |
// 退出成功后跳转的 url | |
.logoutSuccessUrl("/login") | |
// 是否清除 HttpSession | |
.invalidateHttpSession(true) | |
// 删除对应 cookie | |
.deleteCookies("JSESSIONID"); | |
}); | |
return httpSecurity.build(); | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 2024/7/30 下午 11:49 | |
* @Param: [] | |
* @Return: org.springframework.web.cors.CorsConfigurationSource | |
* @Description: CORS 从源策略配置 | |
* @Since 21 | |
*/ | |
public CorsConfigurationSource corsConfigurationSource() { | |
CorsConfiguration corsConfiguration = new CorsConfiguration(); | |
// 允许跨域访问的站点 | |
corsConfiguration.setAllowedOrigins(List.of("http://localhost:5170","http://localhost:4500", "http://localhost:80")); | |
// 允许跨域访问的 methods | |
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST")); | |
// 允许携带凭证 | |
corsConfiguration.setAllowCredentials(true); | |
corsConfiguration.setAllowedHeaders(List.of("*")); | |
// 基于 url 的 cors 配置源 | |
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); | |
// 对所有 URL 生效 | |
source.registerCorsConfiguration("/**", corsConfiguration); | |
return source; | |
} | |
/** | |
* 配置从源策略 | |
*/ | |
// public CorsConfigurationSource corsConfigurationSource() { | |
// CorsConfiguration corsConfiguration = new CorsConfiguration(); | |
// corsConfiguration.addAllowedOrigin("http://localhost:4500"); | |
// corsConfiguration.addAllowedMethod ("*"); // 允许所有方法 | |
// corsConfiguration.addAllowedHeader("Content-Type"); | |
// corsConfiguration.addAllowedHeader("Authorization"); | |
// corsConfiguration.addAllowedHeader("Access-Control-Allow-Origin"); | |
// corsConfiguration.addAllowedHeader("x-csrf-token"); | |
// corsConfiguration.setAllowCredentials(true); | |
// // 基于 url 的 cors 配置源 | |
// UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); | |
// // 对所有 URL 生效 | |
// source.registerCorsConfiguration("/**", corsConfiguration); | |
// return source; | |
// } | |
/** | |
* @Author: LightRain | |
* @Date: 2024/7/30 下午 11:50 | |
* @Param: [] | |
* @Return: org.springframework.security.crypto.password.PasswordEncoder | |
* @Description: 配置密码加密器 | |
* @Since 21 | |
*/ | |
@Bean | |
public PasswordEncoder passwordEncoder() { | |
return new BCryptPasswordEncoder(); | |
} | |
} |
# 实现身份验证入口点
实现 OA 身份验证入口点
OAAuthenticationEntryPoint
,用于处理身份验证失败的情况,如用户名或密码错误等。
package com.fairiy.magic.magicbackend.config; | |
import com.fairiy.magic.magicbackend.bean.Return; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import jakarta.servlet.http.HttpServletRequest; | |
import jakarta.servlet.http.HttpServletResponse; | |
import org.springframework.security.core.AuthenticationException; | |
import org.springframework.security.web.AuthenticationEntryPoint; | |
import java.io.IOException; | |
import java.io.PrintWriter; | |
/** | |
* @Author: LightRain | |
* @Description: OA 身份验证入口点 | |
* @DateTime: 2024/7/30 下午 7:52 | |
* @Version:1.0 | |
**/ | |
public class OAAuthenticationEntryPoint implements AuthenticationEntryPoint { | |
@Override | |
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse response, AuthenticationException e) throws IOException { | |
response.setContentType("application/json;charset=UTF-8"); | |
PrintWriter out = response.getWriter(); | |
Return notAuthorized = Return.builder().code(401).message("尚未授权").status(Return.FAILED).build(); | |
out.write(new ObjectMapper().writeValueAsString(notAuthorized)); | |
out.flush(); | |
out.close(); | |
} | |
} |
实现
Return
类,用于返回统一的返回信息。
package com.fairiy.magic.magicbackend.bean; | |
import lombok.Builder; | |
import lombok.Getter; | |
import lombok.Setter; | |
import lombok.ToString; | |
/** | |
* @Author: LightRain | |
* @Description: 统一返回信息 | |
* @DateTime: 2023-03-29 12:55 | |
* @Version:1.0 | |
**/ | |
@Getter | |
@Setter | |
@Builder | |
@ToString | |
public class Return { | |
// 状态码 | |
private int code; | |
// 返回信息 | |
private String message; | |
// 返回数据 | |
private String status; | |
// 成功 | |
public static final String SUCCESS = "SUCCESS"; | |
// 失败 | |
public static final String FAILED = "FAILED"; | |
} |
# 实现登录接口
实现登录接口,用于处理用户登录请求,并返回登录结果。
package com.fairiy.magic.magicbackend.controller; | |
import com.fairiy.magic.magicbackend.bean.Return; | |
import com.fairiy.magic.magicbackend.service.LoginService; | |
import jakarta.servlet.http.HttpServletRequest; | |
import jakarta.servlet.http.HttpServletResponse; | |
import jakarta.servlet.http.HttpSession; | |
import org.springframework.security.core.Authentication; | |
import org.springframework.security.core.context.SecurityContextHolder; | |
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.RequestMethod; | |
import org.springframework.web.bind.annotation.ResponseBody; | |
import org.springframework.web.bind.annotation.RestController; | |
/** | |
* @Author: LightRain | |
* @Description: 登录控制器 | |
* @DateTime: 2024/7/30 下午 21:10 | |
* @Version:1.0 | |
**/ | |
@ResponseBody | |
@RestController | |
@RequestMapping("/login") | |
public class LoginController { | |
private final LoginService service; | |
public LoginController(LoginService service) { | |
this.service = service; | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 2024/8/31 下午 4:48 | |
* @Param: [session, username, captcha] | |
* @Return: com.fairiy.magic.magicbackend.bean.Return | |
* @Description: 登录成功 | |
* @Since 21 | |
*/ | |
@RequestMapping(value = "/success", method = {RequestMethod.GET, RequestMethod.POST}) | |
public Return loginSuccess(HttpSession session, String username, String captcha) { | |
return service.loginSuccess(session, username, captcha); | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 2024/8/31 下午 4:48 | |
* @Param: [] | |
* @Return: com.fairiy.magic.magicbackend.bean.Return | |
* @Description: 登录失败 | |
* @Since 21 | |
*/ | |
@RequestMapping(value = "/failure", method = {RequestMethod.GET, RequestMethod.POST}) | |
public Return loginFailure() { | |
return Return.builder().code(401).message("授权失败").status(Return.FAILED).build(); | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 2024/8/31 下午 4:48 | |
* @Param: [request, response] | |
* @Return: java.lang.String | |
* @Description: 退出登录 | |
* @Since 21 | |
*/ | |
@RequestMapping(value = "/logout", method = {RequestMethod.GET, RequestMethod.POST}) | |
public String logoutPage(HttpServletRequest request, HttpServletResponse response) { | |
Authentication auth = SecurityContextHolder.getContext().getAuthentication(); | |
if (auth != null) { | |
new SecurityContextLogoutHandler().logout(request, response, auth); | |
} | |
return "redirect:/login"; | |
} | |
} |
package com.fairiy.magic.magicbackend.service; | |
import com.fairiy.magic.magicbackend.bean.Return; | |
import jakarta.servlet.http.HttpSession; | |
/** | |
* Created by LightRain on 2024/07/30 23:30. | |
* 登录服务接口 | |
*/ | |
public interface LoginService { | |
Return loginSuccess(HttpSession session, String username, String captcha); | |
} |
package com.fairiy.magic.magicbackend.service; | |
import com.fairiy.magic.magicbackend.bean.Return; | |
import com.fairiy.magic.magicbackend.mapper.UserMapper; | |
import jakarta.servlet.http.HttpSession; | |
import org.springframework.stereotype.Service; | |
/** | |
* @Author: LightRain | |
* @Description: 登录服务实现类,用于处理登录逻辑,使用 SpringSecurity 来处理授权请求 | |
* @DateTime: 2024/7/30 下午 23:18 | |
* @Version:1.0 | |
**/ | |
@Service | |
public class LoginServiceImpl implements LoginService { | |
// 注入 UserMapper | |
private final UserMapper userMapper; | |
public LoginServiceImpl(UserMapper userMapper) { | |
this.userMapper = userMapper; | |
} | |
// 登录成功,可以在此添加验证码验证等逻辑 | |
@Override | |
public Return loginSuccess(HttpSession session, String username, String captcha) { | |
System.out.println("login success"); | |
session.setAttribute("User", userMapper.getUserName(username)); | |
return Return.builder().code(200).message("授权成功").status(Return.SUCCESS).build(); | |
} | |
} |
# 实现 Token 认证
实现 Token 认证,用于处理用户登录请求,并返回登录结果。
package com.fairiy.magic.magicbackend.controller; | |
import cn.hutool.core.codec.Base64; | |
import com.fairiy.magic.magicbackend.bean.Register; | |
import com.fairiy.magic.magicbackend.bean.Return; | |
import com.fairiy.magic.magicbackend.service.PublicService; | |
import com.google.code.kaptcha.Producer; | |
import jakarta.servlet.http.HttpServletRequest; | |
import jakarta.servlet.http.HttpServletResponse; | |
import org.apache.commons.io.output.ByteArrayOutputStream; | |
import org.springframework.security.web.csrf.CsrfToken; | |
import org.springframework.stereotype.Controller; | |
import org.springframework.web.bind.annotation.GetMapping; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.ResponseBody; | |
import javax.imageio.ImageIO; | |
import java.awt.image.BufferedImage; | |
import java.io.IOException; | |
/** | |
* @Author: LightRain | |
* @Description: 不登录就可以访问的公开控制器 | |
* @DateTime: 2024/7/30 下午 8:13 | |
* @Version:1.0 | |
**/ | |
@Controller | |
@RequestMapping("/public") | |
public class PublicController { | |
private final PublicService publicService; | |
private final Producer kaptchaProducer; | |
public PublicController(PublicService publicService, Producer kaptchaProducer) { | |
this.publicService = publicService; | |
this.kaptchaProducer = kaptchaProducer; | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 2024/8/21 下午 6:42 | |
* @Param: [request] | |
* @Return: org.springframework.security.web.csrf.CsrfToken | |
* @Description: 获取 csrfToken | |
* @Since 21 | |
*/ | |
@ResponseBody | |
@GetMapping("/csrf-token") | |
public CsrfToken csrfToken(HttpServletRequest request) { | |
CsrfToken attribute = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); | |
System.out.println(attribute.getToken()); | |
return attribute; | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 2024/8/21 下午 4:05 | |
* @Param: [request, activationCode] | |
* @Return: com.fairiy.magic.magicbackend.bean.Return | |
* @Description: 注册接口 | |
* @Since 21 | |
*/ | |
@ResponseBody | |
@RequestMapping("/register") | |
public Return register(Register request,String activationCode) { | |
return publicService.registerAccount(request,activationCode); | |
} | |
/** | |
* @Author: LightRain | |
* @Date: 2024/8/21 下午 6:42 | |
* @Param: [request, response] | |
* @Return: com.fairiy.magic.magicbackend.bean.Return | |
* @Description: 生成验证码,暂时弃用 | |
* @Since 21 | |
*/ | |
@Deprecated | |
@ResponseBody | |
@GetMapping("/createKaptchaCodeImg") | |
public Return createKaptchaCode(HttpServletRequest request, HttpServletResponse response) throws IOException { | |
request.getSession().removeAttribute("kaptchaCode"); | |
// 设置浏览器缓存机制 | |
response.setHeader("Cache-Control", "no-store, no-cache"); | |
// 设置返回响应类型 | |
response.setContentType("image/jpeg"); | |
// 生成验证码 | |
String code = kaptchaProducer.createText(); | |
// 保存验证码到 session | |
request.getSession().setAttribute("kaptchaCode", code); | |
// 生成图片验证码 | |
BufferedImage image = kaptchaProducer.createImage(code); | |
// 转为 Base64 | |
ByteArrayOutputStream stream = new ByteArrayOutputStream(); | |
ImageIO.write(image, "png", stream); | |
String imgString = "data:image/gif;base64," + Base64.encode(stream.toByteArray()); | |
stream.flush(); | |
stream.close(); | |
// 将数据存入并将其返回 | |
return Return.builder().code(200).message(imgString).status(Return.SUCCESS).build(); | |
} | |
} |
# 实现 Session 管理
实现 Session 管理,用于处理用户登录请求,并返回登录结果。
server: | |
port: 5170 | |
servlet: | |
session: | |
timeout: 3600 #session Effective time is one hour 3600s | |
spring: | |
datasource: | |
url: jdbc:mysql://localhost:3306/magic_journey?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC | |
username: root | |
password: 123456 | |
driver-class-name: com.mysql.cj.jdbc.Driver |
package com.fairiy.magic.magicbackend.controller; | |
import com.fairiy.magic.magicbackend.bean.Return; | |
import jakarta.servlet.http.HttpSession; | |
import org.springframework.http.HttpStatus; | |
import org.springframework.stereotype.Controller; | |
import org.springframework.web.bind.annotation.GetMapping; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.ResponseBody; | |
import org.springframework.web.bind.annotation.ResponseStatus; | |
/** | |
* @Author: LightRain | |
* @Description: Session 控制器 | |
* @DateTime: 2024/7/30 下午 21:15 | |
* @Version:1.0 | |
**/ | |
@Controller | |
public class SessionController { | |
@ResponseBody | |
@GetMapping("session/invalid") | |
@ResponseStatus(HttpStatus.UNAUTHORIZED) | |
public String sessionInvalid(HttpSession session) { | |
session.removeAttribute("User"); | |
return "session已失效,请重新认证"; | |
} | |
@ResponseBody | |
@RequestMapping("session/status") | |
public Return sessionStatus(HttpSession session) { | |
if (session.getAttribute("User") != null) { | |
return Return.builder().code(200).message("授权成功").status(Return.SUCCESS).build(); | |
} | |
return Return.builder().code(401).message("尚未授权").status(Return.FAILED).build(); | |
} | |
} |
# 实现 MVC-CORS 配置
package com.fairiy.magic.magicbackend.config; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.web.servlet.config.annotation.CorsRegistry; | |
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | |
/** | |
* @Author: LightRain | |
* @Description: Cors 从源策略配置 | |
* @DateTime: 2023-04-01 17:51 | |
* @Version:1.0 | |
**/ | |
@Configuration | |
public class CorsConfig implements WebMvcConfigurer { | |
/** | |
* @Author: LightRain | |
* @Date: 3/4/2023 下午 1:55 | |
* @Param: [registry] | |
* @Return: void | |
* @Description: 添加 Cors 从源策略映射 | |
* @since 17 | |
*/ | |
@Override | |
public void addCorsMappings(CorsRegistry registry) { | |
/* | |
addMapping ("/**"): 设置允许跨域的路径 | |
allowedOriginPatterns ("*"): 设置允许跨域请求的域名 | |
allowCredentials (true): 是否允许 cookie | |
allowedMethods ("GET", "POST", "DELETE", "PUT"): 设置允许的请求方式 | |
allowedHeaders ("*"): 设置允许的 header 属性 | |
maxAge (3600): 跨域允许时间 | |
*/ | |
registry.addMapping("/**") | |
.allowedOriginPatterns("*") | |
.allowCredentials(true) | |
.allowedMethods("GET", "POST", "DELETE", "PUT") | |
.allowedHeaders("*") | |
.maxAge(3600); | |
} | |
} |