spring-attic / spring-security-oauth

Support for adding OAuth1(a) and OAuth2 features (consumer and provider) for Spring web applications.
http://github.com/spring-projects/spring-security-oauth
Apache License 2.0
4.69k stars 4.04k forks source link

CORS Support Not Working with Spring Boot 1.4.2 + Oauth2 #938

Open pluttrell opened 7 years ago

pluttrell commented 7 years ago

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's CorsFilter as described on the spring.io/blog.

The full example code is in this GitHub repo and can be run with gradle bootRun.

@SpringBootApplication
public class AuthServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthServiceApplication.class, args);
    }
}

@Configuration
@EnableAuthorizationServer
class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client1").secret("password")
                .scopes("foo-scope")
                .autoApprove(true)
                .authorizedGrantTypes("implicit", "refresh_token", "password", "authorization_code")
                .accessTokenValiditySeconds(600)
                .refreshTokenValiditySeconds(1800);
    }

    @Autowired
    @Qualifier("authenticationManagerBean")
    @SuppressWarnings("SpringJavaAutowiringInspection")
    private AuthenticationManager authenticationManager;

    @Bean
    public TokenStore buildTokenStore() {
        return new JwtTokenStore(buildJwtTokenConverter());
    }

    @Bean
    public TokenEnhancer buildExtraFieldsTokenEnhancer() {
        return (accessToken, authentication) -> {
            DefaultOAuth2AccessToken defaultOAuth2AccessToken = (DefaultOAuth2AccessToken) accessToken;
            Map<String, Object> additionalInfo = new HashMap<>();
            additionalInfo.put("user-uuid", UUID.randomUUID());
            defaultOAuth2AccessToken.setAdditionalInformation(additionalInfo);
            return defaultOAuth2AccessToken;
        };
    }

    @Bean
    protected JwtAccessTokenConverter buildJwtTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("secret");
        return converter;
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(buildTokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(buildExtraFieldsTokenEnhancer(), buildJwtTokenConverter()));
        endpoints.tokenStore(buildTokenStore())
                .tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }
}

@Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .antMatchers("/**").authenticated()
                .and()
                .httpBasic();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user1").password("password").authorities("right1");
    }

    @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(0);
        return bean;
    }
}

When I use Postman or Httpie it responds perfectly, for example:

http --auth-type basic --auth client1:password --form POST http://localhost:8080/oauth/token grant_type="password" username="user1" password="password"

HTTP/1.1 200 
Cache-Control: no-store
Content-Type: application/json;charset=UTF-8
Date: Fri, 23 Dec 2016 03:15:25 GMT
Pragma: no-cache
Transfer-Encoding: chunked
X-Application-Context: application
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyLXV1aWQiOiI3YzNmNTY3My1jZWRlLTRmYjMtOGUzZC05YjdlMGZhODA0OGYiLCJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImZvby1zY29wZSJdLCJleHAiOjE0ODI0NjM1MjUsImF1dGhvcml0aWVzIjpbInJpZ2h0MSJdLCJqdGkiOiI4OTJkM2JhZC01YTFmLTQ0NTUtOWNhNS1jNjM2MWUwMTg2MjYiLCJjbGllbnRfaWQiOiJjbGllbnQxIn0.ofNkDHhfpFwdW5qcgxoFzpoY9wzvs7vwwH2ULxBm4Bc",
    "expires_in": 599,
    "jti": "892d3bad-5a1f-4455-9ca5-c6361e018626",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyLXV1aWQiOiI3YzNmNTY3My1jZWRlLTRmYjMtOGUzZC05YjdlMGZhODA0OGYiLCJ1c2VyX25hbWUiOiJ1c2VyMSIsInNjb3BlIjpbImZvby1zY29wZSJdLCJhdGkiOiI4OTJkM2JhZC01YTFmLTQ0NTUtOWNhNS1jNjM2MWUwMTg2MjYiLCJleHAiOjE0ODI0NjQ3MjUsImF1dGhvcml0aWVzIjpbInJpZ2h0MSJdLCJqdGkiOiJkNjI2NmNkNS1mZjdkLTRjYzItOWJjMS1jODU2MmEwOTY2ZGIiLCJjbGllbnRfaWQiOiJjbGllbnQxIn0.H_mKswPz8zuEoQajiO4FvrnFXJoVZttqXFG3sP58N4I",
    "scope": "foo-scope",
    "token_type": "bearer",
    "user-uuid": "7c3f5673-cede-4fb3-8e3d-9b7e0fa8048f"
}

But when I use JavaScript in Chrome it fails with a:

XMLHttpRequest cannot load http://localhost:8080/oauth/token. Response for preflight has invalid HTTP status code 401

Here is the full request from Chome:

