我正在尝试创建一个适用于基本身份验证(Spring Security JWT,工作正常)和社交身份验证(谷歌)的应用程序,第一次使用 OAuth2,看起来后端工作正常,我能够使用登录基本身份验证(用户 + 密码)以及 OAuth2。调用端点时http://localhost:8080/oauth2/authorization/google我可以看到身份验证上下文来自 OAuth2 Principal,所以没关系。
我的问题在这里: 假设我想创建前端,例如使用 React 并访问安全端点。 如果我使用基本身份验证,那没关系,因为对 http://localhost:8080/api/v1/auth/authenticate(基本身份验证端点,而不是 oauth2)的 POST 调用返回我可以用来访问安全端点的 Bearer 令牌带有 **授权 **header 和 Bearer token。但是如果我使用 Google OAuth2,在 http://localhost:8080/oauth2/authorization/google 中成功登录到 google 后,我找不到任何令牌存储在 LocalStorage 中并在进一步的请求中使用它,但是在谷歌授权之后我仍然通过身份验证,如果我做更多请求我可以访问安全端点。
这是我的配置:
`@Table(name = "customer") 公共类客户实现 UserDetails {
private Long id;
private String email, password, username...;
}`
安全配置
public class SecurityConfiguration {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
private final UserService userService;
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/" ,"/api/v1/public/**", "/auth", "/logout", "/api/v1/auth/**", "/oauth/**", "/login", "/oauth2/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.authenticationProvider(authenticationProvider)
.logout(l -> l.logoutSuccessUrl("/").permitAll())
.exceptionHandling()
.authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.sessionManagement()
//.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin().permitAll()
.loginPage("/login")
.successHandler(databaseAuthenticationSuccessHandler())
.failureHandler((request, response, exception) -> response.setStatus(HttpStatus.UNAUTHORIZED.value()))
.and()
.logout().logoutSuccessUrl("/").permitAll()
.and()
.oauth2Login()
.loginPage("/login")
.defaultSuccessUrl("/api/v1/secured")
.userInfoEndpoint()
.and()
.successHandler(oAuthAuthenticationSuccessHandler())
.failureHandler((request, response, exception) -> response.setStatus(HttpStatus.UNAUTHORIZED.value()));
return http.build();
}
@Bean
public AuthenticationSuccessHandler oAuthAuthenticationSuccessHandler() {
return new OAuthSuccessHandler(userService);
}
@Bean
public AuthenticationSuccessHandler databaseAuthenticationSuccessHandler() {
return new DatabaseLoginSuccessHandler(userService);
}
}
@Component
public class OAuthSuccessHandler implements AuthenticationSuccessHandler {
private final UserService userService;
@Autowired
public OAuthSuccessHandler(UserService userService) {
this.userService = userService;
}
@Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws IOException, ServletException {
DefaultOidcUser user = (DefaultOidcUser) authentication.getPrincipal();
userService.processOAuthLogin(user);
response.sendRedirect("/api/v1/secured");
}
}
public void processOAuthLogin(DefaultOidcUser user) {
log.info("Attempting to login using Google OAuth2 for user: {}", user.getEmail());
String email = user.getEmail();
Optional<Customer> customerOpt = customerRepository.findByEmail(email);
if(customerOpt.isPresent()) {
Customer customer = customerOpt.get();
if(customer.getProvider() != Provider.GOOGLE) {
log.error("User with email: {} already exists in DB.", email);
throw new IllegalArgumentException("An user already exists with that email, please log in using credentials");
}
} else {
Customer customer = new Customer(user);
customerRepository.save(customer);
}
}
JwtAuthenticationFilter
@Component
@RequiredArgsConstructor 公共类 JwtAuthenticationFilter 扩展 OncePerRequestFilter {
private static final String AUTH_HEADER = "Authorization";
private static final String BEARER_TOKEN = "Bearer ";
private final JwtService jwtService;
private final UserService authService;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
final String authHeader = request.getHeader(AUTH_HEADER);
if (request.getServletPath().contains("/api/v1/auth") || request.getServletPath().contains("/login")) {
filterChain.doFilter(request, response);
return;
}
if(authHeader == null || !authHeader.startsWith(BEARER_TOKEN)) {
filterChain.doFilter(request, response);
return;
}
String token = authHeader.substring(BEARER_TOKEN.length());
String username = jwtService.extractUsername(token);
if(username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails user = authService.loadUserByUsername(username);
if(jwtService.isTokenValid(token, user)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
user, null, user.getAuthorities()
);
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}