【问题标题】:Spring Security 5 Spring MVC Oauth 2 Password grant type not returning token with /oauth/tokenSpring Security 5 Spring MVC Oauth 2 密码授予类型不返回带有 /oauth/token 的令牌
【发布时间】:2019-02-11 05:36:41
【问题描述】:

我正在处理一项要求,试图允许另一个受信任的应用程序(后端)使用 oauth2 密码授权连接到我们的 API,但我无法使用 /oauth/token 获取令牌。 我们的应用程序已经有一个使用登录表单运行的基本表单登录身份验证。

这是允许用户使用表单登录的原始 WebSecurityConfig。这已经工作了一段时间。

@Configuration
@ComponentScan("config")
@EnableWebSecurity
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
UserDetailsServiceImpl userDetailsService;


@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().and()
            .csrf().disable()
            .headers().frameOptions().disable()
        .and()
            .authorizeRequests()
            //allow anyone to access the following with the pattern
            .antMatchers("/", "/static/**", "/ping", "/topic/**",  "/oauth/token" ).permitAll()
            .anyRequest().authenticated()
        .and()
            .formLogin()
            .loginPage("/").permitAll()
            .loginProcessingUrl("/login")
            .usernameParameter("username")
            .passwordParameter("password")
        .and()
            .sessionManagement()
            .expiredUrl("/")
        .and()
            .invalidSessionUrl("/");
}

@Bean(name = "corsConfigurationSource")
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedMethods(Arrays.asList("GET", "POST", "OPTIONS", "DELETE", "PUT"));
    configuration.setAllowedHeaders(Arrays.asList("Cache-Control", "Authorization", "Content-Type", "content-type", "x-requested-with", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "x-auth-token", "x-app-id", "Origin", "Accept", "X-Requested-With", "Access-Control-Request-Method", "Access-Control-Request-Headers"));
    configuration.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService);
    auth.authenticationProvider(authProvider());
}

@Bean
public DaoAuthenticationProvider authProvider() {
    DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(userDetailsService);
    authProvider.setPasswordEncoder(encoder());
    return authProvider;
}

@Bean
public PasswordEncoder encoder() {
    return new MessageDigestPasswordEncoder("md5");
}
}

这是我添加的新授权服务器设置。我只是想让最简单的案例运行。所以我对所有东西都使用内存。

@Configuration
@EnableAuthorizationServer
@ComponentScan ("config")
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

@Autowired
private AuthenticationManager authenticationManager;

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory().withClient("client")
        .secret("clientpassword")
        .secret("{noop}secret")
        .authorizedGrantTypes("password")
        .scopes("read", "write");
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.tokenStore( new InMemoryTokenStore())
            .authenticationManager(authenticationManager)
            .allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
}

这是我创建的另一个 WebSecurityConfig,试图将原来的和新的分开:

@Configuration
@Order(1)
@ComponentScan ("config")
@EnableWebSecurity (debug = true)
public class WebSecurityOauthConfig extends WebSecurityConfigurerAdapter {


@Override
public void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .anyRequest().authenticated().and()
        .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
        .csrf().disable();
    }
@Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
        .passwordEncoder(NoOpPasswordEncoder.getInstance())
        .withUser("user").password("user").roles("ROLE");
}

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

这是版本:

<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.2.RELEASE</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
        <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
        <version>2.3.3.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-oauth2-client</artifactId>
        <version>5.0.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>5.0.0.RELEASE</version>
    </dependency>

这是我发送的请求:

curl -X POST \
  http://localhost:8081/oauth/token \
  -H 'Authorization: Basic Y2xpZW50OmNsaWVudHBhc3N3b3Jk' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'Postman-Token: 06a1108e-d620-4e01-b8f7-81eb7a57ae44' \
  -H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
  -F client_id=client \
  -F user=user \
  -F password=user \
  -F grant_type=password

