我使用的是 Spring Boot 3.2 和 Spring Security 6.2,登录和注册控制器正常进行。即使返回了有效的令牌,但是如果您请求注册后控制器,则会出现空的 403 错误,如果您向尚未声明的控制器发送请求,则会出现空的 403 错误。我的目的是将 jwt 中包含的用户昵称作为作者存储,并将其传递给 dto 类。这是我的源代码。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtUtils jwtUtils;
private final JwtProvider jwtProvider;
public SecurityConfig(JwtUtils jwtUtils, JwtProvider jwtProvider) {
this.jwtUtils = jwtUtils;
this.jwtProvider = jwtProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/auth/**").permitAll()
.anyRequest().authenticated())
.addFilterBefore(new JwtFilter(jwtProvider, jwtUtils), UsernamePasswordAuthenticationFilter.class)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(exception -> exception.authenticationEntryPoint(entryPoint));
return http.build();
}
}
@Component
public class JwtUtils {
private final UserDetailsServiceImpl userDetailsService;
private final RefreshTokenRepository refreshTokenRepository;
private final JwtProvider jwtProvider;
private final MemberRepository memberRepository;
public JwtUtils(UserDetailsServiceImpl userDetailsService, RefreshTokenRepository refreshTokenRepository, JwtProvider jwtProvider, MemberRepository memberRepository) {
this.userDetailsService = userDetailsService;
this.refreshTokenRepository = refreshTokenRepository;
this.jwtProvider = jwtProvider;
this.memberRepository = memberRepository;
}
public String getHeaderToken(HttpServletRequest request, String type) {
return type.equals("Access")
? request.getHeader(jwtProvider.getAccessTokenHeader())
: request.getHeader(jwtProvider.getRefreshTokenHeader());
}
public Boolean tokenValidation(String token) {
try {
Jwts.parserBuilder().setSigningKey(jwtProvider.getKey()).build().parseClaimsJws(token);
return true;
} catch (Exception ex) {
log.error(ex.getMessage());
return false;
}
}
public Boolean refreshTokenValidation(String token) {
if (!tokenValidation(token)) return false;
Optional<RefreshToken> refreshToken = refreshTokenRepository.findByMemberEmail(getEmailFromToken(token));
return refreshToken.isPresent() && token.equals(refreshToken.get().getRefreshToken());
}
public Authentication createAuthentication(String email) {
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
public String getEmailFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(jwtProvider.getKey()).build().parseClaimsJws(token).getBody().getSubject();
}
public String getNicknameFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(jwtProvider.getKey()).build().parseClaimsJws(token).getBody().get("nickname", String.class);
}
public Member loadMemberByEmail(String email) {
return memberRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException("User not found: " + email));
}
}
@Component
public class JwtUtils {
private final UserDetailsServiceImpl userDetailsService;
private final RefreshTokenRepository refreshTokenRepository;
private final JwtProvider jwtProvider;
private final MemberRepository memberRepository;
public JwtUtils(UserDetailsServiceImpl userDetailsService, RefreshTokenRepository refreshTokenRepository, JwtProvider jwtProvider, MemberRepository memberRepository) {
this.userDetailsService = userDetailsService;
this.refreshTokenRepository = refreshTokenRepository;
this.jwtProvider = jwtProvider;
this.memberRepository = memberRepository;
}
public String getHeaderToken(HttpServletRequest request, String type) {
return type.equals("Access")
? request.getHeader(jwtProvider.getAccessTokenHeader())
: request.getHeader(jwtProvider.getRefreshTokenHeader());
}
public Boolean tokenValidation(String token) {
try {
Jwts.parserBuilder().setSigningKey(jwtProvider.getKey()).build().parseClaimsJws(token);
return true;
} catch (Exception ex) {
log.error(ex.getMessage());
return false;
}
}
public Boolean refreshTokenValidation(String token) {
if (!tokenValidation(token)) return false;
Optional<RefreshToken> refreshToken = refreshTokenRepository.findByMemberEmail(getEmailFromToken(token));
return refreshToken.isPresent() && token.equals(refreshToken.get().getRefreshToken());
}
public Authentication createAuthentication(String email) {
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
}
public String getEmailFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(jwtProvider.getKey()).build().parseClaimsJws(token).getBody().getSubject();
}
public String getNicknameFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(jwtProvider.getKey()).build().parseClaimsJws(token).getBody().get("nickname", String.class);
}
public Member loadMemberByEmail(String email) {
return memberRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException("User not found: " + email));
}
}
@Service
@Transactional
public class BoardServiceImpl implements BoardService {
private final BoardRepository boardRepository;
public BoardServiceImpl(BoardRepository boardRepository) {
this.boardRepository = boardRepository;
}
public void createBoard(BoardCreateRequestDto requestDto, String nickname) {
Board board = Board.builder()
.title(requestDto.getTitle())
.content(requestDto.getContent())
.category(requestDto.getCategory())
.writer(nickname)
.build();
boardRepository.save(board);
}
}
@Getter
@Setter
@NoArgsConstructor
public class BoardCreateRequestDto {
private String title;
private String content;
private Category category;
@Builder
public BoardCreateRequestDto(String title, String content, Category category) {
this.title = title;
this.content = content;
this.category = category;
}
}
@RestController
@RequestMapping("/api/v1/board")
public class BoardController {
private final JwtUtils jwtUtils;
private final BoardService boardService;
public BoardController(JwtUtils jwtUtils, BoardService boardService) {
this.jwtUtils = jwtUtils;
this.boardService = boardService;
}
@PostMapping("/new")
public ResponseEntity<String> createBoard(@RequestBody BoardCreateRequestDto requestDto, HttpServletRequest request) {
String token = jwtUtils.getHeaderToken(request, "Access");
if (token != null && !token.isEmpty()) {
String nickname = jwtUtils.getNicknameFromToken(token);
boardService.createBoard(requestDto, nickname);
return ResponseEntity.status(HttpStatus.CREATED).body("Board created");
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized");
}
}
}
@Log4j2
public class JwtFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;
private final JwtUtils jwtUtils;
public JwtFilter(JwtProvider jwtProvider, JwtUtils jwtUtils) {
this.jwtProvider = jwtProvider;
this.jwtUtils = jwtUtils;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String accessToken = jwtUtils.getHeaderToken(request, "Access");
String refreshToken = jwtUtils.getHeaderToken(request, "Refresh");
log.debug(accessToken);
log.debug(refreshToken);
try {
if (accessToken != null) {
if (jwtUtils.tokenValidation(accessToken)) {
setAuthentication(jwtUtils.getEmailFromToken(accessToken));
log.debug("Access token valid. Authentication set.");
} else if (refreshToken != null && jwtUtils.refreshTokenValidation(refreshToken)) {
refreshAccessToken(refreshToken, response);
log.debug("Refresh token valid. Access token refreshed");
} else {
log.debug("Access token invalid. Sending error response.");
jwtExceptionHandler(response, "Access Token Expired or Invalid", HttpStatus.UNAUTHORIZED);
return;
}
} else if (refreshToken != null && jwtUtils.refreshTokenValidation(refreshToken)) {
refreshAccessToken(refreshToken, response);
log.debug("Refresh token valid. Access token refreshed");
}
} catch (Exception e) {
log.error("Error during JWT validation: ", e);
jwtExceptionHandler(response, "Internal Server Error", HttpStatus.INTERNAL_SERVER_ERROR);
return;
}
filterChain.doFilter(request, response);
}
private void refreshAccessToken(String refreshToken, HttpServletResponse response) throws IOException {
String email = jwtUtils.getEmailFromToken(refreshToken);
Member member = jwtUtils.loadMemberByEmail(email);
String newAccessToken = jwtProvider.createToken(
new TokenPayload(email, member.getRole().name(), member.getNickname()), "Access"
);
jwtProvider.setHeaderAccessToken(response, newAccessToken);
setAuthentication(email);
}
private void setAuthentication(String email) {
Authentication authentication = jwtUtils.createAuthentication(email);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
private void jwtExceptionHandler(HttpServletResponse response, String msg, HttpStatus status) {
response.setStatus(status.value());
response.setContentType("application/json");
try {
String json = new ObjectMapper().writeValueAsString(Collections.singletonMap("error", msg));
response.getWriter().write(json);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
我尝试设置crsf并尝试了所有能做的事情,但没有成功。
本可以将其保留为评论,但我超出了字符限制-
代码看起来很合理,虽然有点复杂 - 关键是检查
SecurityContextHolder.getContext().setAuthentication(authentication)
是否被正确调用,看起来确实如此。
为了安心,您还可以根据您的代码检查
authentication.isAuthenticated()
是否为真(它应该是真的)。
您可以在您的
logging.level.org.springframework.security=TRACE
中尝试 application.properties
- 这可以给您一些见解 - 也许过滤器链下游的过滤器正在扰乱身份验证。
作为实验,您可以尝试在
LogoutFilter
之后而不是在 UsernamePasswordAuthenticationFilter
之前添加过滤器,例如
.addFilterAfter(new JwtFilter(jwtProvider, jwtUtils), LogoutFilter.class)