Spring Boot3+Security6自定义认证与Google OAuth2登录JWT实现咨询
Spring Boot 3 + Spring Security 6: 自定义表单+Google OAuth2登录统一返回JWT实现方案
核心思路
不需要自定义OAuth2LoginAuthenticationFilter,Spring Security提供了足够的扩展点来实现你的需求:
- 自定义表单登录:通过
UserDetailsService完成密码校验,登录成功后用AuthenticationSuccessHandler生成JWT返回。 - Google OAuth2登录:利用默认的
OAuth2LoginAuthenticationFilter完成授权码交换、用户信息获取,然后通过自定义OAuth2AuthenticationSuccessHandler拦截认证成功事件,完成用户创建/查询、生成JWT并重定向到UI。
步骤实现
1. 依赖准备
在pom.xml(Maven)中添加必要依赖:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <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> </dependencies>
2. 统一JWT工具类
创建JwtUtil,确保两种登录方式生成的令牌格式一致:
@Component public class JwtUtil { private final String secretKey = "your-strong-secret-key"; // 建议从配置文件读取 private final long expirationMs = 86400000; // 24小时 public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expirationMs)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); } // 令牌解析、有效性验证等方法按需实现 }
3. 自定义表单登录实现
3.1 实现UserDetailsService
从数据库查询用户信息,完成密码校验:
@Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; // 自定义用户数据访问层 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username)); return User.withUsername(user.getUsername()) .password(user.getPassword()) .authorities(user.getAuthorities()) .build(); } }
3.2 表单登录成功处理器
登录成功后生成JWT并返回JSON响应:
@Component public class FormLoginSuccessHandler implements AuthenticationSuccessHandler { @Autowired private JwtUtil jwtUtil; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { UserDetails userDetails = (UserDetails) authentication.getPrincipal(); String token = jwtUtil.generateToken(userDetails); response.setContentType("application/json"); response.getWriter().write("{\"token\":\"" + token + "\"}"); response.getWriter().flush(); } }
4. Google OAuth2登录实现
4.1 配置OAuth2客户端信息
在application.yml中添加Google OAuth2配置:
spring: security: oauth2: client: registration: google: client-id: your-google-client-id client-secret: your-google-client-secret redirect-uri: "{baseUrl}/login/oauth2/code/google" scope: openid, email, profile provider: google: authorization-uri: https://accounts.google.com/o/oauth2/auth token-uri: https://oauth2.googleapis.com/token user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo user-name-attribute: sub
4.2 自定义OAuth2登录成功处理器
拦截认证成功事件,完成用户创建/查询、生成JWT并重定向到UI:
@Component public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler { @Autowired private JwtUtil jwtUtil; @Autowired private UserRepository userRepository; @Autowired private PasswordEncoder passwordEncoder; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; OAuth2User oauthUser = oauthToken.getPrincipal(); // 提取Google用户信息 String email = oauthUser.getAttribute("email"); String username = oauthUser.getAttribute("name"); // 查找或创建本地用户 User user = userRepository.findByEmail(email) .orElseGet(() -> { User newUser = new User(); newUser.setUsername(username); newUser.setEmail(email); // 生成随机密码(OAuth2用户无需密码登录) newUser.setPassword(passwordEncoder.encode(UUID.randomUUID().toString())); newUser.setAuthorities(Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))); return userRepository.save(newUser); }); // 生成统一格式的JWT UserDetails userDetails = User.withUsername(user.getUsername()) .password(user.getPassword()) .authorities(user.getAuthorities()) .build(); String token = jwtUtil.generateToken(userDetails); // 重定向到UI应用,将JWT作为参数或存入Cookie(推荐Cookie更安全) String redirectUrl = "http://your-ui-domain.com?token=" + token; response.sendRedirect(redirectUrl); } }
5. 配置SecurityFilterChain
整合所有组件,实现无状态认证:
@Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private CustomUserDetailsService userDetailsService; @Autowired private FormLoginSuccessHandler formLoginSuccessHandler; @Autowired private OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) // 前后端分离场景关闭CSRF .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 无状态,不创建会话 .authorizeHttpRequests(auth -> auth .requestMatchers("/login", "/oauth2/**", "/register").permitAll() // 开放登录、OAuth2回调、注册接口 .anyRequest().authenticated() ) .formLogin(form -> form .loginProcessingUrl("/login") // 表单登录提交地址 .successHandler(formLoginSuccessHandler) // 自定义表单登录成功逻辑 ) .oauth2Login(oauth2 -> oauth2 .successHandler(oAuth2LoginSuccessHandler) // 自定义OAuth2登录成功逻辑 ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt.decoder(jwtDecoder())) // 配置JWT校验逻辑 ); return http.build(); } @Bean public JwtDecoder jwtDecoder() { SecretKey secretKey = Keys.hmacShaKeyFor("your-strong-secret-key".getBytes()); return NimbusJwtDecoder.withSecretKey(secretKey).build(); } }
关键说明
- 为什么不用自定义
OAuth2LoginAuthenticationFilter:Spring Security的OAuth2LoginAuthenticationFilter已经封装了授权码交换、令牌验证、用户信息获取等核心逻辑,我们只需要在认证成功后插入自定义逻辑,AuthenticationSuccessHandler是官方提供的标准扩展点,完全满足需求,避免了修改过滤器带来的耦合问题。 - 无状态配置:通过
SessionCreationPolicy.STATELESS关闭会话,确保所有请求都通过JWT验证,符合RESTful无状态设计。 - 安全性:将JWT存入Cookie时,建议设置
HttpOnly和Secure属性防止XSS攻击;如果放在URL参数中,UI端需尽快将其存入安全存储,避免泄露。
内容的提问来源于stack exchange,提问作者Samet Baskıcı
相关产品推荐
相关产品推荐