没有明确的错误。这是来自 spring security 的跟踪日志。据我所知, DaoAuthenticationProvider 没有正确注册。我期待使用内存用户,但过滤器链不包括它。我也试过自动连接userDetailsS​​ervice,但结果是一样的

************************************************************

Request received for POST '/oauth/token':

org.apache.catalina.connector.RequestFacade@47616c04

servletPath:/oauth/token
pathInfo:null
headers: 
content-type: multipart/form-data; boundary=--------------------------127172580218970013444831
authorization: Basic Y2xpZW50OmNsaWVudHBhc3N3b3Jk
cache-control: no-cache
postman-token: db0faf20-d49c-485a-8712-4d31bc65615a
user-agent: PostmanRuntime/7.1.5
accept: */*
host: localhost:8081
cookie: JSESSIONID=AC9D0F63FB67F51917751325403CC4B1
accept-encoding: gzip, deflate
content-length: 505
connection: keep-alive


Security filter chain: [
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  LogoutFilter
  BasicAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]


************************************************************


2018-09-05 22:42:36 DEBUG OrRequestMatcher:65 - Trying to match using Ant [pattern='/oauth/token']
2018-09-05 22:42:36 DEBUG AntPathRequestMatcher:157 - Checking match of     request : '/oauth/token'; against '/oauth/token' 2018-09-05 22:42:36 DEBUG OrRequestMatcher:68 - matched
2018-09-05 22:42:36 DEBUG FilterChainProxy:328 - /oauth/token at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2018-09-05 22:42:36 DEBUG FilterChainProxy:328 - /oauth/token at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2018-09-05 22:42:36 DEBUG FilterChainProxy:328 - /oauth/token at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2018-09-05 22:42:36 DEBUG HstsHeaderWriter:130 - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@42b51d34
2018-09-05 22:42:36 DEBUG FilterChainProxy:328 - /oauth/token at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
2018-09-05 22:42:36 DEBUG OrRequestMatcher:65 - Trying to match using Ant [pattern='/logout', GET]
2018-09-05 22:42:36 DEBUG AntPathRequestMatcher:137 - Request 'POST /oauth/token' doesn't match 'GET /logout
2018-09-05 22:42:36 DEBUG OrRequestMatcher:65 - Trying to match using Ant [pattern='/logout', POST]
2018-09-05 22:42:36 DEBUG AntPathRequestMatcher:157 - Checking match of request : '/oauth/token'; against '/logout'
2018-09-05 22:42:36 DEBUG OrRequestMatcher:65 - Trying to match using Ant [pattern='/logout', PUT]
2018-09-05 22:42:36 DEBUG AntPathRequestMatcher:137 - Request 'POST /oauth/token' doesn't match 'PUT /logout
2018-09-05 22:42:36 DEBUG OrRequestMatcher:65 - Trying to match using Ant [pattern='/logout', DELETE]
2018-09-05 22:42:36 DEBUG AntPathRequestMatcher:137 - Request 'POST /oauth/token' doesn't match 'DELETE /logout
2018-09-05 22:42:36 DEBUG OrRequestMatcher:72 - No matches found
2018-09-05 22:42:36 DEBUG FilterChainProxy:328 - /oauth/token at position 5 of 11 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
2018-09-05 22:42:36 DEBUG BasicAuthenticationFilter:170 - Basic Authentication Authorization header found for user 'client'
2018-09-05 22:42:36 DEBUG ProviderManager:169 - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2018-09-05 22:42:36 DEBUG DaoAuthenticationProvider:87 - Authentication failed: password does not match stored value
2018-09-05 22:42:36 DEBUG BasicAuthenticationFilter:198 - Authentication request for failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2018-09-05 22:42:36 DEBUG DelegatingAuthenticationEntryPoint:78 - Trying to match using RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]
2018-09-05 22:42:36 DEBUG DelegatingAuthenticationEntryPoint:91 - No match found. Using default entry point org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint@68e001ed
2018-09-05 22:42:36 DEBUG SecurityContextPersistenceFilter:119 - SecurityContextHolder now cleared, as request processing completed

响应是 401 Unathorized:

<html>
<head>
    <title>Apache Tomcat/7.0.47 - Error report</title>
    <style>
        <!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}-->
    </style>
</head>
<body>
    <h1>HTTP Status 401 - Unauthorized</h1>
    <HR size="1" noshade="noshade">
    <p>
        <b>type</b> Status report
    </p>
    <p>
        <b>message</b>
        <u>Unauthorized</u>
    </p>
    <p>
        <b>description</b>
        <u>This request requires HTTP authentication.</u>
    </p>
    <HR size="1" noshade="noshade">
    <h3>Apache Tomcat/7.0.47</h3>
</body>
</html>

请帮忙。欢迎提出任何建议。

【问题讨论】:

  • 您能否更详细地了解 OAuth2 为您解决了什么而基本身份验证没有解决的问题?我知道您面临着让某项工作发挥作用的压力,但我需要更好地了解您的用例才能提出建议。
  • 另外,只是稍微清理一下。您在客户端配置中配置了两个机密。你打电话给.secret 两次。根据您的配置,该应用程序似乎会认为 client/secret 是有效的 u/p 组合,但您的 curl 在 Authorization 标头中使用了 client/clientpassword。
  • 而且,为了使测试更容易,我推荐使用以下 curl 命令:curl client:secret@localhost:8081/oauth/token -d grant_type=password -d username=user -d password=user 这样您就不必担心自己对客户端 ID 进行编码和加密,而且您没有要发布的原始标题。
  • @jzheaux 感谢您帮助我。因此,我们有一个使用 Java Spring 5 API 的后端单页 Javascript 应用程序。它一直运行良好。现在我们公司内部的另一个团队也想要访问我们正在使用的同一个 Spring 5 API。我们正在尝试找出一种方法,以便他们可以访问它。

标签: java spring spring-mvc spring-security spring-security-oauth2


【解决方案1】:

看起来代码在客户端服务设置中调用了两次.secret:

clients.inMemory().withClient("client")
    .secret("clientpassword")
    .secret("{noop}secret")
    ...

日志抱怨 client 的凭据错误:

2018-09-05 22:42:36 DEBUG BasicAuthenticationFilter:170 - Basic Authentication Authorization header found for user 'client'
2018-09-05 22:42:36 DEBUG ProviderManager:169 - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2018-09-05 22:42:36 DEBUG DaoAuthenticationProvider:87 - Authentication failed: password does not match stored value
2018-09-05 22:42:36 DEBUG BasicAuthenticationFilter:198 - Authentication request for failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials

如果我 base64-decode 您的示例请求中的 Authorization 标头,则您使用的密码是 clientpassword,如果您这样做,它将起作用:

clients.inMemory().withClient("client")
    .secret("{noop}clientpassword")
    // .secret("{noop}secret") 
    // omit the other .secret call and continue w/ remaining config
    ...

此外,当您准备好从探索模式继续前进时,请记住至少将该密码提取到属性文件中并对其进行编码,例如{bcrypt}$2y$12$8UyEwJ1iwyGqXrxfmH...

最后,可能一些混淆可以通过使用更简单的 curl 调用来简化,该调用将密码保留为明文。可能:

curl -v client:clientpassword@localhost:8081/oauth/token -d grant_type=password -d username=user -d password=user

让一切都简单明了。

【讨论】:

  • 你太棒了。我们在您的帮助下使其工作。万分感激!非常感谢!
猜你喜欢
  • 2016-09-06
  • 2020-10-31
  • 2018-04-26
  • 2021-03-25
  • 2013-07-28
  • 2014-08-25
  • 1970-01-01
  • 2020-09-08
  • 2018-02-24
相关资源
最近更新 更多