如何配置Spring Security实现OAuth2+JWT认证及自定义公开授权路径
解决方案
1. 完善SecurityConfig配置
调整SecurityConfig,确保/auth/google路径公开访问,配置OAuth2登录逻辑并绑定自定义成功处理器,同时添加JWT认证过滤器保护/api/**路径:
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.config.http.SessionCreationPolicy; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; this.oAuth2LoginSuccessHandler = oAuth2LoginSuccessHandler; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/auth/google/**").permitAll() .requestMatchers("/api/**").authenticated() .anyRequest().permitAll() ) .oauth2Login(oauth2 -> oauth2 .loginPage("/auth/google") .successHandler(oAuth2LoginSuccessHandler) .userInfoEndpoint(userInfo -> userInfo .userService(oAuth2UserService()) ) ); http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService() { DefaultOAuth2UserService delegate = new DefaultOAuth2UserService(); return request -> { OAuth2User oAuth2User = delegate.loadUser(request); // 可在此将Google返回的用户信息转换为系统内部用户对象 return oAuth2User; }; } }
2. 实现OAuth2登录成功处理器
负责生成JWT令牌,并重定向到前端页面携带令牌参数:
import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import java.io.IOException; @Component public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler { private final JwtUtil jwtUtil; private static final String FRONTEND_REDIRECT_URL = "http://localhost:3000/login-success"; public OAuth2LoginSuccessHandler(JwtUtil jwtUtil) { this.jwtUtil = jwtUtil; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); String email = oAuth2User.getAttribute("email"); String jwtToken = jwtUtil.generateToken(email); String redirectUrl = FRONTEND_REDIRECT_URL + "?token=" + jwtToken; response.sendRedirect(redirectUrl); } }
3. 实现JWT工具类
提供令牌生成、解析和验证功能:
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import org.springframework.stereotype.Component; import java.security.Key; import java.util.Date; @Component public class JwtUtil { private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256); private static final long EXPIRATION_TIME = 86400000; public String generateToken(String username) { return Jwts.builder() .setSubject(username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SECRET_KEY) .compact(); } public String extractUsername(String token) { return Jwts.parserBuilder() .setSigningKey(SECRET_KEY) .build() .parseClaimsJws(token) .getBody() .getSubject(); } public boolean validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token); return true; } catch (Exception e) { return false; } } }
4. 实现JWT认证过滤器
拦截/api/**请求,验证JWT令牌并设置认证上下文:
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.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; 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 JwtUtil jwtUtil; private final UserDetailsService userDetailsService; public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) { this.jwtUtil = jwtUtil; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authorizationHeader = request.getHeader("Authorization"); String username = null; String jwtToken = null; if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { jwtToken = authorizationHeader.substring(7); username = jwtUtil.extractUsername(jwtToken); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtUtil.validateToken(jwtToken)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } filterChain.doFilter(request, response); } }
5. 补充UserDetailsService实现
用于加载系统用户信息,示例中返回默认用户对象,生产环境需对接数据库:
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.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; @Service public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return new User(username, "", new ArrayList<>()); } }
关键说明
/auth/google是Spring Security OAuth2默认的认证入口,无需编写控制器,框架会自动处理跳转Google认证的逻辑。- 前端获取URL中的JWT后,需存入localStorage/sessionStorage,后续请求在
Authorization头中携带Bearer {token}。 - JWT密钥和过期时间建议从配置文件读取,避免硬编码;生产环境需使用HTTPS传输,防止令牌泄露。
内容的提问来源于stack exchange,提问作者M Ansyori
相关产品推荐
相关产品推荐