OPTIONS /oauth/token HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Access-Control-Request-Method: POST
Origin: http://localhost:9000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36
Access-Control-Request-Headers: authorization, cache-control
Accept: */*
Referer: http://localhost:9000/
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: en-US,en;q=0.8

And the full response Chrome receives back:

HTTP/1.1 401
WWW-Authenticate: Basic realm="oauth2/client"
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Access-Control-Allow-Origin: http://localhost:9000
Vary: Origin
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: authorization, cache-control
Access-Control-Allow-Credentials: true
Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
Content-Length: 0
Date: Fri, 23 Dec 2016 03:09:45 GMT

For the JavaScript in Chrome test, I'm simply running Spring Boot on a separate port which hosts this index.html:

<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript">

var form = new FormData();
form.append("grant_type", "password");
form.append("username", "user1");
form.append("password", "password");

var settings = {
  "async": true,
  "crossDomain": true,
  "url": "http://localhost:8080/oauth/token",
  "method": "POST",
  "headers": {
    "authorization": "Basic Y2xpZW50MTpwYXNzd29yZA==",
    "cache-control": "no-cache"
  },
  "processData": false,
  "contentType": false,
  "mimeType": "multipart/form-data",
  "data": form
}

$.ajax(settings).done(function (response) {
  console.log(response);
});
</script>
</head>

<body>
Check the console.
</body>
</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 Spring CorsFilter.

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
class SimpleCorsFilter implements Filter {

  public SimpleCorsFilter() {
  }

  @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", "*");
    response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
    response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, cache-control");

    if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
      response.setStatus(HttpServletResponse.SC_OK);
    } else {
      chain.doFilter(req, res);
    }
  }

  @Override
  public void init(FilterConfig filterConfig) {
  }

  @Override
  public void destroy() {
  }
}
pkondam commented 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;
    }
}
MysteryAngle commented 7 years ago

@pkondam Thank you. The Ordered.HIGHEST_PRECEDENCE is key. not is 0 very important.

mraible commented 7 years ago

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'
kentoj commented 7 years ago

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.

MysteryAngle commented 7 years ago

@kentoj you are right. if you declare bean name is corsFilter and bean type is FilterRegistrationBean will be wrong.

kentoj commented 7 years ago

I think this issue can be closed with a simple docs update.

mr-smithers-excellent commented 7 years ago

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?

frettarenan commented 7 years ago

is there any solution to the problem?

kentoj commented 7 years ago

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;
    }
}
frettarenan commented 7 years ago

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.

`

org.springframework.boot
    <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>`
frettarenan commented 7 years ago

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.

kentoj commented 7 years ago

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')
}
MysteryAngle commented 7 years ago

@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?

frettarenan commented 7 years ago

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.

frettarenan commented 7 years ago

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.

MysteryAngle commented 7 years ago

yes, just send to my email or some else. liao131131@vip.qq.com

frettarenan commented 7 years ago

algamoney-api.zip

Steps:

  1. Configure MySQL Connection: /algamoney-api/src/main/resources/messages.properties
  2. Start spring app
  3. Use a simple server http for use a simple http server to run the test-get-access-token.html file
  4. open the browser console
  5. click the button that appears on the html screen
  6. Finally, the browser will display the token on the console. That is, the CORs configuration is working correctly.
MysteryAngle commented 7 years ago

algamoney-api-fixed.zip Fixed and test everything is ok. See AuthorizationServerConfig.java @frettarenan

MysteryAngle commented 7 years ago

It really helped me Thanks @frettarenan . I changed the project some code, hopefully this will help some people.

frettarenan commented 7 years ago

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.

MysteryAngle commented 7 years ago

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.

frettarenan commented 7 years ago

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

MysteryAngle commented 7 years ago

@frettarenan Thanks for you by the resource. It look up very nice to learning.

frettarenan commented 7 years ago

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.

frettarenan commented 7 years ago

@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.

leosudeep1 commented 7 years ago

@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'

todzheng commented 7 years ago

@leosudeep1 change your method name from corsFilter to something else would work.

soulmachine commented 7 years ago

@kentoj @MysteryAngle @frettarenan Have you figured out why corsFilter is not allowed to be the method name?

MysteryAngle commented 7 years ago

no, i have no idea about why the bean name is not allowed if type not matched.

skyisle commented 6 years ago

@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

kentoj commented 6 years ago

@skyisle That's cool, way to go on figuring that out.

alanhugo commented 6 years ago

@pkondam This is great

deniswsrosa commented 6 years ago

quick update here, Ordered.HIGHEST_PRECEDENCE also worked for me, I was using bean.setOrder(0) and it wasn't enough

binakot commented 6 years ago

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);
        }
    }
}
borgiannis commented 6 years ago

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.

binakot commented 6 years ago

@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.

RaGreen commented 6 years ago

@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().

borgiannis commented 6 years ago

Hello everybody. The filter approach works fine! Thanks a Lot.

binakot commented 6 years ago

@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.

borgiannis commented 6 years ago

In java 8 I have to use "matches". It accepts a string and works fine like "compareIgnoreCase".

jposes22 commented 4 years ago

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() {
}
borgiannis commented 4 years ago

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 .

bielas commented 4 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;
    }
}

Isn't that a security drawback that I allow all origins/headers and methods to be used in my app?