Open pluttrell opened 7 years ago
This is the CORS filter configuration i have used to enable the Authorization Server to serve oauth tokens to browsers.
@Configuration @EnableAuthorizationServer @EnableConfigurationProperties(AuthorizationServerProperties.class) @Order(2) protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
@SuppressWarnings("SpringJavaAutowiringInspection")
private AuthenticationManager authenticationManager;
@Autowired
@SuppressWarnings("SpringJavaAutowiringInspection")
@Qualifier("writeDataSource")
private DataSource dataSource;
@Autowired
@SuppressWarnings("SpringJavaAutowiringInspection")
private AuthorizationServerProperties properties;
@Bean
public JdbcTokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Bean
protected AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
if (this.properties.getCheckTokenAccess() != null) {
security.checkTokenAccess(this.properties.getCheckTokenAccess());
}
if (this.properties.getTokenKeyAccess() != null) {
security.tokenKeyAccess(this.properties.getTokenKeyAccess());
}
if (this.properties.getRealm() != null) {
security.realm(this.properties.getRealm());
}
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authorizationCodeServices(authorizationCodeServices())
.authenticationManager(authenticationManager).tokenStore(tokenStore())
.approvalStoreDisabled();
}
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
@pkondam Thank you.
The Ordered.HIGHEST_PRECEDENCE
is key. not is 0
very important.
I'm using Kotlin and I was able to make this work:
@SpringBootApplication
class EdgeServiceApplication {
@Bean
open fun simpleCorsFilter(): FilterRegistrationBean {
val source = UrlBasedCorsConfigurationSource()
val config = CorsConfiguration()
config.allowCredentials = true
config.allowedOrigins = listOf("http://localhost:4200")
config.allowedMethods = listOf("GET", "POST", "PATCH");
config.allowedHeaders = listOf("*")
source.registerCorsConfiguration("/**", config)
val bean = FilterRegistrationBean(CorsFilter(source))
bean.order = Ordered.HIGHEST_PRECEDENCE
return bean
}
}
fun main(args: Array<String>) {
SpringApplication.run(EdgeServiceApplication::class.java, *args)
}
But I'm unable to make the same thing work with Spring Security Config:
@EnableWebSecurity(debug = true)
open class WebSecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http.cors().and();
}
@Bean
fun corsConfigurationSource(): CorsConfigurationSource {
val config = CorsConfiguration()
config.allowedOrigins = listOf("http://localhost:4200")
config.allowedMethods = listOf("GET", "POST", "PATCH");
config.allowedHeaders = listOf("*")
config.allowCredentials = true
val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", config)
return source;
}
}
Note that I have to use the name simpleCorsFilter
or I get an error:
Bean named 'corsFilter' is expected to be of type 'org.springframework.web.filter.CorsFilter'
I believe the problem is that the bean name of corsFilter, which is pulled from the method name annotated with @Bean is the issue. Changing the name to anything but corsFilter for the method name solved the issue for me.
@kentoj you are right. if you declare bean name is corsFilter and bean type is FilterRegistrationBean will be wrong.
I think this issue can be closed with a simple docs update.
I'm experiencing the same issue. What's the timeline for a docs update? Could we see a working code sample here ahead of that?
is there any solution to the problem?
Just make sure to avoid naming the method "corsFilter" that creates your bean. Here is what worked for me:
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
@Configuration
public class CorsConfig {
@Bean
public FilterRegistrationBean corsFilterRegistrationBean() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.applyPermitDefaultValues();
config.setAllowCredentials(true);
config.setAllowedOrigins(Arrays.asList("*"));
config.setAllowedHeaders(Arrays.asList("*"));
config.setAllowedMethods(Arrays.asList("*"));
config.setExposedHeaders(Arrays.asList("content-length"));
config.setMaxAge(3600L);
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}
Thanks for listening. I tried the way you suggested and could not access the browser services. Which version are you using? Follow my pom.xml.
`
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-java8</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<!-- <dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
</dependency> -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>`
After a few hours of study I was able to find a solution:
` import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter;
@Configuration public class CorsConfig {
private String allowOrigin= "http://localhost:8081";
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration configAutenticacao = new CorsConfiguration();
configAutenticacao.setAllowCredentials(true);
configAutenticacao.addAllowedOrigin(allowOrigin);
configAutenticacao.addAllowedHeader("Authorization");
configAutenticacao.addAllowedHeader("Content-Type");
configAutenticacao.addAllowedHeader("Accept");
configAutenticacao.addAllowedMethod("POST");
configAutenticacao.addAllowedMethod("GET");
configAutenticacao.addAllowedMethod("DELETE");
configAutenticacao.addAllowedMethod("PUT");
configAutenticacao.addAllowedMethod("OPTIONS");
configAutenticacao.setMaxAge(3600L);
source.registerCorsConfiguration("/oauth/token", configAutenticacao);
// source.registerCorsConfiguration("/**", configAutenticacao); // Global for all paths
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
} `
Is it a good solution? For me, it's very flexible.
I am surprised that works since your code
@Bean
public FilterRegistrationBean corsFilter()
has a method named corsFilter
which I thought was the problem in the first place. If it works for you, great! I had to change the method name.
For my versions I am using (Gradle)
buildscript {
ext {
springBootVersion = '1.5.7.RELEASE'
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath('com.bmuschko:gradle-docker-plugin:3.0.8')
}
}
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
ext {
springCloudVersion = 'Dalston.SR4'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-aop')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-jdbc')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-undertow')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.cloud:spring-cloud-starter-eureka')
compile('org.springframework.cloud:spring-cloud-starter-zuul')
}
@frettarenan What's output of browser console? it's CORS wrong or spring-security intercept request on server side? Can you show me some error message or stack trace on server?
Let me see if I understand you.
You: What's output of browser console? it's CORS wrong or spring-security intercept request on server side? Anser: it's CORS wrong. The browser could not obtain CORS authorization.
You: Can you show me some error message or stack trace on server? Anser: no error message is displayed.
Would you like me to share my project? It is very simple, I also have an HTML that I use to test the CORS.
I can go through the steps for playback, which are also simple.
yes, just send to my email or some else. liao131131@vip.qq.com
Steps:
algamoney-api-fixed.zip Fixed and test everything is ok. See AuthorizationServerConfig.java @frettarenan
It really helped me Thanks @frettarenan . I changed the project some code, hopefully this will help some people.
Glad I could help you. @MysteryAngle
` @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager()); }
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager service = new InMemoryUserDetailsManager();
User user = new User("test", "password", Collections.emptyList());
service.createUser(user);
return service;
}
@Bean
public AuthenticationManager authenticationManager() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService());
return new ProviderManager(Collections.singletonList(provider));
}
`
I saw that you made the changes below, but I did not execute. So I did not quite understand. Could you give me an explanation? Do you think it is necessary to apply these changes to my project?
thank you, I'll wait.
It's just create a user used for testing. and used for "password" as grant type on client. cuz i don't connection database find it. it's exists on memory.
DaoAuthenticationProvider implemented interface by AuthenticationManager. It need UserDetailsService instance provide the UserDetails. So.. InMemoryUserDetailsManager implemented interface by UserDetailsService. Suitable for testing the default virtual user.
The above concepts belong to Spring-Security.
ok, thanks.
I'm doing a course, and this source code is produced in this course. Now I'm learning how to customize the JWT token to add my custom attributes to it. I'ts very interesting.
The latest version of the source code is in the directory: 7.9-desafio-atualizacao-lancamento/algamoney-api
Link: https://github.com/algaworks/curso-angular-rest-spring-boot-api
@frettarenan Thanks for you by the resource. It look up very nice to learning.
What I did not understand, is if this solution I adopted is recommended to solve this problem. I tried the recommendations of the official website, but none worked.
@kentoj I do not believe that the name of the method influences the result. The @Bean annotation, from what I've learned in theory, is responsible for telling spring how it will instantiate that type of object. In the case: in the injections of dependencies.
@Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.applyPermitDefaultValues(); config.setAllowCredentials(true); config.setAllowedOrigins(Arrays.asList("")); config.setAllowedHeaders(Arrays.asList("")); config.setAllowedMethods(Arrays.asList("*")); config.setExposedHeaders(Arrays.asList("content-length")); config.setMaxAge(3600L); source.registerCorsConfiguration("/**", config); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); bean.setOrder(0); return bean; }
it throwing exception Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'corsFilter' is expected to be of type 'org.springframework.web.filter.CorsFilter' but was actually of type 'org.springframework.boot.web.servlet.FilterRegistrationBean'
@leosudeep1 change your method name from corsFilter to something else would work.
@kentoj @MysteryAngle @frettarenan Have you figured out why corsFilter
is not allowed to be the method name?
no, i have no idea about why the bean name is not allowed if type not matched.
@soulmachine @MysteryAngle this is a reason. https://github.com/spring-projects/spring-security/blob/master/config/src/main/java/org/springframework/security/config/annotation/web/configurers/CorsConfigurer.java#L83
@skyisle That's cool, way to go on figuring that out.
@pkondam This is great
quick update here, Ordered.HIGHEST_PRECEDENCE also worked for me, I was using bean.setOrder(0) and it wasn't enough
Hi, guys. Same trouble. I'm just using custom CORS filter and disable CORS in HTTP configuration. Btw I upgrade the filter, this one doesn't add allow headers to all responses, even when it's not required. Similar filters on SO or here add allow headers to every response.
It was tested with Angular application in Chromium and Firefox browsers. Everything works: simple requests and requests with preflight. Reading about CORS here.
package my.package;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
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 javax.ws.rs.HttpMethod;
import java.io.IOException;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public class CustomCorsFilter extends OncePerRequestFilter {
@Value("${custom.cors.allowOrigin:*}")
private String allowOrigin;
@Value("${custom.cors.allowMethods:GET, POST, PUT, DELETE, OPTIONS}")
private String allowMethods;
@Value("${custom.cors.allowHeaders:Content-Type}")
private String allowHeaders;
@Value("${custom.cors.allowCredentials:true}")
private String allowCredentials;
@Value("${custom.cors.maxAge:3600}")
private String maxAge;
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
throws ServletException, IOException {
response.addHeader("Access-Control-Allow-Origin", allowOrigin);
if (HttpMethod.OPTIONS.equalsIgnoreCase(request.getMethod())) {
response.addHeader("Access-Control-Allow-Methods", allowMethods);
response.addHeader("Access-Control-Allow-Headers", allowHeaders);
response.addHeader("Access-Control-Allow-Credentials", allowCredentials);
response.addHeader("Access-Control-Max-Age", maxAge);
response.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(request, response);
}
}
}
Hi all. I have to use CORS for Oauth spring implementation. Which is the right solution? Do I have to rename the bean name or what else? Please help me. I'm not so skilled using spring.
@borgiannis Hello. You can try to use my solution before ur comment. I'm using this one in my system and it works like a charm.
@binakot It works for me after small change :)
HttpMethod.OPTIONS.equalsIgnoreCase(request.getMethod())
to
request.getMethod().equalsIgnoreCase(HttpMethod.OPTIONS.toString())
because HttpMethod isn't String and first you need to convert it with toSpring().
Hello everybody. The filter approach works fine! Thanks a Lot.
@RaGreen Sorry, my bad. I should save imports. I'm using https://docs.oracle.com/javaee/7/api/javax/ws/rs/HttpMethod.html#OPTIONS. It's a string.
I added import block into my snippet.
In java 8 I have to use "matches". It accepts a string and works fine like "compareIgnoreCase".
10 Projects and some works and others not... At least I saw the problem!
I used this post by @pkondam
@Bean
open fun corsFilterRegistrationBean(): FilterRegistrationBean<*>? {
val source = UrlBasedCorsConfigurationSource()
val config = CorsConfiguration()
config.applyPermitDefaultValues()
config.allowCredentials = true
config.allowedOrigins = listOf("*")
config.allowedHeaders = listOf("*")
config.allowedMethods = listOf("*")
config.exposedHeaders = listOf("content-length")
config.maxAge = 3600L
source.registerCorsConfiguration("/**", config)
val bean: FilterRegistrationBean<*> = FilterRegistrationBean(CorsFilter(source))
bean.order = Ordered.HIGHEST_PRECEDENCE //this is really important!! but you must check ordered of AuthorizationServerConfigurerAdapter
return bean
}
If you have an AuthorizationServerConfigurerAdapter
you must be "less" order than this corsfilterRegistrationBean or doesn´t work!
I say "less" because te highest precedence is -negative with highest value haha
If have same order doesn´t work too!!
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE + 2)
@EnableWebSecurity
open class AuthorizationServerConfig(private var passwordEncoder: PasswordEncoder,
private val accessTokenConverter: JwtAccessTokenConverter,
private val jwtTokenStore: JwtTokenStore,
private val authManager: AuthenticationManagerOauth): AuthorizationServerConfigurerAdapter() {
}
Hi Ivan
I use it and it works fine!
I forgot to implement the OPTION. In that way the in-flight request failed.
Once implemented it everything goes well.
Regards Stefano
Il Sab 3 Feb 2018, 12:17 Ivan Muratov notifications@github.com ha scritto:
@borgiannis https://github.com/borgiannis Hello. You can try to use my solution before ur comment. I'm using this one in my system and it works like a charm.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/spring-projects/spring-security-oauth/issues/938#issuecomment-362798902, or mute the thread https://github.com/notifications/unsubscribe-auth/AiYOsOPtRUlIh8CYCLsAYUJMhqGPMP5Uks5tREBAgaJpZM4LUjgT .
This is the CORS filter configuration i have used to enable the Authorization Server to serve oauth tokens to browsers.
@configuration @EnableAuthorizationServer @EnableConfigurationProperties(AuthorizationServerProperties.class) @order(2) protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired @SuppressWarnings("SpringJavaAutowiringInspection") private AuthenticationManager authenticationManager; @Autowired @SuppressWarnings("SpringJavaAutowiringInspection") @Qualifier("writeDataSource") private DataSource dataSource; @Autowired @SuppressWarnings("SpringJavaAutowiringInspection") private AuthorizationServerProperties properties; @Bean public JdbcTokenStore tokenStore() { return new JdbcTokenStore(dataSource); } @Bean protected AuthorizationCodeServices authorizationCodeServices() { return new JdbcAuthorizationCodeServices(dataSource); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { if (this.properties.getCheckTokenAccess() != null) { security.checkTokenAccess(this.properties.getCheckTokenAccess()); } if (this.properties.getTokenKeyAccess() != null) { security.tokenKeyAccess(this.properties.getTokenKeyAccess()); } if (this.properties.getRealm() != null) { security.realm(this.properties.getRealm()); } } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authorizationCodeServices(authorizationCodeServices()) .authenticationManager(authenticationManager).tokenStore(tokenStore()) .approvalStoreDisabled(); } @Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); bean.setOrder(Ordered.HIGHEST_PRECEDENCE); return bean; } }
Isn't that a security drawback that I allow all origins/headers and methods to be used in my app?
Using the latest version of Spring Boot (1.4.2.RELEASE) and enabling Oauth2 using
@EnableAuthorizationServer
, I can't get CORS support to work using either the@CrossOrigin
or the global support via Spring'sCorsFilter
as described on the spring.io/blog.The full example code is in this GitHub repo and can be run with
gradle bootRun
.When I use Postman or Httpie it responds perfectly, for example:
But when I use JavaScript in Chrome it fails with a:
Here is the full request from Chome:
And the full response Chrome receives back:
For the JavaScript in Chrome test, I'm simply running Spring Boot on a separate port which hosts this
index.html
:Note that if I add the following custom filter as described in this stackoverflow response, the JavaScript in Chrome source does work. But this is a brute force filter with side effects and I'd much prefer to use Spring's built in CORS support via
@CrossOrigin
or the SpringCorsFilter
.