我正在使用 Spring Boot 应用程序开发一个解耦的 React.js 和 Java,并且刚刚完成了后端的第一部分。诚然,在使用 JWT 方面,我对 Spring Security 并没有牢固的掌握,但我一直在遵循视频教程,并尽我所能阅读文档。不幸的是,该教程有点过时了,我不得不重构其中的某些部分以解决弃用问题。我显然在某个时候犯了一个错误,但无法弄清楚我做错了什么。
当我访问 localhost:8080(后端运行的端口)时,出现 403 错误。这向我表明这是
SecurityConfiguration
的问题,因此我验证了“/”端点位于 permitAll()
行中,确实如此。这是我的全部SecurityConfiguration
:
package com.keyoflife.keyoflife.config;
import com.keyoflife.keyoflife.services.UserDetailsLoader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SecurityConfiguration {
private final UserDetailsLoader usersLoader;
private final JwtFilter jwtFilter;
public SecurityConfiguration(UserDetailsLoader usersLoader, JwtFilter jwtFilter) {
this.usersLoader = usersLoader;
this.jwtFilter = jwtFilter;
}
@Bean
public PasswordEncoder passwordEncoder() {
//TODO: remove this
//get the hashed version of 'asdfasdf' to save in db
//System.out.println(new BCryptPasswordEncoder().encode("asdfasdf"));
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, PasswordEncoder passwordEncoder)
throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(usersLoader).passwordEncoder(passwordEncoder);
return authenticationManagerBuilder.build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/user-dash").authenticated()
.requestMatchers(HttpMethod.OPTIONS,"/api/auth/**", "/login", "/").permitAll()
)
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(Customizer.withDefaults())
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin((login) -> login
.loginPage("/login")
.failureUrl("/login")
.defaultSuccessUrl("/user-dash"))
/* Logout configuration */
.logout((logout) -> logout
.logoutSuccessUrl("/"))
.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("http://127.0.0.1:8080");
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
configuration.setAllowedHeaders(Arrays.asList("Content-Type", "Authorization"));
configuration.setAllowCredentials(true); // for cookies. bc everyone likes cookies
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
接下来,我查看了堆栈跟踪,发现错误实际上发生在我的
JWTFilter
中的 chain.doFilter
行中,在 if()
方法的第一个 doFilterInternal
语句中看到:我将发布这个类和其他类一样似乎无关紧要,因为代码没有通过第一个返回:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
// Get authorization header and validate
final String header = request.getHeader("Authorization");
System.out.println(header);
//ensure header has text and starts with "Bearer "
if (!StringUtils.hasText(header) || (StringUtils.hasText(header) && !header.startsWith("Bearer "))) {
chain.doFilter(request, response); //Access Denied Exception thrown here because header is null
return;
}
//rest of method below
}
我添加了
System.out
行来查看 header
的值是什么,并发现当我尝试访问 localhost8080 时,该值是“Basic(字母数字字符串)”,但是当我尝试访问 / api/auth/login 端点与
{
"username":"testUser",
"password": "asdfasdf"
}
使用具有 JSON 正文和 POST 方法的 Postman,我收到 401 错误和 null
header
。我确实验证了这是我数据库中的实际用户。
我看过其他几篇关于这个问题的帖子,并且已经通读了它们。有几篇文章建议添加我拥有的 cors 配置(请参阅
SecurityConfiguration
代码)。另一个说将“授权”arg 添加到我已有的 getHeader
中的 JWTFilter
行。我不知道从这里该去哪里,所以这是我的第一篇 SO 帖子。我已经读了很多年了,但从来没有被困住,以至于我之前需要问一个问题。如果我遗漏了一些相关的内容,我深表歉意。请告诉我,我会编辑我的问题。我只是不想用无关的代码淹没帖子。
我花了三天时间才弄清楚,但我终于解决了我遇到的问题。事实证明,我没有正确配置 Postman,并且将有效负载发送到了错误的位置。我需要转到 Postman 中的“授权”选项卡并包含我正在使用的密钥,并在同一选项卡上输入有效负载信息。我从下面的 YouTube 视频中得到了答案。
https://www.youtube.com/watch?v=dLxCpd3IGys
如果链接不起作用或者您不想单击链接,请搜索 Postman 的“如何使用 JWT 授权”。