如何将InMemoryUserDetailsManager迁移至MySQL数据库
问题描述
我已找到诸多基于嵌入式数据库的相关解决方案,但尚未找到将InMemoryUserDetailsManager转换为使用MySQL数据库的具体实现方法。请问有人知晓该如何操作吗?
我附上了我认为与此问题相关的后端类代码:
原代码文件
SecurityConfiguration.java
import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.JdbcUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import javax.sql.DataSource; import java.util.List; @Configuration @EnableWebSecurity public class SecurityConfiguration { private final RsaKeyProperties rsaKeys; @Autowired private DataSource dataSource; public SecurityConfiguration(RsaKeyProperties rsaKeys) { this.rsaKeys = rsaKeys; } @Bean public AuthenticationManager authManager(UserDetailsService userDetailsService){ var authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); return new ProviderManager(authProvider); } //This is an in memory database, which adds users below. //TODO: Connect to MySQL in order to add an Add User page. @Bean public UserDetailsService user(){ return new InMemoryUserDetailsManager( User.withUsername("user") .password("{noop}password") .authorities("read") .build() ); } //Configures server to authenticate user @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ return http .cors(Customizer.withDefaults())//Configure cors security .csrf(csrf -> csrf.disable()) //disable cross-site request forgery .authorizeHttpRequests( auth -> auth .mvcMatchers("/token").permitAll()//Authenticate using "controller/AuthController" .anyRequest().authenticated() //authorize all requests from spring ) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))//disable session .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) //Configure with token from front end to backend. .exceptionHandling((ex) -> ex .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) .accessDeniedHandler(new BearerTokenAccessDeniedHandler()) ) .build(); } //Decodes Encrypted Token to access user profile @Bean JwtDecoder jwtDecoder(){ return NimbusJwtDecoder.withPublicKey(rsaKeys.publicKey()).build(); } //Encodes profile information into token @Bean JwtEncoder jwtEncoder(){ JWK jwk = new RSAKey.Builder(rsaKeys.publicKey()).privateKey(rsaKeys.privateKey()).build(); JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk)); return new NimbusJwtEncoder(jwks); } //Connects to Front End @Bean CorsConfigurationSource corsConfigurationSource(){ CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(List.of("http://localhost:5173")); configuration.setAllowedMethods(List.of("GET", "POST")); configuration.setAllowedHeaders(List.of("Authorization", "Content-Type")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }
RSAKeyProperties.java
import org.springframework.boot.context.properties.ConfigurationProperties; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; @ConfigurationProperties(prefix = "rsa") public record RsaKeyProperties(RSAPublicKey publicKey, RSAPrivateKey privateKey) { }
AuthController.java
import com.example.Commerce_Bank_Back_End.model.LoginRequest; import com.example.Commerce_Bank_Back_End.service.TokenService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class AuthController { public static final Logger LOG = LoggerFactory.getLogger(AuthController.class); private final TokenService tokenService; private final AuthenticationManager authenticationManager; public AuthController(TokenService tokenService, AuthenticationManager authenticationManager) { this.tokenService = tokenService; this.authenticationManager = authenticationManager; } //Authenticate using Postman, then generate token to be stored to the header. //************************************************************************ @PostMapping("/token") public String token(@RequestBody LoginRequest userLogin){ Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(userLogin.username(), userLogin.password())); return tokenService.generateToken(authentication); } }
TokenService.java
import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.stereotype.Service; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.stream.Collectors; @Service public class TokenService { private final JwtEncoder encoder; public TokenService(JwtEncoder encoder) { this.encoder = encoder; } //Generate Token for Logged in Session //************************************************************************ public String generateToken(Authentication authentication){ Instant now = Instant.now(); String scope = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(" ")); JwtClaimsSet claims = JwtClaimsSet.builder() .issuer("self") .issuedAt(now) .expiresAt(now.plus(1, ChronoUnit.HOURS)) .subject(authentication.getName()) .claim("scope", scope) .build(); return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); } }
解决方案
1. 添加MySQL驱动与JDBC依赖
若使用Maven,在pom.xml中引入以下依赖:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
2. 配置MySQL数据源
在application.properties中填写数据库连接信息:
spring.datasource.url=jdbc:mysql://localhost:3306/your_db_name?useSSL=false&serverTimezone=UTC spring.datasource.username=your_db_user spring.datasource.password=your_db_pwd spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
3. 替换内存用户管理为JDBC实现
修改SecurityConfiguration中的user() Bean,替换为JdbcUserDetailsManager,同时添加密码编码器替代明文密码:
@Bean public UserDetailsService userDetailsService() { JdbcUserDetailsManager userDetailsManager = new JdbcUserDetailsManager(dataSource); // 可选:首次运行时初始化默认用户 if (!userDetailsManager.userExists("user")) { UserDetails user = User.withUsername("user") .password(passwordEncoder().encode("password")) .authorities("read") .build(); userDetailsManager.createUser(user); } return userDetailsManager; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
4. 创建默认用户表结构
JdbcUserDetailsManager依赖固定表结构,需在MySQL中执行以下SQL:
CREATE TABLE users ( username VARCHAR(50) NOT NULL PRIMARY KEY, password VARCHAR(255) NOT NULL, enabled BOOLEAN NOT NULL ); CREATE TABLE authorities ( username VARCHAR(50) NOT NULL, authority VARCHAR(50) NOT NULL, PRIMARY KEY (username, authority), FOREIGN KEY (username) REFERENCES users(username) );
若需自定义表结构,可通过userDetailsManager.setUsersByUsernameQuery()等方法修改查询语句。
5. 更新认证提供者配置
确保认证提供者使用密码编码器,修改authManager方法:
@Bean public AuthenticationManager authManager(UserDetailsService userDetailsService){ DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); // 新增密码编码器配置 return new ProviderManager(authProvider); }
内容的提问来源于stack exchange,提问作者blueweimer
相关产品推荐
相关产品推荐

