angularjs - example - spring boot cors allow all origins
CORS con arranque de resorte y angularjs no funcionan (11)
Estoy tratando de llamar a los puntos finales REST en una aplicación (aplicación spring-boot) desde otra (angularjs). Las aplicaciones se ejecutan en los siguientes hosts y puertos.
-
Aplicación REST, utilizando arranque de primavera,
http://localhost:8080
-
Aplicación HTML, utilizando angularjs,
http://localhost:50029
También estoy usando
spring-security
con la aplicación spring-boot.
Desde la aplicación HTML, puedo autenticarme en la aplicación REST, pero, a partir de entonces, aún no puedo acceder a ningún punto final REST.
Por ejemplo, tengo un servicio angularjs definido de la siguiente manera.
adminServices.factory(''AdminService'', [''$resource'', ''$http'', ''conf'', function($resource, $http, conf) {
var s = {};
s.isAdminLoggedIn = function(data) {
return $http({
method: ''GET'',
url: ''http://localhost:8080/api/admin/isloggedin'',
withCredentials: true,
headers: {
''X-Requested-With'': ''XMLHttpRequest''
}
});
};
s.login = function(username, password) {
var u = ''username='' + encodeURI(username);
var p = ''password='' + encodeURI(password);
var r = ''remember_me=1'';
var data = u + ''&'' + p + ''&'' + r;
return $http({
method: ''POST'',
url: ''http://localhost:8080/login'',
data: data,
headers: {''Content-Type'': ''application/x-www-form-urlencoded''}
});
};
return s;
}]);
El controlador angularjs tiene el siguiente aspecto.
adminControllers.controller(''LoginController'', [''$scope'', ''$http'', ''AdminService'', function($scope, $http, AdminService) {
$scope.username = '''';
$scope.password = '''';
$scope.signIn = function() {
AdminService.login($scope.username, $scope.password)
.success(function(d,s) {
if(d[''success'']) {
console.log(''ok authenticated, call another REST endpoint'');
AdminService.isAdminLoggedIn()
.success(function(d,s) {
console.log(''i can access a protected REST endpoint after logging in'');
})
.error(function(d, s) {
console.log(''huh, error checking to see if admin is logged in'');
$scope.reset();
});
} else {
console.log(''bad credentials?'');
}
})
.error(function(d, s) {
console.log(''huh, error happened!'');
});
};
}]);
En la llamada a
http://localhost:8080/api/admin/isloggedin
, recibo un
401 Unauthorized
.
En el lado de la aplicación REST, tengo un filtro CORS que se parece a lo siguiente.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSFilter implements Filter {
@Override
public void destroy() { }
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "http://localhost:50029");
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, X-Auth-Token");
response.setHeader("Access-Control-Allow-Credentials", "true");
if(!"OPTIONS".equalsIgnoreCase(request.getMethod())) {
chain.doFilter(req, res);
}
}
@Override
public void init(FilterConfig config) throws ServletException { }
}
Mi configuración de seguridad de primavera se parece a la siguiente.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
private JsonAuthSuccessHandler jsonAuthSuccessHandler;
@Autowired
private JsonAuthFailureHandler jsonAuthFailureHandler;
@Autowired
private JsonLogoutSuccessHandler jsonLogoutSuccessHandler;
@Autowired
private AuthenticationProvider authenticationProvider;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PersistentTokenRepository persistentTokenRepository;
@Value("${rememberme.key}")
private String rememberMeKey;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/", "/admin", "/css/**", "/js/**", "/fonts/**", "/api/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.successHandler(jsonAuthSuccessHandler)
.failureHandler(jsonAuthFailureHandler)
.permitAll()
.and()
.logout()
.deleteCookies("remember-me", "JSESSIONID")
.logoutSuccessHandler(jsonLogoutSuccessHandler)
.permitAll()
.and()
.rememberMe()
.userDetailsService(userDetailsService)
.tokenRepository(persistentTokenRepository)
.rememberMeCookieName("REMEMBER_ME")
.rememberMeParameter("remember_me")
.tokenValiditySeconds(1209600)
.useSecureCookie(false)
.key(rememberMeKey);
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(authenticationProvider);
}
}
Todo lo que están haciendo los controladores es escribir una respuesta JSON como
{success: true}
función de si el usuario inició sesión, no pudo autenticarse o cerró la sesión.
RestAuthenticationEntryPoint
tiene el siguiente aspecto.
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException ex)
throws IOException, ServletException {
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
¿Alguna idea sobre lo que me falta o estoy haciendo mal?
Esto es lo que funcionó para mí.
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
}
}
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedMethods("*")
.allowedHeaders("*")
.allowedOrigins("*")
.allowCredentials(true);
}
}
Esto funciona para mi:
@Configuration
public class MyConfig extends WebSecurityConfigurerAdapter {
//...
@Override
protected void configure(HttpSecurity http) throws Exception {
//...
http.cors().configurationSource(new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedHeaders(Collections.singletonList("*"));
config.setAllowedMethods(Collections.singletonList("*"));
config.addAllowedOrigin("*");
config.setAllowCredentials(true);
return config;
}
});
//...
}
//...
}
Estoy usando
spring boot 2.1.0
y lo que funcionó para mí fue
A. Agregar asignaciones de cors por:
@Configuration
public class Config implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
}
B. Agregue la siguiente configuración a mi
HttpSecurity
para la seguridad de primavera
.cors().configurationSource(new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedHeaders(Collections.singletonList("*"));
config.setAllowedMethods(Collections.singletonList("*"));
config.addAllowedOrigin("*");
config.setAllowCredentials(true);
return config;
}
})
También en el caso de un proxy Zuul, puede usar este
EN LUGAR DE A y B
(solo use
HttpSecurity.cors()
para habilitarlo en Spring Security):
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
Extender la clase WebSecurityConfigurerAdapter y anular el método configure () en su clase @EnableWebSecurity funcionaría: a continuación se muestra la clase de muestra
@Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.exceptionHandling();
http.headers().cacheControl();
@Override
public CorsConfiguration getCorsConfiguration(final HttpServletRequest request) {
return new CorsConfiguration().applyPermitDefaultValues();
}
});
}
}
Había estado en una situación similar. Después de investigar y probar, aquí están mis hallazgos:
-
Con Spring Boot, la forma recomendada de habilitar CORS global es declarar dentro de Spring MVC y combinarlo con la configuración detallada de
@CrossOrigin
como:@Configuration public class CorsConfig { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurerAdapter() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE").allowedOrigins("*") .allowedHeaders("*"); } }; } }
-
Ahora, dado que está utilizando Spring Security, también debe habilitar CORS en el nivel de Spring Security para permitirle aprovechar la configuración definida en el nivel de Spring MVC como:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and()... } }
Here hay un excelente tutorial que explica el soporte de CORS en Spring MVC framework.
Para construir sobre otras respuestas anteriores, en caso de que tenga una aplicación de servicio REST de arranque Spring (no Spring MVC) con seguridad Spring, entonces habilitar CORS a través de la seguridad Spring es suficiente (si usa Spring MVC, entonces usar un bean
WebMvcConfigurer
como lo menciona Yogen podría sea el camino a seguir ya que Spring Security delegará a la definición CORS mencionada allí)
Por lo tanto, debe tener una configuración de seguridad que haga lo siguiente:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//other http security config
http.cors().configurationSource(corsConfigurationSource());
}
//This can be customized as required
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
List<String> allowOrigins = Arrays.asList("*");
configuration.setAllowedOrigins(allowOrigins);
configuration.setAllowedMethods(singletonList("*"));
configuration.setAllowedHeaders(singletonList("*"));
//in case authentication is enabled this flag MUST be set, otherwise CORS requests will fail
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Este enlace tiene más información sobre el mismo: https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#cors
Nota:
- Habilitar CORS para todos los orígenes (*) para una aplicación prod desplegada puede no ser siempre una buena idea.
- CSRF se puede habilitar a través de la personalización Spring HttpSecurity sin ningún problema
-
En caso de que tenga habilitada la autenticación en la aplicación con Spring (a través de un
UserDetailsService
por ejemplo), entonces laconfiguration.setAllowCredentials(true);
debe ser agregado
Probado para Spring boot 2.0.0.RELEASE (es decir, Spring 5.0.4.RELEASE y Spring security 5.0.3.RELEASE)
Para mí, lo único que funcionó al 100% cuando se usó seguridad de primavera fue omitir toda la pelusa adicional de filtros y frijoles adicionales y cualquier cosa "mágica" indirecta que la gente sugiriera que funcionó para ellos, pero no para mí.
En lugar de eso, simplemente forzarlo a escribir los encabezados que necesita con un
StaticHeadersWriter
simple:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// your security config here
.authorizeRequests()
.antMatchers(HttpMethod.TRACE, "/**").denyAll()
.antMatchers("/admin/**").authenticated()
.anyRequest().permitAll()
.and().httpBasic()
.and().headers().frameOptions().disable()
.and().csrf().disable()
.headers()
// the headers you want here. This solved all my CORS problems!
.addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Origin", "*"))
.addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Methods", "POST, GET"))
.addHeaderWriter(new StaticHeadersWriter("Access-Control-Max-Age", "3600"))
.addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Credentials", "true"))
.addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Headers", "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization"));
}
}
Esta es la forma más directa y explícita que encontré para hacerlo. Espero que ayude a alguien.
Si desea habilitar CORS sin usar filtros o sin el archivo de configuración, simplemente agregue
@CrossOrigin
a la parte superior de su controlador y funciona.
Si originalmente su programa no usa seguridad de primavera y no puede permitirse un cambio de código, crear un proxy inverso simple puede ser la solución. En mi caso, usé Nginx con la siguiente configuración:
http {
server {
listen 9090;
location / {
if ($request_method = ''OPTIONS'') {
add_header ''Access-Control-Allow-Origin'' ''*'';
add_header ''Access-Control-Allow-Methods'' ''GET, POST, OPTIONS'';
#
# Custom headers and headers various browsers *should* be OK with but aren''t
#
add_header ''Access-Control-Allow-Headers'' ''DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'';
#
# Tell client that this pre-flight info is valid for 20 days
#
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 = ''POST'') {
add_header ''Access-Control-Allow-Origin'' ''*'';
add_header ''Access-Control-Allow-Methods'' ''GET, POST, OPTIONS'';
add_header ''Access-Control-Allow-Headers'' ''DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'';
add_header ''Access-Control-Expose-Headers'' ''Content-Length,Content-Range'';
}
if ($request_method = ''GET'') {
add_header ''Access-Control-Allow-Origin'' ''*'';
add_header ''Access-Control-Allow-Methods'' ''GET, POST, OPTIONS'';
add_header ''Access-Control-Allow-Headers'' ''DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'';
add_header ''Access-Control-Expose-Headers'' ''Content-Length,Content-Range'';
}
proxy_pass http://localhost:8080;
}
}
}
Mi programa escucha : 8080 .
REF: CORS en Nginx
revisa este:
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
...
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
...
}
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class SimpleCORSFilter implements Filter {
private final Logger log = LoggerFactory.getLogger(SimpleCORSFilter.class);
public SimpleCORSFilter() {
log.info("SimpleCORSFilter init");
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me");
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}
No es necesario definir este filtro adicional simplemente agregue esta clase. Spring escaneará y lo agregará por usted. SimpleCORSFilter. Aquí está el ejemplo: spring-enable-cors