1. 시큐리티 설정
package jhcode.blog.security;
import jhcode.blog.security.jwt.JwtAuthenticationEntryPoint;
import jhcode.blog.security.jwt.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfigurationSource;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final CorsConfigurationSource corsConfigurationSource;
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.httpBasic(httpBasic -> httpBasic.disable())
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource))
.authorizeHttpRequests(authorize
-> authorize
.requestMatchers("/board/list",
"/board/{boardId}",
"/board/search",
"/user/checkId",
"/user/register",
"/user/login",
"/board/{boardId}/comment/list/**",
"/board/{boardId}/file/download/**").permitAll()
.requestMatchers("/user/**").hasRole("USER")
.requestMatchers("/board/**").hasRole("USER")
.requestMatchers("/board/{boardId}/comment/**").hasRole("USER")
.requestMatchers("/board/{boardId}/file/**").hasRole("USER"))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(excep -> excep.authenticationEntryPoint(jwtAuthenticationEntryPoint))
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
import jhcode.blog.security.jwt.JwtAuthenticationEntryPoint;
import jhcode.blog.security.jwt.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfigurationSource;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final CorsConfigurationSource corsConfigurationSource;
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.httpBasic(httpBasic -> httpBasic.disable())
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource))
.authorizeHttpRequests(authorize
-> authorize
.requestMatchers("/board/list",
"/board/{boardId}",
"/board/search",
"/user/checkId",
"/user/register",
"/user/login",
"/board/{boardId}/comment/list/**",
"/board/{boardId}/file/download/**").permitAll()
.requestMatchers("/user/**").hasRole("USER")
.requestMatchers("/board/**").hasRole("USER")
.requestMatchers("/board/{boardId}/comment/**").hasRole("USER")
.requestMatchers("/board/{boardId}/file/**").hasRole("USER"))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(excep -> excep.authenticationEntryPoint(jwtAuthenticationEntryPoint))
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
2. jwt 설정
package jhcode.blog.security.jwt;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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;
/**
* JWT 토큰의 유효성을 검사하고, 인증
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final JwtTokenUtil jwtTokenUtil;
@Value("${jwt.header}") private String HEADER_STRING;
@Value("${jwt.prefix}") private String TOKEN_PREFIX;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Thread currentThread = Thread.currentThread();
log.info("현재 실행 중인 스레드: " + currentThread.getName());
// get token
String header = request.getHeader(HEADER_STRING);
String username = null;
String authToken = null;
if (header != null && header.startsWith(TOKEN_PREFIX)) {
authToken = header.replace(TOKEN_PREFIX," ");
try {
username = this.jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException ex) {
log.info("fail get user id");
ex.printStackTrace();
} catch (ExpiredJwtException ex) {
log.info("Token expired");
ex.printStackTrace();
} catch (MalformedJwtException ex) {
log.info("Invalid JWT !!");
System.out.println();
ex.printStackTrace();
} catch (Exception e) {
log.info("Unable to get JWT Token !!");
e.getStackTrace();
}
} else {
log.info("JWT does not begin with Bearer !!");
}
if ((username != null) && (SecurityContextHolder.getContext().getAuthentication() == null)) {
//log.info(SecurityContextHolder.getContext().getAuthentication().getName());
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (this.jwtTokenUtil.validateToken(authToken, userDetails)) {
// All things going well
// Authentication stuff
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
log.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
} else {
log.info("Invalid JWT Token !!");
}
} else {
log.info("Username is null or context is not null !!");
}
filterChain.doFilter(request, response);
}
}
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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;
/**
* JWT 토큰의 유효성을 검사하고, 인증
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final JwtTokenUtil jwtTokenUtil;
@Value("${jwt.header}") private String HEADER_STRING;
@Value("${jwt.prefix}") private String TOKEN_PREFIX;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Thread currentThread = Thread.currentThread();
log.info("현재 실행 중인 스레드: " + currentThread.getName());
// get token
String header = request.getHeader(HEADER_STRING);
String username = null;
String authToken = null;
if (header != null && header.startsWith(TOKEN_PREFIX)) {
authToken = header.replace(TOKEN_PREFIX," ");
try {
username = this.jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException ex) {
log.info("fail get user id");
ex.printStackTrace();
} catch (ExpiredJwtException ex) {
log.info("Token expired");
ex.printStackTrace();
} catch (MalformedJwtException ex) {
log.info("Invalid JWT !!");
System.out.println();
ex.printStackTrace();
} catch (Exception e) {
log.info("Unable to get JWT Token !!");
e.getStackTrace();
}
} else {
log.info("JWT does not begin with Bearer !!");
}
if ((username != null) && (SecurityContextHolder.getContext().getAuthentication() == null)) {
//log.info(SecurityContextHolder.getContext().getAuthentication().getName());
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (this.jwtTokenUtil.validateToken(authToken, userDetails)) {
// All things going well
// Authentication stuff
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
log.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
} else {
log.info("Invalid JWT Token !!");
}
} else {
log.info("Username is null or context is not null !!");
}
filterChain.doFilter(request, response);
}
}
3. 프로세스
# jwt
request -> jwt 토큰 조회 -> 유저 정보조회 -> 스프링에 가지고 있던 유저정보 비교 (인증) -> 스프링 토큰저장
# spring security
서버 csrf 설정 / cors 설정 / 인증 + 인가 (요청 url 별 권한체크) / 스프링에 저장된 jwt 토큰에서 유저정보 체크
# 출처 https://github.com/jhcode33/react-spring-blog-backend?tab=readme-ov-file
'secure coding' 카테고리의 다른 글
RSA / AES 암호화 관련 (1) | 2025.03.11 |
---|---|
HTTPS / JWT / CSRF 개념 정리 (0) | 2025.03.11 |
js 유효성 검사 템플릿 (+SQL injection) (0) | 2024.05.08 |