Spring Boot移动端API:自定义JWT与OAuth第三方登录整合方案问询
混合认证实现:自定义JWT + OAuth第三方登录(Spring Boot 移动端API)
一、移动端运作逻辑
- 集成Google/Facebook官方SDK,引导用户完成第三方登录,获取第三方平台颁发的
access_token - 调用后端专属接口(如
/api/auth/oauth/exchange),携带access_token和第三方标识(provider=google/facebook) - 后端验证通过后,返回自定义JWT给移动端
- 后续所有API请求,移动端在请求头中携带
Authorization: Bearer {自定义JWT},后端通过JWT过滤器完成身份校验
二、后端实现步骤
1. 依赖引入(Maven)
<dependencies> <!-- Spring Security 核心 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- OAuth2 客户端支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <!-- JJWT 用于JWT生成/解析 --> <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> <!-- Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
2. 第三方OAuth配置(application.yml)
spring: security: oauth2: client: registration: google: client-id: YOUR_GOOGLE_CLIENT_ID client-secret: YOUR_GOOGLE_CLIENT_SECRET scope: email,profile facebook: client-id: YOUR_FACEBOOK_CLIENT_ID client-secret: YOUR_FACEBOOK_CLIENT_SECRET scope: email,public_profile provider: google: user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo facebook: user-info-uri: https://graph.facebook.com/me?fields=id,name,email,picture
3. 核心业务实现
3.1 第三方Token验证服务
@Service public class OAuthTokenValidationService { private final WebClient webClient; private final OAuth2ClientRegistrationRepository clientRegistrationRepository; public OAuthTokenValidationService(WebClient.Builder webClientBuilder, OAuth2ClientRegistrationRepository clientRegistrationRepository) { this.webClient = webClientBuilder.build(); this.clientRegistrationRepository = clientRegistrationRepository; } public Optional<OAuth2UserInfo> validateToken(String provider, String accessToken) { OAuth2ClientRegistration registration = clientRegistrationRepository.findByRegistrationId(provider); if (registration == null) { return Optional.empty(); } try { Map<String, Object> userInfo = webClient.get() .uri(registration.getProviderDetails().getUserInfoEndpoint().getUri()) .headers(headers -> headers.setBearerAuth(accessToken)) .retrieve() .bodyToMono(Map.class) .block(); if (userInfo == null) return Optional.empty(); // 统一封装用户信息(适配不同平台字段差异) OAuth2UserInfo userInfoDto = switch (provider) { case "google" -> new OAuth2UserInfo( userInfo.get("sub").toString(), userInfo.get("email").toString(), userInfo.get("name").toString(), userInfo.get("picture").toString() ); case "facebook" -> new OAuth2UserInfo( userInfo.get("id").toString(), userInfo.get("email").toString(), userInfo.get("name").toString(), ((Map<String, String>) userInfo.get("picture")).get("url") ); default -> null; }; return Optional.ofNullable(userInfoDto); } catch (Exception e) { return Optional.empty(); } } // 统一用户信息DTO public record OAuth2UserInfo(String externalId, String email, String name, String avatar) {} }
3.2 自定义JWT工具类
@Component public class JwtTokenProvider { @Value("${jwt.secret}") private String secretKey; @Value("${jwt.expiration-ms}") private long expirationMs; // 生成自定义JWT public String generateToken(OAuth2UserInfo userInfo) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + expirationMs); return Jwts.builder() .setSubject(userInfo.email()) .claim("externalId", userInfo.externalId()) .claim("name", userInfo.name()) .claim("avatar", userInfo.avatar()) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secretKey) .compact(); } // 解析JWT获取用户邮箱 public String getEmailFromToken(String token) { return Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token) .getBody() .getSubject(); } // 验证JWT有效性 public boolean validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token); return true; } catch (JwtException | IllegalArgumentException e) { return false; } } }
3.3 OAuth转自定义JWT接口
@RestController @RequestMapping("/api/auth") public class AuthController { private final OAuthTokenValidationService validationService; private final JwtTokenProvider jwtTokenProvider; public AuthController(OAuthTokenValidationService validationService, JwtTokenProvider jwtTokenProvider) { this.validationService = validationService; this.jwtTokenProvider = jwtTokenProvider; } @PostMapping("/oauth/exchange") public ResponseEntity<?> exchangeToken(@RequestParam String provider, @RequestParam String accessToken) { return validationService.validateToken(provider, accessToken) .map(userInfo -> { String customJwt = jwtTokenProvider.generateToken(userInfo); return ResponseEntity.ok(Map.of("accessToken", customJwt, "tokenType", "Bearer")); }) .orElse(ResponseEntity.badRequest().body("Invalid OAuth token or provider")); } }
3.4 JWT认证过滤器
@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { this.jwtTokenProvider = jwtTokenProvider; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = getJwtFromRequest(request); if (token != null && jwtTokenProvider.validateToken(token)) { String email = jwtTokenProvider.getEmailFromToken(token); // 构建认证对象,这里可根据业务添加角色信息 UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken( email, null, Collections.emptyList() ); auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(auth); } filterChain.doFilter(request, response); } private String getJwtFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (bearerToken != null && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } }
3.5 Security配置类
@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/auth/**").permitAll() .anyRequest().authenticated() ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }
4. 补充配置(application.yml)
添加JWT相关配置:
jwt: secret: YOUR_SECRET_KEY_HERE_至少32位字符串 expiration-ms: 86400000 # 24小时过期
内容的提问来源于stack exchange,提问作者Ivan Danilov
相关产品推荐
相关产品推荐

