Spring应用能否集成Realm Database(ROS)?账号校验与token实现咨询
Hey there! Let's walk through how to connect your Spring app to Realm Database (ROS) and set up token generation/validation that relies on your existing user credentials stored in ROS. Based on your scenario, we'll cover two common approaches you can take, along with code snippets to make it concrete.
1. First: Set Up ROS Integration in Spring
First, you need to add the Realm Java SDK to your Spring project to interact with ROS. If you're using Maven, add this dependency to your pom.xml:
<dependency> <groupId>io.realm</groupId> <artifactId>realm-java</artifactId> <version>10.15.1</version> <!-- Use the latest compatible version --> </dependency>
For Gradle, add this to your build.gradle:
implementation 'io.realm:realm-java:10.15.1'
Next, configure the Realm app instance in Spring to connect to your ROS server. Create a @Configuration class for this:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import io.realm.Realm; import io.realm.RealmConfiguration; @Configuration public class RealmConfig { @Bean public Realm realmInstance() { RealmConfiguration config = new RealmConfiguration.Builder() .appId("your-ros-app-id") // Replace with your ROS app ID .baseUrl("https://your-ros-server-url") // Replace with your ROS server URL .build(); Realm.setDefaultConfiguration(config); return Realm.getDefaultInstance(); } }
2. Approach 1: Validate ROS-Generated Tokens in Spring
If your Android app already logs into ROS and gets a ROS access token, you can have Spring verify this token directly to protect your endpoints. Here's how:
Step 2.1: Create a Token Validation Service
This service checks if the incoming ROS token is valid by querying ROS:
import org.springframework.stereotype.Service; import io.realm.mongodb.App; import io.realm.mongodb.AppConfiguration; import io.realm.mongodb.User; import io.realm.mongodb.auth.Credentials; import io.realm.mongodb.auth.JwtCredentials; @Service public class RosTokenValidationService { private final App rosApp; public RosTokenValidationService() { AppConfiguration appConfig = new AppConfiguration.Builder("your-ros-app-id") .baseUrl("https://your-ros-server-url") .build(); this.rosApp = App.create(appConfig); } public boolean validateRosToken(String token) { try { // Authenticate using the ROS JWT token User user = rosApp.login(Credentials.jwt(token)); // If login succeeds, token is valid; you can also check user status return user.isLoggedIn(); } catch (Exception e) { // Token is invalid or expired return false; } } }
Step 2.2: Integrate with Spring Security
Add a custom filter to Spring Security that checks the incoming token in the Authorization header:
import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @Component public class RosTokenAuthenticationFilter extends OncePerRequestFilter { private final RosTokenValidationService tokenValidationService; public RosTokenAuthenticationFilter(RosTokenValidationService tokenValidationService) { this.tokenValidationService = tokenValidationService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; } String token = authHeader.substring(7); if (tokenValidationService.validateRosToken(token)) { // Create an authentication object (add user details here if needed) UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( "ros-user", null, null ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } filterChain.doFilter(request, response); } }
Then update your Spring Security configuration to use this filter:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { private final RosTokenAuthenticationFilter rosTokenFilter; public SecurityConfig(RosTokenAuthenticationFilter rosTokenFilter) { this.rosTokenFilter = rosTokenFilter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/images/**").authenticated() .anyRequest().permitAll() ) .addFilterBefore(rosTokenFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }
3. Approach 2: Spring Generates JWT Tokens After ROS Credential Check
If you want Spring to handle its own JWT tokens (instead of using ROS tokens directly), you can have Android send the username/password to Spring, which then validates against ROS before issuing a JWT.
Step 3.1: Add JWT Dependencies
Add these dependencies to your pom.xml (Maven):
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
Step 3.2: Create ROS Credential Validation Service
This service checks if the username/password matches what's stored in ROS:
import org.springframework.stereotype.Service; import io.realm.mongodb.App; import io.realm.mongodb.AppConfiguration; import io.realm.mongodb.User; import io.realm.mongodb.auth.Credentials; import io.realm.mongodb.auth.EmailPasswordCredentials; @Service public class RosCredentialValidationService { private final App rosApp; public RosCredentialValidationService() { AppConfiguration appConfig = new AppConfiguration.Builder("your-ros-app-id") .baseUrl("https://your-ros-server-url") .build(); this.rosApp = App.create(appConfig); } public User validateCredentials(String username, String password) { try { Credentials credentials = Credentials.emailPassword(username, password); return rosApp.login(credentials); } catch (Exception e) { // Invalid credentials or login failed return null; } } }
Step 3.3: JWT Token Service
Create a service to generate and validate JWT tokens:
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Service; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @Service public class JwtTokenService { private final String SECRET_KEY = "your-secret-key-keep-it-safe"; // Use a secure key in production private final long EXPIRATION_TIME = 86400000; // 24 hours public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } public Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { final Claims claims = extractAllClaims(token); return claimsResolver.apply(claims); } private Claims extractAllClaims(String token) { return Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token).getBody(); } private Boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } public String generateToken(String username) { Map<String, Object> claims = new HashMap<>(); return createToken(claims, username); } private String createToken(Map<String, Object> claims, String subject) { return Jwts.builder().setClaims(claims).setSubject(subject) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact(); } public Boolean validateToken(String token, String username) { final String extractedUsername = extractUsername(token); return (extractedUsername.equals(username) && !isTokenExpired(token)); } }
Step 3.4: Login Endpoint and Spring Security Setup
Create a controller to handle login requests:
import org.springframework.http.ResponseEntity; 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 { private final RosCredentialValidationService credentialService; private final JwtTokenService jwtTokenService; public AuthController(RosCredentialValidationService credentialService, JwtTokenService jwtTokenService) { this.credentialService = credentialService; this.jwtTokenService = jwtTokenService; } @PostMapping("/api/login") public ResponseEntity<String> login(@RequestBody LoginRequest request) { var user = credentialService.validateCredentials(request.getUsername(), request.getPassword()); if (user != null) { String token = jwtTokenService.generateToken(user.getEmail()); return ResponseEntity.ok(token); } return ResponseEntity.status(401).body("Invalid credentials"); } // DTO for login request public static class LoginRequest { private String username; private String password; // Getters and setters 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; } } }
Then update the Spring Security filter to validate JWT tokens instead:
import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenService jwtTokenService; public JwtAuthenticationFilter(JwtTokenService jwtTokenService) { this.jwtTokenService = jwtTokenService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); String username = null; String jwt = null; if (authHeader != null && authHeader.startsWith("Bearer ")) { jwt = authHeader.substring(7); username = jwtTokenService.extractUsername(jwt); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { if (jwtTokenService.validateToken(jwt, username)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( username, null, null ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } filterChain.doFilter(request, response); } }
And update the SecurityConfig to use this filter:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/login").permitAll() .requestMatchers("/api/images/**").authenticated() .anyRequest().permitAll() ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }
Key Notes for Production
- Secure Secrets: Store your ROS app ID, server URL, and JWT secret key in environment variables (use Spring Boot's
application.propertieswith placeholders or a secrets manager) instead of hardcoding. - User Details: For more robust authentication, you can fetch user roles/permissions from ROS and add them to the Spring Security authentication object.
- Token Expiry: Configure appropriate token expiry times based on your security needs.
- Error Handling: Add proper exception handling in your services and controllers to return meaningful error responses.
内容的提问来源于stack exchange,提问作者kevingiroux

