Spring 启动 -> UnsatisfiedDependencyException
Spring Boot -> UnsatisfiedDependencyException
我正在尝试使用 Oauth2 + JWT 激活安全性,但没有成功。当我尝试 运行 Spring 启动中的应用程序时,它返回此错误:通过字段 'authenticationManager' 表达的不满意的依赖性,我搜索了互联网但没有得到帮助我的东西。
我正在使用 Spring 启动:2.1.0.RELEASE
关注我的 类:
资源服务器:
@Configuration
@EnableWebSecurity
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("us3r").password("pwd").roles("ROLE");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/categorias").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.csrf().disable();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(true);
}
}
授权服务器:
@Configuration
@EnableAuthorizationServer
@Component
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("mobile").secret("p@sswd").scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token").accessTokenValiditySeconds(20)
.refreshTokenValiditySeconds(3600 * 24);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).accessTokenConverter(accessTokenConverter()).reuseRefreshTokens(false)
.authenticationManager(authenticationManager);
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("algaworks");
return accessTokenConverter;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
}
堆栈跟踪:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authorizationServerConfig': Unsatisfied dependency expressed through field 'authenticationManager'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.authentication.AuthenticationManager' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=authenticationManagerBean)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1378) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:575) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean[=13=](AbstractBeanFactory.java:320) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:846) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:863) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at com.example.algamoney.api.AlgamoneyApiApplication.main(AlgamoneyApiApplication.java:10) [classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_181]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_181]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.1.0.RELEASE.jar:2.1.0.RELEASE]
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.authentication.AuthenticationManager' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=authenticationManagerBean)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1646) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1205) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1166) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
... 24 common frames omitted
请尝试使用 authenticationManager
bean,如下所示:
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
您需要连接 AuthenticationManager
的原因是您正在使用 password
授权流程:
.authorizedGrantTypes("password", ...
通过密码授予流程,OAuth2 客户端将向您提供其客户端凭据以及用户的 username/password,并且该用户需要在您的系统中的某处定义。通过 AuthenticationManager
实例公开对这些用户进行身份验证的能力。
在授权服务器中,有两组用户:OAuth2 API 的客户端和应用程序的最终用户(想要登录的人)。
客户端 定义在 class 中,该 class 扩展了您拥有的 AuthorizationServerConfigurerAdapter
。您在配置 ClientDetailsServiceConfigurer
.
时定义它们
用户在扩展WebSecurityConfigurerAdapter
的class中定义,没有。您可以像这样定义它们并公开相应的 AuthenticationManager
bean(请注意,这是您的主要问题,但您的配置还有其他问题,因此请继续阅读):
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("us3r")
.password("pwd")
.roles("USER")
.build());
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
}
最后一个方法公开了 AuthenticationManager
bean,以便其他 Spring bean 可以依赖它(比如您的 AuthorizationServerConfig
bean)。
让我们也对您的其他 class 进行一些清理以减少噪音,以防您 运行 遇到更多障碍。
首先,AuthorizationServerConfig
。使用 @Configuration
和 @Component
是多余的。您可以只使用 @Configuration
。此外,这似乎是额外的工作,但在 Spring 安全 DSL 中使用良好的空白也很有帮助。因为流利的API,很强大,但也很容易让人看不懂。
此外,当您投入生产时,这会感觉更自然一些,但您需要指定用于客户端密码的哈希机制。由于您现在没有进行任何哈希处理,这可能会让人觉得有点奇怪,但是 you'll need to prepend it with "{noop}" 这个示例可以正常工作。
最后,20 秒对于访问令牌来说非常短。我建议至少几分钟,尽管很多 access tokens live for multiple hours。如果您希望您的用户必须每天重新登录,那么 24 小时刷新令牌就可以了。刷新令牌过期后,用户将需要使用她的凭据重新登录。
根据您现有的配置,我建议(尽管请继续关注此设置的其他一些问题):
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig
extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()
.withClient("mobile")
.secret("{noop}p@sswd")
.scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(180)
.refreshTokenValiditySeconds(3600 * 24);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.reuseRefreshTokens(false)
.authenticationManager(authenticationManager);
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter =
new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("algaworks");
return accessTokenConverter;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
}
现在,我注意到您的客户的名字是 mobile
。 移动客户端的现代建议是使用 PKCE,Spring 安全性尚不支持。不了解有关您的用例的所有信息(似乎您现在只是想让事情正常运行),不过,我只会提到移动应用程序不擅长保密,因此您需要 select 授予类型不需要移动应用程序存储客户端机密(并且 password
最肯定 确实 需要客户端机密)。
好的,第二,ResourceServerConfig
。我们可以删除 @EnableWebSecurity
,因为我们在新的 SecurityConfig
class 上有它。此外,资源服务器通常不维护一组用户(授权服务器有),因此我们可以删除第一个 configure
方法。
根据您现有的配置,我建议:
@Configuration
@EnableResourceServer
public class ResourceServerConfig
extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/categorias").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources)
throws Exception {
resources.stateless(true);
}
}
为了确定,我将这一切粘贴到我的 IDE 中的一个本地项目中,启动它并能够执行以下操作:
curl --user "mobile:p@sswd" localhost:8080/oauth/token -dgrant_type=password -dusername=us3r -dpassword=pwd
带令牌回来的:
{ "access_token":"eyJh...",
"token_type":"bearer",
"refresh_token":"eyJh...",
"expires_in":199,
"scope":"read write",
"jti":"09855c19-7a71-4314-bf8f-eb74689d158c" }
那我可以这样做:
export set TOKEN="eyJh..."
如果我这样做:
curl localhost:8080/yo
我收到 401,但如果我包含令牌:
curl -H "Authorization: Bearer $TOKEN" localhost:8080/yo
我得到一个 404!
我意识到这有点反气候,但是嘿,这表明我通过了身份验证。 :)
既然我已经说了所有这些,如果您仍在阅读,那么我还想指出,根据您的需要,Spring Security 5.1 提供了简化的 OAuth 2.0 API。它还没有授权服务器,但它有基于 JWT 的资源服务器和客户端。我鼓励您 take a look at the feature matrix 看看更新的内容是否具有您需要的功能。
我正在尝试使用 Oauth2 + JWT 激活安全性,但没有成功。当我尝试 运行 Spring 启动中的应用程序时,它返回此错误:通过字段 'authenticationManager' 表达的不满意的依赖性,我搜索了互联网但没有得到帮助我的东西。
我正在使用 Spring 启动:2.1.0.RELEASE
关注我的 类:
资源服务器:
@Configuration
@EnableWebSecurity
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("us3r").password("pwd").roles("ROLE");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/categorias").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.csrf().disable();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(true);
}
}
授权服务器:
@Configuration
@EnableAuthorizationServer
@Component
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("mobile").secret("p@sswd").scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token").accessTokenValiditySeconds(20)
.refreshTokenValiditySeconds(3600 * 24);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).accessTokenConverter(accessTokenConverter()).reuseRefreshTokens(false)
.authenticationManager(authenticationManager);
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("algaworks");
return accessTokenConverter;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
}
堆栈跟踪:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authorizationServerConfig': Unsatisfied dependency expressed through field 'authenticationManager'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.authentication.AuthenticationManager' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=authenticationManagerBean)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1378) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:575) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean[=13=](AbstractBeanFactory.java:320) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:846) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:863) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at com.example.algamoney.api.AlgamoneyApiApplication.main(AlgamoneyApiApplication.java:10) [classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_181]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_181]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.1.0.RELEASE.jar:2.1.0.RELEASE]
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.authentication.AuthenticationManager' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=authenticationManagerBean)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1646) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1205) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1166) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
... 24 common frames omitted
请尝试使用 authenticationManager
bean,如下所示:
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
您需要连接 AuthenticationManager
的原因是您正在使用 password
授权流程:
.authorizedGrantTypes("password", ...
通过密码授予流程,OAuth2 客户端将向您提供其客户端凭据以及用户的 username/password,并且该用户需要在您的系统中的某处定义。通过 AuthenticationManager
实例公开对这些用户进行身份验证的能力。
在授权服务器中,有两组用户:OAuth2 API 的客户端和应用程序的最终用户(想要登录的人)。
客户端 定义在 class 中,该 class 扩展了您拥有的 AuthorizationServerConfigurerAdapter
。您在配置 ClientDetailsServiceConfigurer
.
用户在扩展WebSecurityConfigurerAdapter
的class中定义,没有。您可以像这样定义它们并公开相应的 AuthenticationManager
bean(请注意,这是您的主要问题,但您的配置还有其他问题,因此请继续阅读):
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("us3r")
.password("pwd")
.roles("USER")
.build());
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
}
最后一个方法公开了 AuthenticationManager
bean,以便其他 Spring bean 可以依赖它(比如您的 AuthorizationServerConfig
bean)。
让我们也对您的其他 class 进行一些清理以减少噪音,以防您 运行 遇到更多障碍。
首先,AuthorizationServerConfig
。使用 @Configuration
和 @Component
是多余的。您可以只使用 @Configuration
。此外,这似乎是额外的工作,但在 Spring 安全 DSL 中使用良好的空白也很有帮助。因为流利的API,很强大,但也很容易让人看不懂。
此外,当您投入生产时,这会感觉更自然一些,但您需要指定用于客户端密码的哈希机制。由于您现在没有进行任何哈希处理,这可能会让人觉得有点奇怪,但是 you'll need to prepend it with "{noop}" 这个示例可以正常工作。
最后,20 秒对于访问令牌来说非常短。我建议至少几分钟,尽管很多 access tokens live for multiple hours。如果您希望您的用户必须每天重新登录,那么 24 小时刷新令牌就可以了。刷新令牌过期后,用户将需要使用她的凭据重新登录。
根据您现有的配置,我建议(尽管请继续关注此设置的其他一些问题):
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig
extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()
.withClient("mobile")
.secret("{noop}p@sswd")
.scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token")
.accessTokenValiditySeconds(180)
.refreshTokenValiditySeconds(3600 * 24);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.reuseRefreshTokens(false)
.authenticationManager(authenticationManager);
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter =
new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("algaworks");
return accessTokenConverter;
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
}
现在,我注意到您的客户的名字是 mobile
。 移动客户端的现代建议是使用 PKCE,Spring 安全性尚不支持。不了解有关您的用例的所有信息(似乎您现在只是想让事情正常运行),不过,我只会提到移动应用程序不擅长保密,因此您需要 select 授予类型不需要移动应用程序存储客户端机密(并且 password
最肯定 确实 需要客户端机密)。
好的,第二,ResourceServerConfig
。我们可以删除 @EnableWebSecurity
,因为我们在新的 SecurityConfig
class 上有它。此外,资源服务器通常不维护一组用户(授权服务器有),因此我们可以删除第一个 configure
方法。
根据您现有的配置,我建议:
@Configuration
@EnableResourceServer
public class ResourceServerConfig
extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/categorias").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources)
throws Exception {
resources.stateless(true);
}
}
为了确定,我将这一切粘贴到我的 IDE 中的一个本地项目中,启动它并能够执行以下操作:
curl --user "mobile:p@sswd" localhost:8080/oauth/token -dgrant_type=password -dusername=us3r -dpassword=pwd
带令牌回来的:
{ "access_token":"eyJh...",
"token_type":"bearer",
"refresh_token":"eyJh...",
"expires_in":199,
"scope":"read write",
"jti":"09855c19-7a71-4314-bf8f-eb74689d158c" }
那我可以这样做:
export set TOKEN="eyJh..."
如果我这样做:
curl localhost:8080/yo
我收到 401,但如果我包含令牌:
curl -H "Authorization: Bearer $TOKEN" localhost:8080/yo
我得到一个 404!
我意识到这有点反气候,但是嘿,这表明我通过了身份验证。 :)
既然我已经说了所有这些,如果您仍在阅读,那么我还想指出,根据您的需要,Spring Security 5.1 提供了简化的 OAuth 2.0 API。它还没有授权服务器,但它有基于 JWT 的资源服务器和客户端。我鼓励您 take a look at the feature matrix 看看更新的内容是否具有您需要的功能。