spring-security-oauth2 2.0.7 刷新令牌 UserDetailsService 配置 - 需要 UserDetailsService
spring-security-oauth2 2.0.7 refresh token UserDetailsService Configuration - UserDetailsService is required
我想问一个关于 spring-security-oauth2 2.0.7 配置的问题。
我正在通过 GlobalAuthenticationConfigurerAdapter 使用 LDAP 进行身份验证:
@SpringBootApplication
@Controller
@SessionAttributes("authorizationRequest")
public class AuthorizationServer extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(AuthorizationServer.class, args);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/oauth/confirm_access").setViewName("authorize");
}
@Configuration
public static class JwtConfiguration {
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(
new ClassPathResource("keystore.jks"), "foobar".toCharArray())
.getKeyPair("test");
converter.setKeyPair(keyPair);
return converter;
}
@Bean
public JwtTokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
}
@Configuration
@EnableAuthorizationServer
public static class OAuth2Config extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
private static final String ENV_OAUTH = "authentication.oauth.";
private static final String PROP_CLIENTID = "clientid";
private static final String PROP_SECRET = "secret";
private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver;
@Inject
private AuthenticationManager authenticationManager;
@Inject
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Inject
private JwtTokenStore jwtTokenStore;
@Inject
private UserDetailsService userDetailsService;
@Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(jwtTokenStore);
tokenServices.setAuthenticationManager(authenticationManager);
return tokenServices;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).tokenStore(jwtTokenStore).accessTokenConverter(
jwtAccessTokenConverter).userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
"isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(propertyResolver.getProperty(PROP_CLIENTID))
.scopes("read", "write")
.authorities(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)
.authorizedGrantTypes("authorization_code", "refresh_token", "password")
.secret(propertyResolver.getProperty(PROP_SECRET))
.accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800));
}
}
@Configuration
@Order(-10)
protected static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().loginPage("/login").permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
}
@Configuration
protected static class AuthenticationConfiguration extends
GlobalAuthenticationConfigurerAdapter {
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource().ldif("classpath:test-server.ldif");
}
}
}
虽然刷新令牌在 spring-security-oauth2 的 2.0.6 版中可以正常工作,但在 2.0.7 版中不再适用。
正如阅读 here,应该设置 AuthenticationManager
在刷新期间尝试获取新的访问令牌时使用。
据我了解,这与 spring-security-oauth2 的 following 更改有关。
不幸的是我没能正确设置它。
org.springframework.security.oauth2.provider.token.DefaultTokenServices#setAuthenticationManager
被调用并得到一个 AuthenticationManager
注入。我不确定我是否理解 LdapUserDetailsService
将如何被注入。我唯一看到的是 PreAuthenticatedAuthenticationProvider
在令牌刷新调用期间尝试重新验证用户时将被调用。
有人可以告诉我怎么做吗?
ps:我得到的异常如下:
p.PreAuthenticatedAuthenticationProvider : PreAuthenticated authentication request: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken@5775: Principal: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d5545: Principal: bob; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: IllegalStateException, UserDetailsService is required.
对于 OAuth 部分,您需要创建一个 LdapUserDetailsService
,使用与您的身份验证器相同的查询,并将其注入 AuthorizationServerEndpointsConfigurer
。我认为不支持在 @Configuration
样式中创建 UserDetailService
(可能值得在 JIRA 中为此开一张票),但看起来您可以在 XML 中创建.
根据 Dave Syer 的建议,我创建了一个自定义 LdapUserDetailsService
。
可以在以下 tag.
下找到工作解决方案
应用程序上下文
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:property-placeholder location="application.yml"/>
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="${authentication.ldap.url}" />
</bean>
<bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg index="0" value="${authentication.ldap.userSearchBase}" />
<constructor-arg index="1" value="uid={0}" />
<constructor-arg index="2" ref="contextSource"/>
</bean>
<bean id="ldapAuthoritiesPopulator" class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
<constructor-arg index="0" ref="contextSource"/>
<constructor-arg index="1" value="${authentication.ldap.groupSearchBase}"/>
<property name="groupSearchFilter" value="${authentication.ldap.groupSearchFilter}"/>
</bean>
<bean id="myUserDetailsService"
class="org.springframework.security.ldap.userdetails.LdapUserDetailsService">
<constructor-arg index="0" ref="userSearch"/>
<constructor-arg index="1" ref="ldapAuthoritiesPopulator"/>
</bean>
</beans>
属性
authentication:
ldap:
url: ldap://127.0.0.1:33389/dc=springframework,dc=org
userSearchBase:
userDnPatterns: uid={0},ou=people
groupSearchBase: ou=groups
groupSearchFilter: (uniqueMember={0})
我在使用 JWT 令牌和自定义 AuthenticationProvider
而不是 UserDetailsService
实现 OAuth2 服务器来解决登录身份验证时遇到了类似的问题。
但最近我发现,如果您希望 refresh_token
正常工作,那么 Spring 引发的错误是正确的。对于 AuthenticationProvider
实现,不可能使用 refresh_token
刷新令牌,因为在那种实现中,您必须解析密码是否正确,但刷新令牌没有该信息。但是,UserDetailsService
不知道密码。
spring-security-oauth2
的 2.0.6 版本有效,因为从不检查用户授权,只检查刷新令牌是否有效(使用私钥签名),但是,如果用户已从系统中删除首次登录后,使用刷新令牌删除的用户将无限次访问您的系统,这是一个很大的安全问题。
看看我报告的问题:https://github.com/spring-projects/spring-security-oauth/issues/813
我想问一个关于 spring-security-oauth2 2.0.7 配置的问题。 我正在通过 GlobalAuthenticationConfigurerAdapter 使用 LDAP 进行身份验证:
@SpringBootApplication
@Controller
@SessionAttributes("authorizationRequest")
public class AuthorizationServer extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(AuthorizationServer.class, args);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.addViewController("/oauth/confirm_access").setViewName("authorize");
}
@Configuration
public static class JwtConfiguration {
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyPair keyPair = new KeyStoreKeyFactory(
new ClassPathResource("keystore.jks"), "foobar".toCharArray())
.getKeyPair("test");
converter.setKeyPair(keyPair);
return converter;
}
@Bean
public JwtTokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
}
@Configuration
@EnableAuthorizationServer
public static class OAuth2Config extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
private static final String ENV_OAUTH = "authentication.oauth.";
private static final String PROP_CLIENTID = "clientid";
private static final String PROP_SECRET = "secret";
private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver;
@Inject
private AuthenticationManager authenticationManager;
@Inject
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Inject
private JwtTokenStore jwtTokenStore;
@Inject
private UserDetailsService userDetailsService;
@Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(jwtTokenStore);
tokenServices.setAuthenticationManager(authenticationManager);
return tokenServices;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager).tokenStore(jwtTokenStore).accessTokenConverter(
jwtAccessTokenConverter).userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
"isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(propertyResolver.getProperty(PROP_CLIENTID))
.scopes("read", "write")
.authorities(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)
.authorizedGrantTypes("authorization_code", "refresh_token", "password")
.secret(propertyResolver.getProperty(PROP_SECRET))
.accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800));
}
}
@Configuration
@Order(-10)
protected static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().loginPage("/login").permitAll()
.and()
.requestMatchers().antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests().anyRequest().authenticated();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return super.userDetailsServiceBean();
}
}
@Configuration
protected static class AuthenticationConfiguration extends
GlobalAuthenticationConfigurerAdapter {
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource().ldif("classpath:test-server.ldif");
}
}
}
虽然刷新令牌在 spring-security-oauth2 的 2.0.6 版中可以正常工作,但在 2.0.7 版中不再适用。
正如阅读 here,应该设置 AuthenticationManager
在刷新期间尝试获取新的访问令牌时使用。
据我了解,这与 spring-security-oauth2 的 following 更改有关。
不幸的是我没能正确设置它。
org.springframework.security.oauth2.provider.token.DefaultTokenServices#setAuthenticationManager
被调用并得到一个 AuthenticationManager
注入。我不确定我是否理解 LdapUserDetailsService
将如何被注入。我唯一看到的是 PreAuthenticatedAuthenticationProvider
在令牌刷新调用期间尝试重新验证用户时将被调用。
有人可以告诉我怎么做吗?
ps:我得到的异常如下:
p.PreAuthenticatedAuthenticationProvider : PreAuthenticated authentication request: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken@5775: Principal: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d5545: Principal: bob; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: IllegalStateException, UserDetailsService is required.
对于 OAuth 部分,您需要创建一个 LdapUserDetailsService
,使用与您的身份验证器相同的查询,并将其注入 AuthorizationServerEndpointsConfigurer
。我认为不支持在 @Configuration
样式中创建 UserDetailService
(可能值得在 JIRA 中为此开一张票),但看起来您可以在 XML 中创建.
根据 Dave Syer 的建议,我创建了一个自定义 LdapUserDetailsService
。
可以在以下 tag.
应用程序上下文
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<context:property-placeholder location="application.yml"/>
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<constructor-arg value="${authentication.ldap.url}" />
</bean>
<bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg index="0" value="${authentication.ldap.userSearchBase}" />
<constructor-arg index="1" value="uid={0}" />
<constructor-arg index="2" ref="contextSource"/>
</bean>
<bean id="ldapAuthoritiesPopulator" class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
<constructor-arg index="0" ref="contextSource"/>
<constructor-arg index="1" value="${authentication.ldap.groupSearchBase}"/>
<property name="groupSearchFilter" value="${authentication.ldap.groupSearchFilter}"/>
</bean>
<bean id="myUserDetailsService"
class="org.springframework.security.ldap.userdetails.LdapUserDetailsService">
<constructor-arg index="0" ref="userSearch"/>
<constructor-arg index="1" ref="ldapAuthoritiesPopulator"/>
</bean>
</beans>
属性
authentication:
ldap:
url: ldap://127.0.0.1:33389/dc=springframework,dc=org
userSearchBase:
userDnPatterns: uid={0},ou=people
groupSearchBase: ou=groups
groupSearchFilter: (uniqueMember={0})
我在使用 JWT 令牌和自定义 AuthenticationProvider
而不是 UserDetailsService
实现 OAuth2 服务器来解决登录身份验证时遇到了类似的问题。
但最近我发现,如果您希望 refresh_token
正常工作,那么 Spring 引发的错误是正确的。对于 AuthenticationProvider
实现,不可能使用 refresh_token
刷新令牌,因为在那种实现中,您必须解析密码是否正确,但刷新令牌没有该信息。但是,UserDetailsService
不知道密码。
spring-security-oauth2
的 2.0.6 版本有效,因为从不检查用户授权,只检查刷新令牌是否有效(使用私钥签名),但是,如果用户已从系统中删除首次登录后,使用刷新令牌删除的用户将无限次访问您的系统,这是一个很大的安全问题。
看看我报告的问题:https://github.com/spring-projects/spring-security-oauth/issues/813