自定义OAUTH_CLIENT_DETAILS表实现Spring REST OAuth2认证
Hey there! Since you already have a solid working custom authentication setup with UserDetails and your existing database tables, let's break down how to build your custom OAUTH_CLIENT_DETAILS table and integrate it seamlessly with your OAuth2 authorization server.
1. Design the Custom OAUTH_CLIENT_DETAILS Table Structure
Based on your request to mirror your user table plus add token-related fields, here's a SQL schema that combines OAuth2 client requirements with your existing table pattern:
CREATE TABLE OAUTH_CLIENT_DETAILS ( client_id VARCHAR(255) PRIMARY KEY COMMENT '类似user表的user_id,作为客户端唯一标识', name VARCHAR(255) NOT NULL COMMENT '客户端名称', email VARCHAR(255) UNIQUE COMMENT '客户端联系邮箱', password VARCHAR(255) NOT NULL COMMENT '客户端密钥,哈希存储,和user表的password处理逻辑一致', active BOOLEAN DEFAULT TRUE COMMENT '客户端是否启用,对应user表的active', access_token VARCHAR(512) COMMENT '新增的token字段,存储当前有效的access token', refresh_token VARCHAR(512) COMMENT '新增的refresh token字段', access_token_validity_seconds INT DEFAULT 3600 COMMENT 'access token有效期(秒)', refresh_token_validity_seconds INT DEFAULT 86400 COMMENT 'refresh token有效期(秒)', scope VARCHAR(255) DEFAULT 'read,write' COMMENT '客户端权限范围', authorized_grant_types VARCHAR(255) DEFAULT 'password,refresh_token' COMMENT '允许的授权类型', web_server_redirect_uri VARCHAR(255) COMMENT '授权回调地址(如果使用授权码模式)', authorities VARCHAR(255) COMMENT '客户端拥有的权限(和你的自定义角色权限体系对应)' );
Note: I added standard OAuth2 client fields alongside your requested token columns to ensure full compatibility with Spring Security OAuth2. Feel free to adjust default values, field lengths, or remove non-required fields to match your application's needs.
2. Create the Client Details Entity Class
Next, build a JPA entity that maps to this table and implements Spring's ClientDetails interface (extending BaseClientDetails will save you from writing boilerplate code):
import org.springframework.security.oauth2.provider.client.BaseClientDetails; import jakarta.persistence.*; import java.util.Set; import java.util.Arrays; @Entity @Table(name = "OAUTH_CLIENT_DETAILS") public class CustomClientDetails extends BaseClientDetails { @Id @Column(name = "client_id") private String clientId; @Column(name = "name") private String clientName; @Column(name = "email") private String email; @Column(name = "password") private String clientSecret; @Column(name = "active") private boolean enabled; @Column(name = "access_token") private String accessToken; @Column(name = "refresh_token") private String refreshToken; @Column(name = "access_token_validity_seconds") private Integer accessTokenValiditySeconds; @Column(name = "refresh_token_validity_seconds") private Integer refreshTokenValiditySeconds; @Column(name = "scope") private String scope; @Column(name = "authorized_grant_types") private String authorizedGrantTypes; // Override getters/setters to map to your entity fields @Override public String getClientId() { return this.clientId; } public void setClientId(String clientId) { this.clientId = clientId; } @Override public String getClientSecret() { return this.clientSecret; } public void setClientSecret(String clientSecret) { this.clientSecret = clientSecret; } @Override public boolean isEnabled() { return this.enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } @Override public Set<String> getScope() { return Set.of(this.scope.split(",")); } @Override public Set<String> getAuthorizedGrantTypes() { return Set.of(this.authorizedGrantTypes.split(",")); } // Add getters and setters for all remaining fields (clientName, email, tokens, validity, etc.) }
3. Implement ClientDetailsService with JPA Repository
First, create a JPA Repository for your CustomClientDetails entity:
import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface CustomClientDetailsRepository extends JpaRepository<CustomClientDetails, String> { Optional<CustomClientDetails> findByClientId(String clientId); }
Then build a custom ClientDetailsService implementation that uses this repository to fetch client data:
import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.ClientRegistrationException; import org.springframework.stereotype.Service; @Service public class CustomClientDetailsService implements ClientDetailsService { private final CustomClientDetailsRepository clientDetailsRepository; public CustomClientDetailsService(CustomClientDetailsRepository clientDetailsRepository) { this.clientDetailsRepository = clientDetailsRepository; } @Override public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException { return clientDetailsRepository.findByClientId(clientId) .orElseThrow(() -> new ClientRegistrationException("Client not found with id: " + clientId)); } }
4. Configure Authorization Server to Use Custom Client Details
Update your authorization server configuration to use your custom ClientDetailsService instead of the default in-memory or JDBC client store:
import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { private final CustomClientDetailsService customClientDetailsService; private final PasswordEncoder passwordEncoder; // Reuse your existing password encoder from user auth public AuthorizationServerConfig(CustomClientDetailsService customClientDetailsService, PasswordEncoder passwordEncoder) { this.customClientDetailsService = customClientDetailsService; this.passwordEncoder = passwordEncoder; } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(customClientDetailsService) .passwordEncoder(passwordEncoder); // Ensure client secrets are decoded correctly } // Keep your existing configuration for token store, authorization endpoints, etc. // If you want to persist tokens to your custom table automatically, implement a custom TokenStore }
5. Test the Setup
- Insert a test client into your
OAUTH_CLIENT_DETAILStable using a hashed password (use the same password encoder you use for user passwords). - Use a tool like Postman to send a token request (e.g., password grant type) using the client's
client_idandclient_secret. - Verify the server returns a valid token, and check if the
access_tokenandrefresh_tokenfields in your table are populated (if you added token persistence logic).
Tip: If you want to automatically persist tokens to your custom table, you'll need to implement a custom
TokenStorethat saves tokens directly to yourOAUTH_CLIENT_DETAILStable or a separate dedicated token table. The current setup uses Spring's default in-memory token store unless you configure otherwise.
内容的提问来源于stack exchange,提问作者Juan Carlos Rodriguez

