带有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;
        

    

CustomUserDetailsS​​ervice

@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;

【问题讨论】:

验证数据时,您确定连接到与应用相同的数据库吗?在我看来,它只是找不到桌子。配置中的凭据错误? 它击中了正确的数据库。在 CustomUserDetailsS​​ervice 中有一个打印语句检索用户 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&amp;username=test&amp;grant_type=password&amp;scope=read%20write&amp;client_secret=123456&amp;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中的哪里?

带有加密 JWT 访问令牌的 Spring Boot OAuth2

在 Spring Boot 和 Spring Security 中,不活动、过期的令牌会导致带有 Keycloak 的 IllegalStateException

带有 JWT auth 和 csrf 令牌的 Spring Boot STATELESS 应用程序

如何在 Spring Boot 应用程序上启用 Bearer 身份验证?

带有 Spring Boot REST 应用程序的 OAuth2 - 无法使用令牌访问资源