Spring 启动 Oauth2,无法从 javascript 客户端发送 clientId
Spring Boot Oauth2, Can't send clientId from a javascript client
我的问题在一个非常简短的描述中是我无法发送我从网络客户端配置的 client-id。
详细信息:
我创建了一个简单的 spring 引导 oauth2 服务器。
服务器按预期完美运行,我通过发送用户凭据请求令牌,并通过使用 postman as:
发送 post 请求来接收令牌
trusted-app@localhost:8080/oauth/token
注意我发送客户端 trusted-app
id 作为 url!
的一部分
成功回复:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyX2FwcGxpY2F0aW9uIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIiwidXNlcl9pbmZvIl0sImV4cCI6MTUzMDA5Njg5OSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9SRUdJU1RFUiIsIlJPTEVfQURNSU4iXSwianRpIjoiZTZjMjdlYmItN2ZkNC00MzU2LWFmYzgtNmQ5NTk4M2YwMWE0IiwiY2xpZW50X2lkIjoiQ2xpZW50SWQifQ.Xsi-9J7R5oeiaa29VAYgtLYFB971VMRLKAwpTYz0gNI",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyX2FwcGxpY2F0aW9uIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIiwidXNlcl9pbmZvIl0sImF0aSI6ImU2YzI3ZWJiLTdmZDQtNDM1Ni1hZmM4LTZkOTU5ODNmMDFhNCIsImV4cCI6MTUzMjY0NTY5OSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9SRUdJU1RFUiIsIlJPTEVfQURNSU4iXSwianRpIjoiNzI5ZDViMDctN2E0Yy00MmEyLWEzYTQtMTk2MTY0YWU2YmNmIiwiY2xpZW50X2lkIjoiQ2xpZW50SWQifQ.QgNg7LwYs8M4UuBW1ntkqHPGugqFIbHG8XQUPARWq3M",
"expires_in": 43199,
"scope": "read write user_info",
"jti": "e6c27ebb-7fd4-4356-afc8-6d95983f01a4"
}
我的问题是从 javascript 代码发送相同的请求,使用任何 javascript 客户端库,JQuery/Axios 或任何东西,我总是得到一个错误:
{
"timestamp": 1530058939432,
"status": 401,
"error": "Unauthorized",
"message": "Full authentication is required to access this resource",
"path": "/oauth/token"
}
- 我在 postman 上也遇到同样的错误,如果我请求错误
客户端 ID,或没有客户端 ID。
- 检查Chrome 网络日志,我看到发送的请求总是没有客户端 ID!
- 我做了很多研究,我可以看到其他项目将客户端 id 作为查询参数发送:
clientId:trusted-app
,但这对我不起作用,不是作为查询参数,不是作为a header,而不是作为表单参数。
现在我完全糊涂了,难道不允许从 Web 客户端在 url 中发送客户端 ID 吗?
有没有一种方法可以配置 spring 安全性以从 header、查询或表单参数中读取 clientId?
这是
AuthorizationServerConfigurerAdapter:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Value("${security.oauth2.resource.id}")
private String resourceId;
@Value("${access_token.validity_period}")
private int accessTokenValiditySeconds;
@Value("${refresh_token.validity_period}")
private int refreshTokenValiditySeconds;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private SecretKeyProvider keyProvider;
@Bean
public UserDetailsService userDetailsService() {
return new AccountServiceImpl();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenServices(tokenServices())
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("trusted-app")
.authorizedGrantTypes("authorization_code", "implicit", "client_credentials", "password", "refresh_token")
.authorities("ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "user_info")
.resourceIds(resourceId)
.accessTokenValiditySeconds(accessTokenValiditySeconds)
.refreshTokenValiditySeconds(refreshTokenValiditySeconds)
.autoApprove(true);
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
try {
converter.setSigningKey(keyProvider.getKey());
} catch (URISyntaxException | KeyStoreException | NoSuchAlgorithmException | IOException | UnrecoverableKeyException | CertificateException e) {
e.printStackTrace();
}
return converter;
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}
}
和 WebSecurityConfigurerAdapter:
@Configuration
@EnableWebSecurity(debug = true)
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(bCryptPasswordEncoder);
provider.setUserDetailsService(userDetailsService);
return provider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.authorizeRequests()
.antMatchers("/oauth/token").anonymous()
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**");
}
}
我现在只分享这两个类,因为我相信我需要的解决方案只能在这里修复,我可能是错的。
您可以配置授权服务器,以便客户端可以通过表单数据进行身份验证,如下所示,
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients();
}
}
然后您可以作为表单数据发送
curl -X POST \
http://localhost:54040/oauth/token \
-H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
-F grant_type=system \
-F username=udara \
-F password=udara123 \
-F client_id=wfw3e5454353wwrwrtr \
-F client_secret=432fwrw5425242543w245325
或作为 x-www-form-urlencoded
curl -X POST \
http://localhost:54040/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=internal&username=udara&password=udara123&client_id=wfw3e5454353wwrwrtr&client_secret=432fwrw5425242543w245325'
我的问题在一个非常简短的描述中是我无法发送我从网络客户端配置的 client-id。
详细信息:
我创建了一个简单的 spring 引导 oauth2 服务器。 服务器按预期完美运行,我通过发送用户凭据请求令牌,并通过使用 postman as:
发送 post 请求来接收令牌trusted-app@localhost:8080/oauth/token
注意我发送客户端 trusted-app
id 作为 url!
成功回复:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyX2FwcGxpY2F0aW9uIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIiwidXNlcl9pbmZvIl0sImV4cCI6MTUzMDA5Njg5OSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9SRUdJU1RFUiIsIlJPTEVfQURNSU4iXSwianRpIjoiZTZjMjdlYmItN2ZkNC00MzU2LWFmYzgtNmQ5NTk4M2YwMWE0IiwiY2xpZW50X2lkIjoiQ2xpZW50SWQifQ.Xsi-9J7R5oeiaa29VAYgtLYFB971VMRLKAwpTYz0gNI",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsib2F1dGgyX2FwcGxpY2F0aW9uIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIiwidXNlcl9pbmZvIl0sImF0aSI6ImU2YzI3ZWJiLTdmZDQtNDM1Ni1hZmM4LTZkOTU5ODNmMDFhNCIsImV4cCI6MTUzMjY0NTY5OSwiYXV0aG9yaXRpZXMiOlsiUk9MRV9SRUdJU1RFUiIsIlJPTEVfQURNSU4iXSwianRpIjoiNzI5ZDViMDctN2E0Yy00MmEyLWEzYTQtMTk2MTY0YWU2YmNmIiwiY2xpZW50X2lkIjoiQ2xpZW50SWQifQ.QgNg7LwYs8M4UuBW1ntkqHPGugqFIbHG8XQUPARWq3M",
"expires_in": 43199,
"scope": "read write user_info",
"jti": "e6c27ebb-7fd4-4356-afc8-6d95983f01a4"
}
我的问题是从 javascript 代码发送相同的请求,使用任何 javascript 客户端库,JQuery/Axios 或任何东西,我总是得到一个错误:
{
"timestamp": 1530058939432,
"status": 401,
"error": "Unauthorized",
"message": "Full authentication is required to access this resource",
"path": "/oauth/token"
}
- 我在 postman 上也遇到同样的错误,如果我请求错误 客户端 ID,或没有客户端 ID。
- 检查Chrome 网络日志,我看到发送的请求总是没有客户端 ID!
- 我做了很多研究,我可以看到其他项目将客户端 id 作为查询参数发送:
clientId:trusted-app
,但这对我不起作用,不是作为查询参数,不是作为a header,而不是作为表单参数。
现在我完全糊涂了,难道不允许从 Web 客户端在 url 中发送客户端 ID 吗? 有没有一种方法可以配置 spring 安全性以从 header、查询或表单参数中读取 clientId?
这是
AuthorizationServerConfigurerAdapter:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Value("${security.oauth2.resource.id}")
private String resourceId;
@Value("${access_token.validity_period}")
private int accessTokenValiditySeconds;
@Value("${refresh_token.validity_period}")
private int refreshTokenValiditySeconds;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private SecretKeyProvider keyProvider;
@Bean
public UserDetailsService userDetailsService() {
return new AccountServiceImpl();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenServices(tokenServices())
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("trusted-app")
.authorizedGrantTypes("authorization_code", "implicit", "client_credentials", "password", "refresh_token")
.authorities("ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "user_info")
.resourceIds(resourceId)
.accessTokenValiditySeconds(accessTokenValiditySeconds)
.refreshTokenValiditySeconds(refreshTokenValiditySeconds)
.autoApprove(true);
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
try {
converter.setSigningKey(keyProvider.getKey());
} catch (URISyntaxException | KeyStoreException | NoSuchAlgorithmException | IOException | UnrecoverableKeyException | CertificateException e) {
e.printStackTrace();
}
return converter;
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}
}
和 WebSecurityConfigurerAdapter:
@Configuration
@EnableWebSecurity(debug = true)
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(bCryptPasswordEncoder);
provider.setUserDetailsService(userDetailsService);
return provider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(bCryptPasswordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.authorizeRequests()
.antMatchers("/oauth/token").anonymous()
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/images/**");
}
}
我现在只分享这两个类,因为我相信我需要的解决方案只能在这里修复,我可能是错的。
您可以配置授权服务器,以便客户端可以通过表单数据进行身份验证,如下所示,
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients();
}
}
然后您可以作为表单数据发送
curl -X POST \
http://localhost:54040/oauth/token \
-H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
-F grant_type=system \
-F username=udara \
-F password=udara123 \
-F client_id=wfw3e5454353wwrwrtr \
-F client_secret=432fwrw5425242543w245325
或作为 x-www-form-urlencoded
curl -X POST \
http://localhost:54040/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=internal&username=udara&password=udara123&client_id=wfw3e5454353wwrwrtr&client_secret=432fwrw5425242543w245325'