J'essaie de faire en sorte que CORS joue bien avec Spring Security mais ce n'est pas conforme. J'ai apporté les modifications décrites dans cet article et la modification de cette ligne dans applicationContext-security.xml
A obtenu POST et les demandes GET fonctionnent pour mon application (expose temporairement les méthodes du contrôleur) , donc je peux tester CORS):
<intercept-url pattern="/**" access="isAuthenticated()" />
<intercept-url pattern="/**" access="permitAll" />
Malheureusement, l'URL suivante qui autorise les connexions Spring Security via AJAX ne répond pas: http://localhost:8080/mutopia-server/resources/j_spring_security_check
. Je fais la demande AJAX de http://localhost:80
À http://localhost:8080
.
Lorsque j'essaie d'accéder à j_spring_security_check
, J'obtiens (pending)
Dans Chrome pour la demande de contrôle en amont OPTIONS et AJAX l'appel renvoie avec l'état HTTP) code 0 et message "erreur".
Le contrôle en amont réussit avec le code d'état HTTP 302 et j'obtiens toujours le rappel d'erreur pour ma AJAX directement après avec l'état HTTP 0 et le message "erreur".
function get(url, json) {
var args = {
type: 'GET',
url: url,
// async: false,
// crossDomain: true,
xhrFields: {
withCredentials: false
},
success: function(response) {
console.debug(url, response);
},
error: function(xhr) {
console.error(url, xhr.status, xhr.statusText);
}
};
if (json) {
args.contentType = 'application/json'
}
$.ajax(args);
}
function post(url, json, data, dataEncode) {
var args = {
type: 'POST',
url: url,
// async: false,
crossDomain: true,
xhrFields: {
withCredentials: false
},
beforeSend: function(xhr){
// This is always added by default
// Ignoring this prevents preflight - but expects browser to follow 302 location change
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader("X-Ajax-call", "true");
},
success: function(data, textStatus, xhr) {
// var location = xhr.getResponseHeader('Location');
console.error('success', url, xhr.getAllResponseHeaders());
},
error: function(xhr) {
console.error(url, xhr.status, xhr.statusText);
console.error('fail', url, xhr.getAllResponseHeaders());
}
}
if (json) {
args.contentType = 'application/json'
}
if (typeof data != 'undefined') {
// Send JSON raw in the body
args.data = dataEncode ? JSON.stringify(data) : data;
}
console.debug('args', args);
$.ajax(args);
}
var loginJSON = {"j_username": "username", "j_password": "password"};
// Fails
post('http://localhost:8080/mutopia-server/resources/j_spring_security_check', false, loginJSON, false);
// Works
post('http://localhost/mutopia-server/resources/j_spring_security_check', false, loginJSON, false);
// Works
get('http://localhost:8080/mutopia-server/landuses?projectId=6', true);
// Works
post('http://localhost:8080/mutopia-server/params', true, {
"name": "testing",
"local": false,
"generated": false,
"project": 6
}, true);
Veuillez noter - je peux POST vers toute autre URL de mon application via CORS à l'exception de la connexion Spring Security. J'ai parcouru beaucoup d'articles, donc tout aperçu de ce problème étrange serait grandement apprécié
J'ai pu le faire en étendant UsernamePasswordAuthenticationFilter ... mon code est dans Groovy, j'espère que c'est OK:
public class CorsAwareAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
static final String Origin = 'Origin'
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
if (request.getHeader(Origin)) {
String Origin = request.getHeader(Origin)
response.addHeader('Access-Control-Allow-Origin', Origin)
response.addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
response.addHeader('Access-Control-Allow-Credentials', 'true')
response.addHeader('Access-Control-Allow-Headers',
request.getHeader('Access-Control-Request-Headers'))
}
if (request.method == 'OPTIONS') {
response.writer.print('OK')
response.writer.flush()
return
}
return super.attemptAuthentication(request, response)
}
}
Les bits importants ci-dessus:
Vous devez déclarer ce bean dans votre configuration Spring. Il existe de nombreux articles montrant comment procéder, je ne les copierai pas ici.
Dans ma propre implémentation, j'utilise une liste blanche de domaine Origin car je n'autorise CORS que pour les développeurs internes. Ce qui précède est une version simplifiée de ce que je fais, il faudra peut-être l'ajuster, mais cela devrait vous donner une idée générale.
bien Ceci est mon code qui fonctionne très bien et parfait pour moi: j'ai passé deux jours à travailler dessus et à comprendre la sécurité du printemps donc j'espère que vous l'acceptez comme réponse, lol
public class CorsFilter extends OncePerRequestFilter {
static final String Origin = "Origin";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
System.out.println(request.getHeader(Origin));
System.out.println(request.getMethod());
if (request.getHeader(Origin).equals("null")) {
String Origin = request.getHeader(Origin);
response.setHeader("Access-Control-Allow-Origin", "*");//* or Origin as u prefer
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers",
request.getHeader("Access-Control-Request-Headers"));
}
if (request.getMethod().equals("OPTIONS")) {
try {
response.getWriter().print("OK");
response.getWriter().flush();
} catch (IOException e) {
e.printStackTrace();
}
}else{
filterChain.doFilter(request, response);
}
}
}
eh bien, vous devez également définir votre filtre à invoquer:
<security:http use-expressions="true" .... >
...
//your other configs
<security:custom-filter ref="corsHandler" after="PRE_AUTH_FILTER"/> // this goes to your filter
</security:http>
Eh bien et vous avez besoin d'un bean pour le filtre personnalisé que vous avez créé:
<bean id="corsHandler" class="mobilebackbone.mesoft.config.CorsFilter" />
Depuis Spring Security 4.1, c'est la bonne façon de faire en sorte que Spring Security prenne en charge CORS (également nécessaire dans Spring Boot 1.4/1.5):
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}
}
et:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// http.csrf().disable();
http.cors();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(ImmutableList.of("*"));
configuration.setAllowedMethods(ImmutableList.of("HEAD",
"GET", "POST", "PUT", "DELETE", "PATCH"));
// setAllowCredentials(true) is important, otherwise:
// The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
configuration.setAllowCredentials(true);
// setAllowedHeaders is important! Without it, OPTIONS preflight request
// will fail with 403 Invalid CORS request
configuration.setAllowedHeaders(ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Ne pas faites l'une des choses ci-dessous, qui sont la mauvaise façon de tenter de résoudre le problème:
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll();
web.ignoring().antMatchers(HttpMethod.OPTIONS);
Référence: http://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/cors.html
Généralement, la demande OPTIONS ne contient pas de cookie pour l'authentification de la sécurité Spring.
Pour résoudre ce problème, vous pouvez modifier la configuration de la sécurité du ressort pour autoriser [~ # ~] options [~ # ~] demande sans authentification.
Je recherche beaucoup et j'obtiens deux solutions:
1.Utilisation de Java avec la configuration de sécurité Spring,
@Override
protected void configure(HttpSecurity http) throws Exception
{
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS,"/path/to/allow").permitAll()//allow CORS option calls
.antMatchers("/resources/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic();
}
2.En utilisant XML ( note. ne peut pas écrire "POST, GET"):
<http auto-config="true">
<intercept-url pattern="/client/edit" access="isAuthenticated" method="GET" />
<intercept-url pattern="/client/edit" access="hasRole('EDITOR')" method="POST" />
<intercept-url pattern="/client/edit" access="hasRole('EDITOR')" method="GET" />
</http>
Au final, il y a la source de la solution ... :)
Pour moi, le problème était que la vérification de contrôle en amont OPTIONS
a échoué l'authentification, car les informations d'identification n'ont pas été transmises lors de cet appel.
Cela fonctionne pour moi:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
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.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import Java.io.IOException;
@Configuration
@EnableAsync
@EnableScheduling
@EnableSpringDataWebSupport
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.httpBasic().and()
.authorizeRequests()
.anyRequest().authenticated()
.and().anonymous().disable()
.exceptionHandling().authenticationEntryPoint(new BasicAuthenticationEntryPoint() {
@Override
public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException, ServletException {
if(HttpMethod.OPTIONS.matches(request.getMethod())){
response.setStatus(HttpServletResponse.SC_OK);
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_Origin, request.getHeader(HttpHeaders.Origin));
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD));
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
}else{
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}
});
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
La partie pertinente étant:
.exceptionHandling().authenticationEntryPoint(new BasicAuthenticationEntryPoint() {
@Override
public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException, ServletException {
if(HttpMethod.OPTIONS.matches(request.getMethod())){
response.setStatus(HttpServletResponse.SC_OK);
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_Origin, request.getHeader(HttpHeaders.Origin));
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD));
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
}else{
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}
});
Cela corrige le problème de contrôle en amont OPTIONS
. Ce qui se passe ici, c'est lorsque vous recevez un appel et que l'authentification échoue, vous vérifiez s'il s'agit d'un appel OPTIONS
et s'il l'est, laissez-le passer et laissez-le faire tout ce qu'il veut. Cela désactive essentiellement toutes les vérifications de contrôle en amont côté navigateur, mais la stratégie de domaine croisé normale s'applique toujours.
Lorsque vous utilisez la dernière version de Spring, vous pouvez utiliser le code ci-dessous pour autoriser les demandes Cross Origin à l'échelle mondiale (pour tous vos contrôleurs):
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Component
public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:3000");
}
}
Notez que c'est rarement une bonne idée de simplement coder en dur comme ça. Dans quelques entreprises pour lesquelles j'ai travaillé, les origines autorisées étaient configurables via un portail d'administration, donc sur les environnements de développement, vous pourriez ajouter toutes les origines dont vous avez besoin.
Dans mon cas, response.getWriter (). Flush () ne fonctionnait pas
Changé le code comme ci-dessous et il a commencé à fonctionner
public void doFilter(ServletRequest request, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
LOGGER.info("Start API::CORSFilter");
HttpServletRequest oRequest = (HttpServletRequest) request;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST,PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers",
" Origin, X-Requested-With, Content-Type, Accept,AUTH-TOKEN");
if (oRequest.getMethod().equals("OPTIONS")) {
response.flushBuffer();
} else {
chain.doFilter(request, response);
}
}
Étant donné que la partie principale de la question concerne la demande POST CORS non autorisée au point de connexion, je vous indique immédiatement l'étape 2 .
Mais en ce qui concerne le nombre de réponses, c'est la question la plus pertinente pour la demande Spring Security CORS. Je décrirai donc une solution plus élégante pour configurer CORS avec Spring Security. Car sauf cas rares il n'est pas nécessaire de créer des filtres/intercepteurs /… pour mettre quoi que ce soit en réponse. Nous le ferons de manière déclarative d'ici le printemps. Depuis Spring Framework 4.2, nous avons des éléments CORS tels que filtre, processeur, etc. prêts à l'emploi. Et quelques liens pour lire 12 .
Allons-y:
Cela peut se faire de différentes manières:
en tant que configuration globale Spring MVC CORS (dans les classes de configuration comme WebMvcConfigurerAdapter
)
...
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
...
}
en tant que bean corsConfigurationSource
séparé
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.applyPermitDefaultValues();
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
}
en tant que classe externe (qui peut être utilisée via le constructeur ou câblée en tant que composant)
// @Component // <- for autowiring
class CorsConfig extends UrlBasedCorsConfigurationSource {
CorsConfig() {
orsConfiguration config = new CorsConfiguration();
config.applyPermitDefaultValues(); // <- frequantly used values
this.registerCorsConfiguration("/**", config);
}
}
Nous activerons la prise en charge de CORS dans les classes Spring Security comme WebSecurityConfigurerAdapter
. Assurez-vous que corsConfigurationSource
est accessible pour ce support. Sinon, fournissez-le via @Resource
câblage automatique ou défini explicitement (voir dans l'exemple). Nous autorisons également l'accès non autorisé à certains points de terminaison comme la connexion:
...
// @Resource // <- for autowired solution
// CorseConfigurationSource corsConfig;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
// or autowiring
// http.cors().configurationSource(corsConfig);
// or direct set
// http.cors().configurationSource(new CorsConfig());
http.authorizeRequests()
.antMatchers("/login").permitAll() // without this line login point will be unaccessible for authorized access
.antMatchers("/*").hasAnyAuthority(Authority.all()); // <- all other security stuff
}
Si la configuration de base fonctionne, nous pouvons personnaliser les mappages, les origines, etc. Ajoutez même plusieurs configurations pour différents mappages. Par exemple, je déclare explicitement tous les paramètres CORS et laisse UrlPathHelper ne pas couper mon chemin de servlet:
class RestCorsConfig extends UrlBasedCorsConfigurationSource {
RestCorsConfig() {
this.setCorsConfigurations(Collections.singletonMap("/**", corsConfig()));
this.setAlwaysUseFullPath(true);
}
private static CorsConfiguration corsConfig() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedHeader("*");
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.setMaxAge(3600L);
return config;
}
}
Pour déboguer mon problème, je traçais org.springframework.web.filter.CorsFilter#doFilterInternal
méthode. Et j'ai vu que la recherche CorsConfiguration renvoie null
car la configuration globale de Spring MVC CORS n'était pas vue par Spring Security. J'ai donc utilisé une solution avec une utilisation directe de la classe externe:
http.cors().configurationSource(corsConfig);
Je suis totalement d'accord avec la réponse donnée par Bludream, mais j'ai quelques remarques:
Je voudrais étendre la clause if dans le filtre CORS avec une vérification NULL sur l'en-tête Origin:
public class CorsFilter extends OncePerRequestFilter {
private static final String Origin = "Origin";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request.getHeader(Origin) == null || request.getHeader(Origin).equals("null")) {
response.addHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.addHeader("Access-Control-Max-Age", "10");
String reqHead = request.getHeader("Access-Control-Request-Headers");
if (!StringUtils.isEmpty(reqHead)) {
response.addHeader("Access-Control-Allow-Headers", reqHead);
}
}
if (request.getMethod().equals("OPTIONS")) {
try {
response.getWriter().print("OK");
response.getWriter().flush();
} catch (IOException e) {
e.printStackTrace();
}
} else{
filterChain.doFilter(request, response);
}
}
}
De plus, j'ai remarqué le comportement indésirable suivant: Si j'essaie d'accéder à une API REST avec un rôle non autorisé, la sécurité Spring me renvoie un état HTTP 403: INTERDIT et les en-têtes CORS sont renvoyés. Cependant, si j'utilise un jeton inconnu ou un jeton qui n'est plus valide, un état HTTP 401: NON AUTORISÉ est renvoyé SANS en-têtes CORS.
J'ai réussi à le faire fonctionner en changeant la configuration du filtre dans le XML de sécurité comme ceci:
<security:http use-expressions="true" .... >
...
//your other configs
<sec:custom-filter ref="corsFilter" before="HEADERS_FILTER"/>
</security:http>
Et le bean suivant pour notre filtre personnalisé:
<bean id="corsFilter" class="<<location of the CORS filter class>>" />