带有jdbc令牌存储的spring boot oauth2给出oauth_access_token关系不存在
Posted
技术标签:
【中文标题】带有jdbc令牌存储的spring boot oauth2给出oauth_access_token关系不存在【英文标题】:spring boot oauth2 with jdbc token store gives oauth_access_token relation doesn't exist 【发布时间】:2016-03-14 05:20:41 【问题描述】:我正在尝试将 Spring Boot 与 OAuth2 集成。通过遵循此https://github.com/royclarkson/spring-rest-service-oauth,我能够使其与 InMemoryStore 一起用于令牌。但是当我尝试使用 JdbcTokenStore 和 postgres 数据库来实现它时,我得到了错误
Handling error: BadSqlGrammarException, PreparedStatementCallback; bad SQL grammar [select token_id, token from oauth_access_token where authentication_id = ?]; nested exception is org.postgresql.util.PSQLException: ERROR: relation "oauth_access_token" does not exist
我检查了我的数据库,该表存在。
网络安全配置
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
private static PasswordEncoder encoder;
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception
return super.authenticationManagerBean();
@Bean
public PasswordEncoder passwordEncoder()
if(encoder == null)
encoder = new BCryptPasswordEncoder();
return encoder;
Oauth2Config
@Configuration
public class OAuth2ServerConfiguration
private static final String RESOURCE_ID = "restservice";
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends
ResourceServerConfigurerAdapter
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources)
// @formatter:off
resources
.tokenStore(tokenStore)
.resourceId(RESOURCE_ID);
// @formatter:on
@Override
public void configure(HttpSecurity http) throws Exception
// @formatter:off
http
.authorizeRequests()
.antMatchers("/users").hasRole("ADMIN")
.antMatchers("/userAccounts/create").permitAll()
.antMatchers("/greeting").authenticated();
// @formatter:on
@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter
@Autowired
DataSource dataSource;
@Bean
public JdbcTokenStore tokenStore()
return new JdbcTokenStore(dataSource);
private static PasswordEncoder encoder;
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception
// @formatter:off
endpoints
//.tokenStore(new InMemoryTokenStore())
.tokenStore(tokenStore())
.authenticationManager(this.authenticationManager)
.userDetailsService(userDetailsService);
// @formatter:on
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception
// @formatter:off
clients
//.inMemory()
.jdbc(dataSource)
.passwordEncoder(passwordEncoder());
//.withClient("clientapp")
//.authorizedGrantTypes("password", "refresh_token")
//.authorities("USER")
//.scopes("read", "write")
//.resourceIds(RESOURCE_ID)
//.secret("123456");
// @formatter:on
@Bean
@Primary
public DefaultTokenServices tokenServices()
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(tokenStore());
//tokenServices.setTokenStore(new InMemoryTokenStore());
return tokenServices;
@Bean
public PasswordEncoder passwordEncoder()
if(encoder == null)
encoder = new BCryptPasswordEncoder();
return encoder;
CustomUserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService
private AccountInfoRepository accountInfoRepository;
@Autowired
public CustomUserDetailsService(AccountInfoRepository accountInfoRepository)
this.accountInfoRepository = accountInfoRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
AccountInfo user = accountInfoRepository.findByUsername(username);
System.out.println("USER IS "+user);
if (user == null)
throw new UsernameNotFoundException(String.format("User %s does not exist!", username));
return new UserRepositoryUserDetails(user);
private final static class UserRepositoryUserDetails extends AccountInfo implements UserDetails,Serializable
private static final long serialVersionUID = 1L;
private UserRepositoryUserDetails(AccountInfo user)
super(user);
@Override
public Collection<? extends GrantedAuthority> getAuthorities()
return getRoles();
@Override
public boolean isAccountNonExpired()
return true;
@Override
public boolean isAccountNonLocked()
return true;
@Override
public boolean isCredentialsNonExpired()
return true;
@Override
public boolean isEnabled()
return true;
application.properties
spring.jpa.database=POSTGRESQL
spring.datasource.platform=postgres
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.default_schema=test
spring.jpa.hibernate.ddl-auto=none
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/test
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.schema=test
spring.profiles.active=dev
#Application specific
security.oauth2.client.client-id=clientapp
security.oauth2.client.client-secret=123456
security.oauth2.client.authorized-grant-types=password,refresh_token
security.oauth2.client.authorities=ROLE_USER
security.oauth2.client.scope=read,write
security.oauth2.client.resource-ids=restservice
security.oauth2.client.access-token-validity-seconds=1800
用户对象
@JsonIgnoreProperties(ignoreUnknown = true)
@Entity
@Table(name = "account_info")
public class AccountInfo implements Serializable
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "account_id")
Integer accountId;
@Column(name = "account_name")
String accountName;
@Column(name = "address_line_1")
String addressLine1;
@Column(name = "address_line_2")
String addressLine2;
String city;
String state;
String country;
@NotEmpty
@Column(unique = true, nullable = false)
String username;
@NotEmpty
String password;
String email;
@JsonIgnore
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id") , inverseJoinColumns = @JoinColumn(name = "role_id") )
private Set<Role> roles = new HashSet<>();
public AccountInfo()
public AccountInfo(AccountInfo accountInfo)
this.accountId = accountInfo.getAccountId();
this.accountName = accountInfo.getAccountName();
this.username = accountInfo.getUsername();
this.password = accountInfo.getPassword();
this.roles = accountInfo.getRoles();
public boolean isSetup()
return isSetup;
public void setSetup(boolean isSetup)
this.isSetup = isSetup;
public Integer getAccountId()
return accountId;
public void setAccountId(Integer accountId)
this.accountId = accountId;
public String getAccountName()
return accountName;
public void setAccountName(String accountName)
this.accountName = accountName;
public String getAddressLine1()
return addressLine1;
public void setAddressLine1(String addressLine1)
this.addressLine1 = addressLine1;
public String getAddressLine2()
return addressLine2;
public void setAddressLine2(String addressLine2)
this.addressLine2 = addressLine2;
public String getCity()
return city;
public void setCity(String city)
this.city = city;
public String getUsername()
return username;
public void setUsername(String username)
this.username = username;
public String getPassword()
return password;
public void setPassword(String password)
this.password = password;
public String getEmail()
return email;
public void setEmail(String email)
this.email = email;
public Set<Role> getRoles()
return roles;
public void setRoles(Set<Role> roles)
this.roles = roles;
这就是我创建用户的方式
account.setPassword(new BCryptPasswordEncoder().encode(account.getPassword()));
Set<Role> roles = new HashSet<>();
roles.add(new Role("ROLE_USER",1));
account.setRoles(roles);
AccountInfo savedAccount=accountInfoRepository.save(account);
OAuth2 表
CREATE TABLE oauth_client_details (
client_id VARCHAR(256) PRIMARY KEY,
resource_ids VARCHAR(256),
client_secret VARCHAR(256),
scope VARCHAR(256),
authorized_grant_types VARCHAR(256),
web_server_redirect_uri VARCHAR(256),
authorities VARCHAR(256),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information VARCHAR(4096),
autoapprove VARCHAR(256)
);
ALTER TABLE oauth_client_details OWNER TO postgres;
CREATE TABLE oauth_client_token (
token_id VARCHAR(256),
token bytea,
authentication_id VARCHAR(256),
user_name VARCHAR(256),
client_id VARCHAR(256)
);
ALTER TABLE oauth_client_token OWNER TO postgres;
CREATE TABLE oauth_access_token (
token_id VARCHAR(256),
token bytea,
authentication_id VARCHAR(256),
user_name VARCHAR(256),
client_id VARCHAR(256),
authentication bytea,
refresh_token VARCHAR(256)
);
ALTER TABLE oauth_access_token OWNER TO postgres;
CREATE TABLE oauth_refresh_token (
token_id VARCHAR(256),
token bytea,
authentication bytea
);
ALTER TABLE oauth_refresh_token OWNER TO postgres;
CREATE TABLE oauth_code (
code VARCHAR(256), authentication bytea
);
ALTER TABLE oauth_code OWNER TO postgres;
【问题讨论】:
验证数据时,您确定连接到与应用相同的数据库吗?在我看来,它只是找不到桌子。配置中的凭据错误? 它击中了正确的数据库。在 CustomUserDetailsService 中有一个打印语句检索用户USER IS AccountInfoaccountId=1, accountName='HAppy', addressLine1='null', addressLine2='null', city='null', state='null', country='null', username='test', email='null'
。 这是 curl 请求 curl -X POST -vu clientapp:123456 http://localhost:8080/oauth/token -H "Accept: application/json" -d "password=test&username=test&grant_type=password&scope=read%20write&client_secret=123456&client_id=clientapp"
@DaveSyer:另外,您是否建议在生产环境中使用 InMemory 存储或 JdbcTokenStore 来持久化令牌?
好的,问题是 spring-boot 正在默认模式(公共)中寻找 oauth 特定表,而不是我定义的测试模式。这很奇怪,因为对于其余用户定义的表来说,它正在命中正确的模式。免责声明 - 我没有使用 Spring Boot 的数据库初始化功能自动生成我的模式。
@Appy 我被困在这一点上,但是对于 mysql ,如果这不敏感,请您分享您的代码仓库。谢谢
【参考方案1】:
OAuth2 JDBC 连接器不知道架构。您需要将默认架构添加到数据库中的用户配置文件中,或者在 URL 中明确指定它。像这样的东西:jdbc:postgresql://localhost:5432/test?currentSchema=test
。
【讨论】:
所以我需要使用默认模式?我尝试在 URL 中设置当前架构,但不幸的是,这不起作用。 大多数人只是使用我认为的默认模式。如果您登录到服务器,很容易为用户设置默认架构(只需 google 即可)。【参考方案2】:通过从 oauth_access_token 表中删除所有内容并在资源服务器和身份验证服务器上添加设置 tokenStore 来解决此问题,如下所示:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
【讨论】:
【参考方案3】:得到它的工作
ALTER USER postgres SET search_path TO test,public;
USER
已被弃用,所以改为
ALTER ROLE postgres SET search_path TO test,public;
可以通过以下方式验证该值:
SHOW search_path;
这将在数据源的搜索路径中包含架构。所以 jdbc 查询现在会遇到表。
或者或更恰当地,仔细检查test?currentSchema=test
以确保数据库和架构与描述的一致。使用docker 时,它会根据POSTGRES_USER
设置数据库,除非使用POSTGRES_DB
指定,所以我的root
(您的情况postgres
)用户看不到test
。
【讨论】:
以上是关于带有jdbc令牌存储的spring boot oauth2给出oauth_access_token关系不存在的主要内容,如果未能解决你的问题,请参考以下文章
带有加密 JWT 访问令牌的 Spring Boot OAuth2
在 Spring Boot 和 Spring Security 中,不活动、过期的令牌会导致带有 Keycloak 的 IllegalStateException
带有 JWT auth 和 csrf 令牌的 Spring Boot STATELESS 应用程序