Spring Security & JWT
build.gradle 설정
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.0'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'org.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
... 중략
// 20250625 - JWT 추가
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
implementation 'org.springframework.boot:spring-boot-starter-security'
...
}
tasks.named('test') {
useJUnitPlatform()
}
Java
복사
... 중략 ...
jwt.accessTokenExpirationTime=1000000
jwt.refreshTokenExpirationTime=86400000
jwt.secretKey=ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ
Java
복사
JWT 패키지 구성
JwtKey (jwt > JwtKey)
package org.example.backendproject.security.jwt;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JwtKey {
@Value("${jwt.secretKey}")
private String secretKey;
// 서명키를 만들어서 반환하는 메서드
@Bean
public SecretKey secretKey() {
byte[] keyBytes = secretKey.getBytes(); // 설정파일에서 불러온 키 값을 바이트 배열로 변환
return new SecretKeySpec(keyBytes, "HmacSHA256"); // 바이트 배열을 HmacSHA256용 Security 객체로 변환
}
}
Java
복사
Role (core > Role)
package org.example.backendproject.security.core;
// 회원가입시에 사용자의 권한을 정의
public enum Role {
ROLE_USER("USER"),
ROLE_ADMIN("ADMIN");
private String role;
Role(String role) {
this.role = role;
}
public String getRole() {
return this.role;
}
}
Java
복사
CustomUserDetails (core 패키지)
package org.example.backendproject.security.core;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.example.backendproject.user.entity.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {
// UserDetails <- 사용자 정보를 담는 인터페이스
// 로그인한 사용자의 정보를 담아두는 역할을 함
private final User user;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// User의 권한을 반환하는 메서드
// Collections.singleton <- 이 사용자는 한 가지 권한만 갖는다는 의미
return Collections.singleton(new SimpleGrantedAuthority(user.getRole().name()));
}
// 토큰에서 추출한 사용자 정보의 Id를 반환 (테이블의 pk 값)
// User 엔티티에서 Id 추출
public Long getId() {
return user.getId();
}
@Override
public String getPassword() {
return user.getPassword(); // User 엔티티에서 password 반환
}
@Override
public String getUsername() { // Username이라고 해서 username이라고 하기 보단 유니크한 값을 적용해야 함
return user.getUserid(); // User 엔티티와 참조되어 있는 UserProfile의 username 반환, 로그인 식별할 수 있는 값
}
/** 아래는 현재 계정 상태를 확인하는 메서드 **/
@Override // 현재 계정 상태가 활성화인지 여부 확인
public boolean isEnabled() {
return true;
}
@Override // 이 계정이 만료되었는지
public boolean isAccountNonExpired() {
return true;
}
@Override // 이 계정이 잠겨있는지
public boolean isAccountNonLocked() {
return true;
}
@Override // 자격증명이 만료되지 않았는지
public boolean isCredentialsNonExpired() {
return true;
}
}
Java
복사
User - Role 추가
... 생략 ...
@Enumerated(EnumType.STRING) // 이 필드를 DB에 문자열로 저장
private Role role;
Java
복사
CustomUserDetailsService
package org.example.backendproject.security.core;
import lombok.RequiredArgsConstructor;
import org.example.backendproject.user.entity.User;
import org.example.backendproject.user.repository.UserRepository;
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;
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
// 로그인할 때 스프링에서 DB에 현재 로그인 하는 사용자가 존재하는지 확인
@Override
public UserDetails loadUserByUsername(String userid) throws UsernameNotFoundException {
User user = userRepository.findByUserid(userid).orElseThrow(
() -> new IllegalArgumentException("해당 유저가 존재하지 않습니다 -> " + userid));
return new CustomUserDetails(user);
}
public UserDetails loadUserById(Long id) throws UsernameNotFoundException {
User user = userRepository.findById(id).orElseThrow(
() -> new IllegalArgumentException("해당 유저가 존재하지 않습니다 -> " + id));
return new CustomUserDetails(user);
}
}
Java
복사
JwtTokenProvider
package org.example.backendproject.security.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.UnsupportedJwtException;
import java.util.Date;
import javax.crypto.SecretKey;
import lombok.RequiredArgsConstructor;
import org.example.backendproject.security.core.CustomUserDetails;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class JwtTokenProvider {
/** JWT 토큰 생성 및 추출 검증하는 클래스 **/
private final SecretKey secretKey; // 토큰을 만들 때 서명하는 키
// 현재 인증된 사용자 정보를 기반으로 access, refresh token 발급
public String generateToken(Authentication authentication, Long expirationMillis) {
// 현재 로그인한 사용자의 정보를 꺼냄
CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();
Date expiryDate = new Date(new Date().getTime() + expirationMillis); // 토큰 만료시간 생성 (밀리초 단위까지)
Claims claims = Jwts.claims();
claims.put("user-id", customUserDetails.getId());
claims.put("username", customUserDetails.getUsername());
return Jwts.builder()
.setSubject(customUserDetails.getUsername()) // 이 JWT 토큰의 주체를 지정
.setClaims(claims) // payload
.setIssuedAt(new Date()) // 토큰 발급시간
.setExpiration(expiryDate) // 토큰 만료시간
.signWith(secretKey, SignatureAlgorithm.HS512) // secret 키와 알고리즘을 이용해서 암호화하여 서명
.compact(); // <- 위에서 저장한 정보들을 최종적으로 문자열로 만들어주는 메서드
}
// JWT 토큰에서 사용자 ID를 추출하는 메서드
public Long getUserIdFromToken(String token) {
return Jwts
.parserBuilder() // JWT 토큰을 해석하겠다고 선언
.setSigningKey(secretKey) // 토큰을 검증하기 위해 비밀키 사용
.build() // 해석할 준비완료
.parseClaimsJws(token) // 전달받은 토큰을 파싱
.getBody() // 파싱한 토큰의 payload 부분을 꺼내서
.get("user-id", Long.class); // user-id 를 반환
}
public Boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token);
return true;
} catch (MalformedJwtException e) {
// 토큰 형식이 잘못되었을 때
return false;
} catch (ExpiredJwtException e) {
// 토큰이 만료가 되었을 때
return false;
} catch (UnsupportedJwtException e) {
// 지원하지 않는 토큰일 때
return false;
} catch (IllegalArgumentException e) {
// 토큰 문자열이 비어있거나 이상할 때
return false;
} catch (JwtException e) {
// 기타 예외
return false;
}
}
}
Java
복사
JwtTokenFilter
package org.example.backendproject.security.jwt;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.backendproject.security.core.CustomUserDetailsService;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
@Component
@RequiredArgsConstructor
@Slf4j
public class JwtTokenFilter extends OncePerRequestFilter {
// JwtTokenFilter 모든 HTTP 요청을 가로채서 JWT 토큰을 검사하는 필터 역할
// OncePerRequestFilter는 한 요청당 딱 한 번만 실행되는 필터 역할
private final JwtTokenProvider jwtTokenProvider;
private final CustomUserDetailsService customUserDetailsService;
// HTTP 매 요청마다 호출
@Override
protected void doFilterInternal(HttpServletRequest request, // http 요청
HttpServletResponse response, // http 응답
FilterChain filterChain
) throws ServletException, IOException {
String accessToken = getTokenFromRequest(request); // 요청 헤더에서 토큰 추출
if (accessToken != null && jwtTokenProvider.validateToken(accessToken)) {
UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(accessToken);
// 토큰에서 사용자를 꺼내서 담은 사용자 인증 객체
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// http 요청으로부터 부가 정보(ip, 세션 등)를 추출해서 사용자 인증 객체에 넣어줌
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// 토큰에서 사용자 인증정보를 조회해서 인증정보를 현재 스레드에 인증된 사용자로 등록
String url = request.getRequestURI().toString();
log.info("현재 들어온 HTTP 요청 = " + url);
String method = request.getMethod(); // GET, POST, PUT ...
log.info("HTTP 메소드 + method = " + method);
}
/**
* CharacterEncodingFilter: 문자 인코딩 처리
* CorsFilter: CORS 정책 처리
* CsrfFilter: CSRF 보안 처리
* JWTTokenFilter: JWT 토큰 처리(핵심)
* SecurityContextFilter: 인증/인가 정보 저장
* ExceptionFilter: 예외 처리
*/
filterChain.doFilter(request, response); // JwtTokenFilter를 거치고 다음 필터로 넘어감 (이동...이동...이동)
}
// HTTP 요청 헤더에서 토큰을 추출하는 메서드
public String getTokenFromRequest(HttpServletRequest request) {
String token = null;
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
token = bearerToken.substring(7);
}
return token;
}
// http 요청에서 사용자 인증 정보를 담는 객체
private UsernamePasswordAuthenticationToken getAuthentication(String token) {
// JWT 토큰에서 사용자 userId 추출
Long userId = jwtTokenProvider.getUserIdFromToken(token);
// 위 추출한 id를 DB에서 사용자 정보 조회
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
return new UsernamePasswordAuthenticationToken(
userDetails, // 사용자 정보
null,
userDetails.getAuthorities()); // 사용자의 권한
}
}
Java
복사
SecurityConfig
package org.example.backendproject.security.config;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.example.backendproject.security.jwt.JwtTokenFilter;
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.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;
@Configuration // 설정 클래스 등록
@EnableWebSecurity // 스프링 시큐리티 활성화
@RequiredArgsConstructor // 생성자 자동 생성
public class SecurityConfig {
private final JwtTokenFilter jwtTokenFilter;
// 스프링 시큐리티에서 어떤 순서로 어떤 보안 규칙의 필터를 거칠지를 정의하는 클래스
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
// CSRF 보호 기능 비활성화
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/", "/index.html", "/*.html", "/favicon.ico",
"/css/**", "fetchWithAuth.js", "/js/**", "/images/**",
"/.well-know/**" ).permitAll() // 인증 필요없이 모두 허용
.requestMatchers("/boards/**", "/api/auth/**").permitAll()
.requestMatchers("/api/comments/**").permitAll()
.requestMatchers( "/api/user/**").authenticated() // 인증이 필요한 경로
)
// 인증 실패시 예외 처리
.exceptionHandling(e -> e
// 인증 안 된 사용자가 접근하려고 할 때
.authenticationEntryPoint((request, response, authException) -> {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
})
// 인증은 되었지만 권한이 없을 때
.authenticationEntryPoint((request, response, authException) -> {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
})
)
// 스프링 시큐리티에서 세션관리정책을 설정하는 부분
// 기본적으로 스프링 시큐리티는 세션을 생성함
// 하지만 JWT 기반 인증은 세션상태를 저장하지 않는 무상태(stateless) 방식
// 인증 정보를 세션에 저장하지 않고, 매 요청마다 토큰으로 인증
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
.build(); // 위 명시한 설정들을 적용
}
// 회원가입시 비밀번호를 암호화해주는 메서드
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Java
복사
Auth
•
LoginResponseDTO 생성 - Request가 있으면 Response도 있어야 함
package org.example.backendproject.Auth.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.example.backendproject.Auth.entity.Auth;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class LoginResponseDTO {
private String tokenType;
private String accessToken;
private String refreshToken;
private Long userId;
@Builder
public LoginResponseDTO(Auth auth) {
this.tokenType = auth.getTokenType();
this.accessToken = auth.getAccessToken();
this.refreshToken = auth.getRefreshToken();
this.userId = auth.getId();
}
}
Java
복사
•
업데이트 메서드 + Auth 생성자 추가
package org.example.backendproject.Auth.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.example.backendproject.user.entity.User;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Entity
public class Auth {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String tokenType;
@Column(nullable = false)
private String accessToken;
@Column(nullable = false)
private String refreshToken;
@OneToOne(fetch = FetchType.LAZY) // 1:1 관계, 지연로딩 적용 -> Auth 엔티티 조회할 때 user 객체는 불러오지 않음
@JoinColumn(name = "user_id") // auth.getUser()에 실제로 접근할 때 User 쿼리 발생!!
private User user;
// updateAccessToken 메서드 추가
//토큰값을 업데이트 해주는 메서드
public void updateAccessToken(String newAccessToken) {
this.accessToken = newAccessToken;
}
// updateRefreshToken 메서드 추가
public void updateRefreshToken(String newRefreshToken) {
this.refreshToken = newRefreshToken;
}
public Auth(User user, String refreshToken, String accessToken, String tokenType) {
this.user = user;
this.refreshToken = refreshToken;
this.accessToken = accessToken;
this.tokenType = tokenType;
}
}
Java
복사
•
AuthRepositoy 에서 RefreshToken을 찾는 메서드와 사용자 존재 여부 확인 메서드 생성
package org.example.backendproject.Auth.repository;
import java.util.Optional;
import org.example.backendproject.Auth.entity.Auth;
import org.example.backendproject.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface AuthRepository extends JpaRepository<Auth, Long> {
Optional<Auth> findByRefreshToken(String refreshToken);
boolean existsByUser(User user);
}
Java
복사
•
AuthService, Jwt 토큰 반영하여 서비스 로직 수정
package org.example.backendproject.Auth.service;
import lombok.RequiredArgsConstructor;
import org.example.backendproject.Auth.dto.LoginRequestDTO;
import org.example.backendproject.Auth.dto.LoginResponseDTO;
import org.example.backendproject.Auth.dto.SignUpRequestDTO;
import org.example.backendproject.Auth.entity.Auth;
import org.example.backendproject.Auth.repository.AuthRepository;
import org.example.backendproject.security.core.CustomUserDetails;
import org.example.backendproject.security.core.Role;
import org.example.backendproject.security.jwt.JwtTokenProvider;
import org.example.backendproject.user.entity.User;
import org.example.backendproject.user.entity.UserProfile;
import org.example.backendproject.user.repository.UserRepository;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Value;
@RequiredArgsConstructor
@Service
public class AuthService {
private final AuthRepository authRepository;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider jwtTokenProvider;
@Value("${jwt.accessTokenExpirationTime}")
private Long jwtAccessTokenExpirationTime;
@Value("${jwt.refreshTokenExpirationTime}")
private Long jwtRefreshTokenExpirationTime;
// 회원가입
@Transactional // 해당 어노테이션 선언해야 저장이 된다.
public void signUp(SignUpRequestDTO dto) {
// 사용자 조회 여부 확인, null값 체크
if (userRepository.findByUserid(dto.getUserid()).isPresent()) {
throw new RuntimeException("사용자가 이미 존재합니다.");
}
User user = new User();
user.setUserid(dto.getUserid());
user.setPassword(passwordEncoder.encode(dto.getPassword())); // 비밀번호 암호화하여 저장
user.setRole(Role.ROLE_USER); // 일반 사용자로 회원가입
UserProfile profile = new UserProfile();
profile.setUsername(dto.getUsername());
profile.setEmail(dto.getEmail());
profile.setPhone(dto.getPhone());
profile.setAddress(dto.getAddress());
/** 연관관계 설정 **/
profile.setUser(user);
user.setUserProfile(profile);
userRepository.save(user);
}
// 로그인
public LoginResponseDTO login(LoginRequestDTO loginRequestDTO) {
User user = userRepository.findByUserid(loginRequestDTO.getUserid())
.orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다."));
// 입력한 비밀번호가 암호화된 비밀번호와 일치하는지 확인
if (!passwordEncoder.matches(loginRequestDTO.getPassword(), user.getPassword())) {
throw new BadCredentialsException("비밀번호가 일치하지 않습니다.");
// 시큐리티 로그인 과정에서 비밀번호가 일치하지 않으면 던져주는 예외
}
// 위 비밀번호가 일치하면 기존 토큰 정보를 비교하고 토큰이 있으면 업데이트, 없으면 새로 발급
String accessToken = jwtTokenProvider.generateToken(
new UsernamePasswordAuthenticationToken(new CustomUserDetails(user),
user.getPassword()), jwtAccessTokenExpirationTime);
// 리프레시 토큰
String refreshToken = jwtTokenProvider.generateToken(
new UsernamePasswordAuthenticationToken(new CustomUserDetails(user),
user.getPassword()), jwtRefreshTokenExpirationTime);
// 현재 로그인 한 사람이 DB에 있는지 확인하고 있으면 토큰을 DB에 저장하고 로그인 처리
if (authRepository.existsByUser(user)) {
Auth auth = user.getAuth();
auth.setRefreshToken(refreshToken);
auth.setAccessToken(accessToken);
authRepository.save(auth);
return new LoginResponseDTO(auth);
}
// 위에서 DB에 사용자 정보가 없으면 아래 새로 생성해서 로그인 처리
Auth auth = new Auth(user, refreshToken, accessToken, "Bearer");
authRepository.save(auth);
return new LoginResponseDTO(auth);
}
// 리프레시 토큰을 받아서 새로운 액세스 토큰을 발급해주는 서비스
@Transactional
public String refreshToken(String refreshToken) {
// 리프레시 토큰 유효성 검사
if (jwtTokenProvider.validateToken(refreshToken)) {
// DB에서 리프레시 토큰을 가진 사용자가 있는지 확인
Auth auth = authRepository.findByRefreshToken(refreshToken).orElseThrow(
() -> new IllegalArgumentException("해당 REFRESH_TOKEN 을 찾을 수 없습니다.\nREFRESH_TOKEN: " + refreshToken));
// 있으면 인증 객체를 만들어서 새로운 토큰 발급
String newAccessToken = jwtTokenProvider.generateToken(
new UsernamePasswordAuthenticationToken(
new CustomUserDetails(auth.getUser()), auth.getUser().getPassword()), jwtAccessTokenExpirationTime); // 액세스 토큰
auth.updateAccessToken(newAccessToken); // 토큰 업데이트
authRepository.save(auth); // DB에 반영
return newAccessToken;
} else {
throw new IllegalArgumentException("토큰이 유효하지 않습니다.");
}
}
}
Java
복사
새로 회원가입 후 로그인
•
DB에 등록된 access_token, refresh_token 확인
•
accessToken payload, refreshToken payload 확인
•
jwt.io 접속해서 확인
내 정보 수정
•
UserController 수정
/** 내 정보 보기 **/
@GetMapping("/me/")
public ResponseEntity<UserDTO> getMyInfo(@AuthenticationPrincipal CustomUserDetails userDetails){
Long id = userDetails.getId();
return ResponseEntity.ok(userService.getMyInfo(id));
}
// @AuthenticationPrincipal - 스프링 시큐리티에서 인증한 사용자 정보를 자동으로 주입받는 어노테이션
// 요청 헤더 안에 있는 JWT 토큰에서 사용자 정보를 읽어옴
/** 유저 정보 수정 **/
@PutMapping("/me")
public ResponseEntity<UserDTO> updateUser(@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody UserDTO dto) {
Long id = userDetails.getId();
UserDTO updated = userService.updateUser(id, dto);
return ResponseEntity.ok(updated);
}
Java
복사
•
내 정보 수정 후 DB쪽에서 결과 확인 (기존 username: jonathan → kkk 로 변경)
•
수정된 후 내 정보 창에서 확인
게시판 테스트
게시판 CRUD 체크 + 댓글, 대댓글 체크
•
게시판 클릭~
•
게시판 접속 확인 후 글쓰기 클릭~
•
내가 쓴 글 클릭
•
수정 클릭
•
수정 내용 확인
•
댓글 + 대댓글 내용 확인
•
삭제 버튼 클릭
•
게시판에서 실종된 모습 확인
오늘 푸시한 커밋 리스트
날짜 | 커밋 메시지 |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 | |
2025-06-25 |