이번 시간에는 지난 시간에 이어서 spring boot 프로젝트를 만들겠습니다.
1. spring boot 프로젝트 생성
2. JWT 의존 모듈 설정
JWT 의존 모듈을 추가합니다.
dependencies {
// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.4'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.4'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
3. 애플리케이션 환경 변수 설정(application.properties)
mysql, mybatis, jwt 관련 환경 변수를 선언합니다.
spring.application.name=jwtex
# Tomcat
server.port=8090
# Dev Tools
spring.devtools.restart.enabled=true
# Thymeleaf
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.check-template-location=true
# MySQL
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/DB_JWT
spring.datasource.username=root
spring.datasource.password=1234
#DB(MySQL) Mapper XML Locations
mybatis.config-location=classpath:mybatis/config/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mappers/*.xml
# JWT
spring.jwt.secret=slkvfairsflsvaisfakvmccadpeivnzmxcveguisfasvnadsnfasjdnfas
4. Spring Security Config 파일 작성
package com.office.jwtex.config;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean PasswordEncoder passwordEncoder() {
log.info("passwordEncoder()");
return new BCryptPasswordEncoder();
}
@Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.disable())
.csrf(csrf -> csrf.disable());
http
.formLogin(login -> login.disable());
return http.build();
}
}
5. CORS Config 파일 작성
package com.office.jwtex.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 모든 경로에 대해
.allowedOrigins("http://localhost:3000", "http://localhost:3001") // 허용할 Origin
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 허용할 HTTP 메서드
.allowedHeaders("*") // 허용할 헤더
.allowCredentials(true); // 자격 증명 허용 (쿠키 포함)
log.info("CORS configuration has been applied: Allowed Origins - http://localhost:3000, http://localhost:3001");
}
}
5. jwt 패키지
package com.office.jwtex.jwt;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Service;
import com.office.jwtex.jwt.token.RefreshTokenDto;
import com.office.jwtex.jwt.token.RefreshTokenService;
import com.office.jwtex.jwt.token.TokenConstant;
import com.office.jwtex.jwt.token.TokenGenerateAndSaveResult;
import com.office.jwtex.member.MemberDto;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class JwtService {
private final JwtUtil jwtUtil;
private final RefreshTokenService refreshTokenService;
public JwtService(JwtUtil jwtUtil, RefreshTokenService refreshTokenService) {
this.jwtUtil = jwtUtil;
this.refreshTokenService = refreshTokenService;
}
public TokenGenerateAndSaveResult generateTokenAndSave(MemberDto memberDto) {
log.info("generateTokenAndSave()");
TokenGenerateAndSaveResult tokenGenerateAndSaveResult = new TokenGenerateAndSaveResult();
try {
String accessToken = jwtUtil.generateAccessToken("accessToken", memberDto.getId(), "USER");
String refreshToken = jwtUtil.generateRefreshToken("refreshToken", memberDto.getId(), "USER");
// RefreshToken을 쿠키에 저장
ResponseCookie responseCookie = ResponseCookie.from("refreshToken", refreshToken)
.httpOnly(true)
.secure(false) // HTTPS 연결에서만 전송되도록 설정
.path("/") // 모든 경로에서 쿠키를 사용
.sameSite("Strict") // CSRF 보호를 위해 SameSite 속성 설정
.maxAge(Duration.ofDays(7)) // 쿠키의 만료 시간
.build();
// 리프레시 토큰을 DB에 저장
refreshTokenService.saveRefreshToken(memberDto.getNo(), refreshToken);
// tokenGenerateAndSaveResult.setSuccess(true);
// tokenGenerateAndSaveResult.setMessage("Tokens successfully generated and saved.");
// tokenGenerateAndSaveResult.setAccessToken(accessToken);
// tokenGenerateAndSaveResult.setRefreshToken(refreshToken);
// tokenGenerateAndSaveResult.setResponseCookie(responseCookie);
tokenGenerateAndSaveResult = TokenGenerateAndSaveResult.builder()
.success(true)
.message(TokenConstant.TOKENS_GENERATE_SAVE_SUCCESS)
.accessToken(accessToken)
.refreshToken(refreshToken)
.responseCookie(responseCookie)
.build();
} catch (Exception e) {
e.printStackTrace();
// tokenGenerateAndSaveResult.setSuccess(false);
// tokenGenerateAndSaveResult.setMessage("Failed to generate or save tokens: " + e.getMessage());
tokenGenerateAndSaveResult = TokenGenerateAndSaveResult.builder()
.success(false)
.message(TokenConstant.TOKENS_GENERATE_SAVE_SUCCESS)
.build();
}
return tokenGenerateAndSaveResult;
}
public boolean verify(HttpServletRequest request, String authorizationHeader) {
log.info("verify()");
String accesstoken = authorizationHeader.substring(7); // "Bearer " 이후의 토큰 추출
String oldRefreshToken = getRefreshToken(request);
// 토큰 검증
if (!jwtUtil.validateToken(accesstoken)) { // AccessToken에 문제가 있는 경우
log.warn("유효하지 않은 토큰입니다.");
RefreshTokenDto refreshTokenDto = refreshTokenService.getRefreshToken(oldRefreshToken);
if (refreshTokenDto != null) { // DB에 RefreshToken이 있는 경우
String regDate = refreshTokenDto.getReg_date();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime regDateTime = LocalDateTime.parse(regDate, formatter); // regDate 문자열을 LocalDateTime으로 변환
LocalDateTime now = LocalDateTime.now(); // 현재 시간
Duration duration = Duration.between(regDateTime, now); // 두 시간 간의 차이를 Duration으로 계산
// 30분 이상 경과한 경우
if (duration.toMinutes() >= 1) {
log.info("30분이 지났습니다.");
return false;
} else {
log.info("30분이 지나지 않았습니다.");
return true;
}
}
return false;
}
return true;
}
public String getRefreshToken(HttpServletRequest request) {
log.info("getRefreshToken()");
// 요청의 쿠키 배열 가져오기
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("refreshToken".equals(cookie.getName())) {
// refreshToken 값 반환
return cookie.getValue();
}
}
}
// 쿠키가 없거나 refreshToken이 없을 경우
return null;
}
public void deleteRefreshToken(HttpServletRequest request, HttpServletResponse response) {
log.info("deleteRefreshToken()");
refreshTokenService.deleteRefreshToken(getRefreshToken(request));
// ResponseCookie를 사용하여 쿠키 생성
ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", null)
.httpOnly(true) // HttpOnly 속성
.secure(false)
.path("/") // 쿠키 경로 설정
.sameSite("Strict") // CSRF 보호를 위해 SameSite 속성 설정
.maxAge(0) // 즉시 만료
.build();
// 응답 헤더에 쿠키 추가
response.addHeader(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString());
}
}
package com.office.jwtex.jwt;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
public class JwtUtil {
// 비밀 키 (Base64로 인코딩된 문자열로 처리)
final private SecretKey SECRET_KEY;
private final long ACCESS_TOKEN_EXPIRATION = 1000 * 10 * 1; // 1분
private final long REFRESH_TOKEN_EXPIRATION = 1000 * 60 * 60 * 24 * 7; // 7일
public JwtUtil(@Value("${spring.jwt.secret}") String secret) {
this.SECRET_KEY = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
}
// 액세스 토큰 생성
public String generateAccessToken(String category, String username, String role) {
log.info("generateAccessToken()");
return Jwts.builder()
.claim("category", category)
.claim("username", username)
.claim("role", role)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION))
.signWith(SECRET_KEY)
.compact();
}
// 리프레시 토큰 생성
public String generateRefreshToken(String category, String username, String role) {
log.info("generateRefreshToken()");
return Jwts.builder()
.claim("category", category)
.claim("username", username)
.claim("role", role)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + REFRESH_TOKEN_EXPIRATION))
.signWith(SECRET_KEY)
.compact();
}
// 토큰에서 사용자 CATEGORY 추출
public String getCategory(String token) {
log.info("getCategory()");
if (validateToken(token))
return Jwts.parser().verifyWith(SECRET_KEY).build().parseSignedClaims(token).getPayload().get("category", String.class);
return null;
}
// 토큰에서 사용자 ID 추출
public String getUsername(String token) {
log.info("getUsername()");
if (validateToken(token))
return Jwts.parser().verifyWith(SECRET_KEY).build().parseSignedClaims(token).getPayload().get("username", String.class);
return null;
}
// 토큰에서 사용자 ROLE 추출
public String getRole(String token) {
log.info("getRole()");
if (validateToken(token))
return Jwts.parser().verifyWith(SECRET_KEY).build().parseSignedClaims(token).getPayload().get("role", String.class);
return null;
}
// 토큰에서 토큰 발행일 추출
public Date getIssuedAt(String token) {
log.info("getIssuedAt()");
if (validateToken(token))
return Jwts.parser().verifyWith(SECRET_KEY).build().parseSignedClaims(token).getPayload().getIssuedAt();
return null;
}
// 토큰에서 토큰 유효시간 추출
public Date getExpiration(String token) {
log.info("getExpiration()");
if (validateToken(token))
return Jwts.parser().verifyWith(SECRET_KEY).build().parseSignedClaims(token).getPayload().getExpiration();
return null;
}
// 유휴시간 검증
public boolean isExpired(String token) {
log.info("isExpired()");
return Jwts.parser().verifyWith(SECRET_KEY).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date());
}
// 토큰 유효성 검증
public boolean validateToken(String token) {
log.info("validateToken()");
try {
// Jwts.parser()를 사용하여 JWT 파싱 및 서명 검증
Jwts.parser()
.verifyWith(SECRET_KEY)
.build()
.parseSignedClaims(token);
return true; // 서명이 유효하면 true 반환
} catch (ExpiredJwtException e) {
log.info("토큰이 만료되었습니다: {}", e.getMessage());
} catch (MalformedJwtException e) {
log.info("토큰 형식이 잘못되었습니다: {}", e.getMessage());
} catch (IllegalArgumentException e) {
log.info("토큰이 null이거나 비어 있습니다: {}", e.getMessage());
} catch (Exception e) {
log.info("유효하지 않은 토큰: {}", e.getMessage());
}
return false; // 예외가 발생하면 false 반환
}
}
6. token 패키지
RefreshTokenDto
package com.office.jwtex.jwt.token;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RefreshTokenDto {
private int no;
private int owner_no;
private String refresh_token;
private String reg_date;
private String mod_date;
}
RefreshTokenMapper
package com.office.jwtex.jwt.token;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RefreshTokenMapper {
int insertNewRefreshToken(RefreshTokenDto refreshTokenDto);
RefreshTokenDto selectRefreshToken(String refreshToken);
int deleteRefreshToken(String oldRefreshToken);
}
RefreshTokenService
package com.office.jwtex.jwt.token;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class RefreshTokenService {
final private RefreshTokenMapper refreshTokenMapper;
public RefreshTokenService(RefreshTokenMapper refreshTokenMapper) {
this.refreshTokenMapper = refreshTokenMapper;
}
public int saveRefreshToken(int no, String refreshToken) {
log.info("insertNewRefreshToken()");
return refreshTokenMapper.insertNewRefreshToken(RefreshTokenDto.builder()
.owner_no(no)
.refresh_token(refreshToken)
.build());
}
public RefreshTokenDto getRefreshToken(String refreshToken) {
log.info("getRefreshToken()");
return refreshTokenMapper.selectRefreshToken(refreshToken);
}
public int deleteRefreshToken(String oldRefreshToken) {
log.info("deleteRefreshToken()");
return refreshTokenMapper.deleteRefreshToken(oldRefreshToken);
}
}
TokenConstant
package com.office.jwtex.jwt.token;
public class TokenConstant {
public static final String TOKENS_GENERATE_SAVE_SUCCESS = "TOKENS GENERATED AND SAVED SUCCESS!!.";
public static final String TOKENS_GENERATE_SAVE_FAIl = "TOKENS GENERATED AND SAVED FAIL!!";
}
TokenGenerateAndSaveResult
package com.office.jwtex.jwt.token;
import org.springframework.http.ResponseCookie;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TokenGenerateAndSaveResult {
private boolean success;
private String message; // 에러 메시지 또는 성공 메시지
private String accessToken;
private String refreshToken;
private ResponseCookie responseCookie;
}
TokenResponse
package com.office.jwtex.jwt.token;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TokenResponse {
private String accessToken;
private String refreshToken;
}
8. mybatis config & mapper 파일 작성
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
</configuration>
member-mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTO Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.office.jwtex.member.MemberMapper">
<insert id="insertNewMember"
parameterType="com.office.jwtex.member.MemberDto">
INSERT INTO MEMBER(ID, PW, MAIL)
VALUES(#{id}, #{pw}, #{mail})
</insert>
<select id="selectMemberById"
parameterType="String"
resultType="com.office.jwtex.member.MemberDto">
SELECT
*
FROM
MEMBER
WHERE
ID = #{id}
</select>
<update id="updateMember"
parameterType="com.office.jwtex.member.MemberDto">
UPDATE
MEMBER
SET
PW = #{pw},
MAIL = #{mail},
MOD_DATE = NOW()
WHERE
ID = #{id}
</update>
</mapper>
refreshtoken-mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTO Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.office.jwtex.jwt.token.RefreshTokenMapper">
<insert
id="insertNewRefreshToken"
parameterType="com.office.jwtex.jwt.token.RefreshTokenDto">
INSERT INTO REFRESH_TOKEN(OWNER_NO, REFRESH_TOKEN)
VALUES(#{owner_no}, #{refresh_token})
</insert>
<select id="selectRefreshToken"
parameterType="com.office.jwtex.jwt.token.RefreshTokenDto">
SELECT
*
FROM
REFRESH_TOKEN
WHERE
REFRESH_TOKEN = #{refreshToken}
</select>
<delete id="deleteRefreshToken"
parameterType="String">
DELETE FROM
REFRESH_TOKEN
WHERE
REFRESH_TOKEN = #{oldRefreshToken}
</delete>
</mapper>
9. member 패키지
MemberConstant.java
package com.office.jwtex.member;
public class MemberConstant {
public static final String SIGNUP_SUCCESS = "SIGNUP SUCCESS!!";
public static final String SIGNUP_FAIL = "SIGNUP FAIL!!";
public static final String SIGNIN_SUCCESS = "SIGNIN SUCCESS!!";
public static final String SIGNIN_FAIL = "SIGNIN FAIL!!";
public static final String CREDENTIALS_INVALID = "THE CREDENTIALS ARE INVALID."; // 인증 정보가 유효하지 않습니다.
public static final String INVALID_TOKEN = "INVALID TOKEN."; // 유효하지 않은 토큰입니다.
public static final String GET_USER_INFO_SUCCESS = "GET USER INFO SUCCESS!!"; // 회원 정보 조회 성공
public static final String GET_USER_INFO_FAIL = "GET USER INFO FAIL!!"; // 회원 정보 조회 성공
public static final String MODIFY_SUCCESS = "MODIFY SUCCESS!!";
public static final String MODIFY_FAIL = "MODIFY FAIL!!";
public static final String SIGNOUT_SUCCESS = "SIGNOUT SUCCESS!!";
public static final String SIGNOUT_FAIL = "SIGNOUT FAIL!!";
}
MemberController.java
package com.office.jwtex.member;
import java.util.HashMap;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.office.jwtex.jwt.JwtService;
import com.office.jwtex.jwt.token.TokenGenerateAndSaveResult;
import com.office.jwtex.jwt.token.TokenResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequestMapping("/member")
public class MemberController {
private final MemberService memberService;
private final JwtService jwtService;
public MemberController(MemberService memberService, JwtService jwtService) {
this.memberService = memberService;
this.jwtService = jwtService;
}
@PostMapping("/signup")
public ResponseEntity<?> signup(@RequestBody MemberDto memberDto) {
log.info("signup()");
int signupResult = memberService.signup(memberDto);
if (signupResult > 0)
return ResponseEntity.ok(
// new SignUpResponse(true, MemberConstant.SIGNUP_SUCCESS, memberDto.getId())
SignUpResponse.builder()
.isSuccess(true)
.message(MemberConstant.SIGNUP_SUCCESS)
.userId(memberDto.getId())
.build());
return ResponseEntity.ok(
// new SignUpResponse(false, MemberConstant.SIGNUP_FAIL, null)
SignUpResponse.builder()
.message(MemberConstant.SIGNUP_FAIL)
.build()
);
}
@PostMapping("/signin")
public ResponseEntity<?> signin(@RequestBody MemberDto memberDto) {
log.info("signin()");
MemberDto signinedMemberDto = memberService.signin(memberDto);
if (signinedMemberDto != null) {
TokenGenerateAndSaveResult tokenGenerateAndSaveResult = jwtService.generateTokenAndSave(signinedMemberDto);
// 쿠키는 헤더에, AccessToken은 바디에 담아서 클라이언트에게 응답
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, tokenGenerateAndSaveResult.getResponseCookie().toString()) // 쿠키를 헤더에 설정
.body(new HashMap<>() {{
// put("signInResponse", new SignInResponse(true, MemberConstant.SIGNIN_SUCCESS, signinedMemberDto.getId()));
// put("tokenResponse", new TokenResponse(MemberConstant.SIGNIN_SUCCESS, tokenGenerateAndSaveResult.getAccessToken(), tokenGenerateAndSaveResult.getRefreshToken()));
put("signInResponse", SignInResponse.builder()
.isSuccess(true)
.message(MemberConstant.SIGNIN_SUCCESS)
.userId(signinedMemberDto.getId()).build());
put("tokenResponse", TokenResponse.builder()
.accessToken(tokenGenerateAndSaveResult.getAccessToken())
.refreshToken(tokenGenerateAndSaveResult.getRefreshToken()).build());
}});
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(new HashMap<>() {{
// put("signInResponse", new SignInResponse(false, MemberConstant.SIGNIN_FAIL, null));
put("signInResponse", SignInResponse.builder()
.message(MemberConstant.SIGNIN_FAIL)
.build());
}});
}
@GetMapping("/getuserinfo")
public ResponseEntity<?> getUserInfo (
@RequestParam(value = "id") String id,
HttpServletRequest request,
HttpServletResponse response,
@RequestHeader(value = "Authorization", required = false) String authorizationHeader) {
log.info("getUserInfo()");
// Authorization 헤더 검증
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
log.warn("Authorization header is missing or incorrect.");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
// .body(new UserInfoResponse(MemberConstant.CREDENTIALS_INVALID));
.body(UserInfoResponse.builder()
.message(MemberConstant.CREDENTIALS_INVALID)
.build());
}
// JWT 검증
if (!jwtService.verify(request, authorizationHeader)) {
log.warn("The token is invalid.");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
// .body(new UserInfoResponse(MemberConstant.INVALID_TOKEN));
.body(UserInfoResponse.builder()
.message(MemberConstant.INVALID_TOKEN)
.build());
}
// 사용자 정보 조회
MemberDto memberDto = memberService.getUserInfo(id);
if (memberDto != null) {
jwtService.deleteRefreshToken(request, response);
TokenGenerateAndSaveResult tokenGenerateAndSaveResult = jwtService.generateTokenAndSave(memberDto);
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, tokenGenerateAndSaveResult.getResponseCookie().toString())
.body(new HashMap<String, Object>() {{
put("member", memberDto);
put("userInfoResponse", UserInfoResponse.builder()
.accessToken(tokenGenerateAndSaveResult.getAccessToken())
.message(MemberConstant.GET_USER_INFO_SUCCESS)
.build());
}});
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(UserInfoResponse.builder()
.message(MemberConstant.GET_USER_INFO_FAIL)
.build());
}
@PostMapping("/modify")
public ResponseEntity<?> modify(
@RequestBody MemberDto memberDto,
HttpServletRequest request,
HttpServletResponse response,
@RequestHeader(value = "Authorization", required = false) String authorizationHeader) {
log.info("modify()");
// Authorization 헤더 검증
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
log.warn("Authorization header is missing or incorrect.");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
// .body(new ModifyResponse(MemberConstant.CREDENTIALS_INVALID));
.body(ModifyResponse.builder()
.message(MemberConstant.CREDENTIALS_INVALID)
.build());
}
// JWT 검증
if (!jwtService.verify(request, authorizationHeader)) {
log.warn("The token is invalid.");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
// .body(new ModifyResponse(MemberConstant.INVALID_TOKEN));
.body(ModifyResponse.builder()
.message(MemberConstant.INVALID_TOKEN)
.build());
}
int modifyResult = memberService.modify(memberDto);
if (modifyResult > 0) {
jwtService.deleteRefreshToken(request, response);
TokenGenerateAndSaveResult tokenGenerateAndSaveResult = jwtService.generateTokenAndSave(memberDto);
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, tokenGenerateAndSaveResult.getResponseCookie().toString())
.body(new HashMap<String, Object>() {{
put("member", memberDto);
put("modifyResponse", ModifyResponse.builder()
.accessToken(tokenGenerateAndSaveResult.getAccessToken())
.message(MemberConstant.MODIFY_SUCCESS)
.build());
}});
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ModifyResponse.builder()
.message(MemberConstant.MODIFY_FAIL)
.build());
}
@PostMapping("/signout")
public ResponseEntity<?> signout(HttpServletRequest request, HttpServletResponse response) {
log.info("signout()");
jwtService.deleteRefreshToken(request, response);
return ResponseEntity.ok(SignoutResponse.builder()
.message(MemberConstant.SIGNOUT_SUCCESS)
.build());
}
}
MemberDto.java
package com.office.jwtex.member;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MemberDto {
private int no;
private String id;
private String pw;
private String mail;
private String reg_date;
private String mod_date;
}
MemberMapper.java
package com.office.jwtex.member;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MemberMapper {
int insertNewMember(MemberDto memberDto);
MemberDto selectMemberById(String id);
MemberDto getUserInfo(String id);
int updateMember(MemberDto memberDto);
}
MemberService.java
package com.office.jwtex.member;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class MemberService {
final private MemberMapper memberMapper;
final private PasswordEncoder passwordEncoder;
public MemberService(MemberMapper memberMapper, PasswordEncoder passwordEncoder) {
this.memberMapper = memberMapper;
this.passwordEncoder = passwordEncoder;
}
public int signup(MemberDto memberDto) {
log.info("signup()");
memberDto.setPw(passwordEncoder.encode(memberDto.getPw()));
return memberMapper.insertNewMember(memberDto);
}
public MemberDto signin(MemberDto memberDto) {
log.info("signin()");
MemberDto selectMemberDto = memberMapper.selectMemberById(memberDto.getId());
if (selectMemberDto == null || !passwordEncoder.matches(memberDto.getPw(), selectMemberDto.getPw()))
return null;
return selectMemberDto;
}
public MemberDto getUserInfo(String id) {
log.info("findMemberById()");
return memberMapper.selectMemberById(id);
}
public int modify(MemberDto memberDto) {
log.info("modify()");
memberDto.setPw(passwordEncoder.encode(memberDto.getPw()));
return memberMapper.updateMember(memberDto);
}
}
10. member response 패키지
ModifyResponse
package com.office.jwtex.member.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ModifyResponse {
private String message;
private String accessToken;
}
SignInResponse
package com.office.jwtex.member.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SignInResponse {
private boolean isSuccess;
private String message;
private String userId;
}
SignoutResponse
package com.office.jwtex.member.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SignoutResponse {
private String message;
}
SignUpResponse
package com.office.jwtex.member.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SignUpResponse {
private boolean isSuccess;
private String message;
private String userId;
}
UserInfoResponse
package com.office.jwtex.member.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserInfoResponse {
private String message;
private String accessToken;
}
spring 소스 첨부합니다.
'spring boot' 카테고리의 다른 글
react에서 구글로그인 구현-II(feat. @react-oauth/google) (0) | 2024.12.30 |
---|---|
JWT를 이용한 인증-III(feat. react, spring boot, spring security) (0) | 2024.12.28 |
JWT를 이용한 인증-I(feat. react) (0) | 2024.12.25 |
Log4j2를 이용한 로깅(logging) 방법 - V (0) | 2024.12.12 |
Log4j2를 이용한 로깅(logging) 방법 - IV (2) | 2024.12.12 |