์์ฃผ ๊ธฐ๋ณธ์ ์ธ ์ฝ๋๊น์ง ๋ชจ๋ ์์ต๋๋ค.. ์๋๋ฉด... ์ ๋ ๊ธฐ์ ๋ธ๋ก๊ทธ๋ค์ ๋ณด๋ฉด์ ๋น ์ ธ์๋ ๋ถ๋ถ์ ๋ณด๋ฉด ๋๊ฐ์ ์ด๋ณด๋ ์ด์ฉ๋ผ๊ณ ํ๋ฉฐ ์ฌํผํ๊ธฐ ๋๋ฌธ์,,,
์ฌ์ค Security์ ์ฌ๋ฌ๊ฐ์ง Filter๋ฅผ ์ฌ์ฉํ์ฌ JWT ๋ฐฉ์์ ์ด์ฉํ๋๊ฒ ๋ง์ง๋ง
ํ๋ก์ ํธ ํฌ๊ธฐ,, ๊ธฐ๊ฐ์ ๋ถํ์ํ๋ค ํ๋จํ๊ฒ ๋์ด์ JWT ๋ง์ ์ฌ์ฉํ์ฌ ๊ตฌํํ์๋ค. ๋์ค์ Security๋ ๊ฐ์ด ๊ตฌํํ์ฌ ์ฌ๋ฆฌ๋๋ก ํ๊ฒ ์!
์ด๋ฒ์๋ git์ ์ฌ๋ ค ์ฝ๋๊น์ง ๊ณต์ ํ๊ธฐ๋ก ํ๋ค! ์ฒ์๋ณด๋ฉด ์ฝ๊ฐ ๋ณต์กํ๋ค๊ณ ์๊ฐ๋๊ณ ๋๋ํ ๊ทธ๋ฌ๋ค ใ ใ ,, (๋ถ๋๋ฌ์ด) ๊ตฌํ์ฝ๋๋ฅผ ๋ณด๋ฉด์ ์ดํดํ๊ณ ํน์ ์ด์ํ๊ณณ์ PRํด์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค...(๊ฐ์ ,,)
git ์ฃผ์ (์ค๋ฌด์์ ์ค์ ๋ก ์ฌ์ฉํ๋ฉด์ ์์ ํ๊ณ ์์ต๋๋ค ~!)
git clone https://github.com/aejeong-context/tokenTest
API ๋ฐฉ์ ์ด๊ธฐ ๋๋ฌธ์ Server๋ง ๊ตฌํํ์ต๋๋ค!
์ ๊ฐ ๊ตฌํ ํ ๋ฐฉ์์ ์ด๋ ์ต๋๋ค!
1. ์ ์ ๊ฐ ํ์๊ฐ์ ์ ํ ๋ ํด๋น ์ ์ ์ ์์ด๋๋ก Token์ ์์ฑํด์ AccessToken, RefreshToken์ ์์ฑํ๋ค.
2. ์ด ๋ ์์ฑํ RefreshToken์ DB์ ์ ์ฅํ๋ค.
3. ์ฌ์ฉ์๊ฐ AccessToken์ ์ด์ฉํ์ฌ ๋ก๊ทธ์ธ์ ํ๊ณ ๊ทธ token์ผ๋ก ์ฌ๋ฌ ์์ฒญ์ ๋ํ ๋ฆฌ์์ค๋ฅผ ์ ๊ณต๋ฐ๋๋ค.
4. AccessToken์ 30๋ถ์ผ๋ก ์ ํ๋ค. 30๋ถ ํ์ ์ด๋ ํ ์์ฒญ์ ํ ๊ฒฝ์ฐ ๋ค์ ๋ก๊ทธ์ธ์ ํด์ RefreshToken์ Token๊ณผ ์ ํจ์๊ฐ๋ ํ์ธํ๊ณ ์๊ฐ์ด ์๋ฌ๋ค๋ฉด ์ฌ๋ฐ๊ธ ํด์ฃผ๊ณ AccessToken๋ ์ฌ๋ฐ๊ธ ๋ฐ๋๋ค. (์ด๋ ํ๋ก ํธ์์ ๋ก๊ทธ์ธ์์ฒญ์ ๋ค์ ํ๊ฒ ์ฃ ?)
-> ์ฌ๊ธฐ์ ๊ณ ๋ฏผ์ธ๊ฒ Interceptor์์ ์์ฒญ๋ง๋ค ํ์ธ์ ํด์ ๋ก๊ทธ์ธ์ ์์ํค๋๋ก ํด์ผํ ์ง, ์ฌ์ฉ์ฑ์ ์ํด ๋ค์ ๋ก๊ทธ์ธ ์ํค๋๊ฒ ๋ง๋์ง ๊ณ ๋ฏผ์ด๋ค....
5.์ฌ๋ฐ๊ธ ๋ฐ์ AccessToken์ผ๋ก ๋ฆฌ์์ค๋ฅผ ์ป๋๋ค.
์ด๋ ๊ฒ ๋๋ฉด session๊ด๋ฆฌ๋ฅผ ํด์ค ํ์ ์์ด ์์ฒญ๋ง๋ค Token์ ํ์ธ ํด์ฃผ๋ฉด ๋๋ค!
0. ํจํค์ง ๊ตฌ์ฑ
1. build.gradle ์์กด์ฑ ์ถ๊ฐํ๊ธฐ
implementation 'io.jsonwebtoken:jjwt:0.9.1'
2. UserEntitiy.class ์ ํ๊ธฐ
Test์ฉ์ด๋ผ์ userId, pw๋ก ๋๋์์ง๋ง ์ด๋ถ๋ถ์ ๋ณ๊ฒฝํ์ ๋ ๋ฉ๋๋ค email์ด๋ผ๋๊ฐ...
package com.token.domains.users.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import javax.persistence.*;
@Getter
@RequiredArgsConstructor
@Table(name = "users")
@Entity
public class UsersEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;
private String userId;
private String pw;
@Builder
public UsersEntity(String userId, String pw) {
this.userId = userId;
this.pw = pw;
}
}
3. UserRepository.intrerface
Optional์ Java8๋ฌธ๋ฒ์ธ๋ฐ null์ฒ๋ฆฌ์ ์์ฃผ ์ ์ฉํ๋ ํ๋ฒ์ฉ ์ฌ์ฉํด๋ณด์๊ธธ ์ถ์ฒ๋๋ฆฝ๋๋ค!
์ฐ์ ์์ฑํ๊ณ ์ฌ์ฉํ ๋ ์ค๋ช ์ ํด๋ณด๊ฒ ์ต๋๋ค
public interface UsersRepository extends JpaRepository<UsersEntity,Long> {
Optional<UsersEntity> findByUserIdAndPw(String userId,String pw);
Optional<UsersEntity> findByUserId(String userId);
}
4.UserController.class
์ฌ์ค ์ด๋ฆ์ ์ด๋ด์์ผ๋ก ์ง์ผ๋ฉด ํผ๋ฉ๋๋ค. ์ด๊ฑด Test์ด๊ธฐ ๋๋ฌธ์ ^^...
/signUp, /signIn์ ํ์๊ฐ์ , ๋ก๊ทธ์ธ์ด๊ตฌ์ Test๋ก AccessToken์ด ์ ํจํด์ผ๋ง ๋ฆฌ์์ค๋ฅผ ๋ฐ์ ์ ์๋ /info๊ฐ ์์ต๋๋ค.
์ด ๋ชจ๋ ์์ฒญ๋ค์ Interceptor๊ฐ ๋จผ์ ์ฒ๋ฆฌ๋๊ณ ๋ค์ด๊ฐ๊ธฐ ๋๋ฌธ์ ์ฌ๊ธฐ์ ๋ฑํ ํด์ผํ๋๊ฑด ์์ต๋๋ค.
@RequiredArgsConstructor
@RestController
public class UserController {
private final UserService userService;
@PostMapping("/user/signUp")
public ResponseEntity signUp(@RequestBody UserRequest userRequest) {
return userService.findByUserId(userRequest.getUserId()).isPresent()
? ResponseEntity.badRequest().build()
: ResponseEntity.ok(userService.signUp(userRequest));
}
@PostMapping("/user/signIn")
public ResponseEntity<TokenResponse> signIn(@RequestBody UserRequest userRequest) {
return ResponseEntity.ok().body(userService.signIn(userRequest));
}
@GetMapping("/info")
public ResponseEntity<List<UsersEntity>> findUser() {
return ResponseEntity.ok().body(userService.findUsers());
}
}
5. AuthEntity.class
ํ ํฐ์ ๊ด๋ฆฌํด์ค ํ ์ด๋ธ์ ๋๋ค. N:1๋ก ๋จ๋ฐํฅ ๋งตํ ํด์ฃผ์์ต๋๋ค. ์ฌ์ค ์๋ฐํฅ์ ํด์ฃผ์ด๋ ์๊ด์์ง๋ง ์ฐ์ User๊ฐ ์๋์ง ํ์ธ ํ Token์ ํ์ธํ๋ ํ๋ก์ฐ๊ฐ ๋ง์ง ์๋ ์ถ์ด์ ๋จ๋ฐํฅ์ผ๋ก ์ค์ ํด์ฃผ์์ต๋๋ค.
- refreshUpdate ๋ฉ์๋๋ DB์ ์ ์ฅํ๊ณ , ์ฌ์ฉํ๋ refreshToken์ด ์ ํจ์๊ฐ์ด ๋ง๋ฃ๋์์ ๋ DB์ ์ ๋ฐ์ดํธ ๋๋ ๊ธฐ๋ฅ์ ๋๋ค.
@Getter
@RequiredArgsConstructor
@Table(name = "auth")
@Entity
public class AuthEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;
private String refreshToken;
@ManyToOne
@JoinColumn(name = "user_id")
private UsersEntity usersEntity;
@Builder
public AuthEntity(String refreshToken, UsersEntity usersEntity) {
this.refreshToken = refreshToken;
this.usersEntity = usersEntity;
}
public void refreshUpdate(String refreshToken) {
this.refreshToken = refreshToken;
}
}
6. AuthRepository.inrerface
public interface AuthRepository extends JpaRepository<AuthEntity, Long> {
Optional<AuthEntity> findByUsersEntityId(Long userId);
}
7. WebMvcConfig.class
TokenInterceptor๋ฅผ ๊ตฌํํด์ฃผ๊ธฐ ์ํด ์ํ๋ ๊ฒฝ๋ก ๋ฅผ ์ถ๊ฐํด์ค๋๋ค.
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
private final JwtTokenInterceptor jwtTokenInterceptor;
public void addInterceptors(InterceptorRegistry registry) {
System.out.println("์ธํฐ์
ํฐ ๋ฑ๋ก");
registry.addInterceptor(jwtTokenInterceptor).addPathPatterns("/info");
}
}
/info๋ง ํด์ testํ ์์ ์ด๊ธฐ ๋๋ฌธ์ ์ ๋ ๊ฒ ํด๋จ์ง๋ง.
๋ณดํต ํ์๊ฐ์ , ๋ฉ์ธ ํ์ด์ง ์กฐํ ๋ฑ ์ ํ์์ด ์๋์ด๋ ๋ณผ ์ ์๊ธฐ ๋๋ฌธ์ ์ค์ ๋ก ๊ตฌํํ์ค ๋๋ ์๋์ ๊ฐ์ด ์ค์ ํด์ฃผ์๋ฉด ๋ฉ๋๋ค.
registry.addInterceptor(jwtTokenInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/signUp")
8. JwtTokenInterceptor.class
ํ..log๋ฅผ ์ฌ์ฉํด์ผํ๋๋ฐ ์์ง๋ syso๊ฐ ์ต์ํ ๋ฐ๋์ System ๋ฒ๋ฒ ์ ๋๋ค,, ์ดํด๋ถํ,,
์๊น /info๋ฅผ ์ถ๊ฐ ํด์คฌ์ผ๋ API๋ก /info์ ๋ํ ๋ฆฌ์์ค๋ฅผ ์์ฒญํ ๋ ๋ง๋ค ์ด ํด๋์ค๊ฐ ์คํ๋ฉ๋๋ค.
request.getheader("ACCESS_TOKEN") ์ ํด๋ผ์ด์ธํธ๊ฐ ACCESS_TOKEN์ด๋ผ๋ key๊ฐ์ผ๋ก ํ์๊ฐ์ ๋ ์์ฑํ์ฌ ๋ณด๊ดํ๋ token์ ๋ณด๋ด์ฃผ๋ฉด ๊ทธ value๊ฐ์ ๊ฐ์ ธ์์ null์ธ์ง ํ์ธํฉ๋๋ค. null์ด ์๋๊ฒฝ์ฐ isValidToken์์ ํด๋น token์ด ์๋ฒ์์ ์์ฑํ token์ธ์ง, ์ ํจ๊ธฐ๊ฐ์ด ์ง๋ฌ๋์ง ํ์ธํ ํ ๋ฐ์! ๋ฐ์์ ํ์ธํด๋ด ์๋ค!
@Component
@RequiredArgsConstructor
public class JwtTokenInterceptor implements HandlerInterceptor {
private final TokenUtils tokenUtils;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws IOException {
System.out.println("JwtToken ํธ์ถ");
String accessToken = request.getHeader("ACCESS_TOKEN");
System.out.println("AccessToken:" + accessToken);
String refreshToken = request.getHeader("REFRESH_TOKEN");
System.out.println("RefreshToken:" + refreshToken);
if (accessToken != null) {
if (tokenUtils.isValidToken(accessToken)) {
return true;
}
}
response.setStatus(401);
response.setHeader("ACCESS_TOKEN", accessToken);
response.setHeader("REFRESH_TOKEN", refreshToken);
response.setHeader("msg", "Check the tokens.");
return false;
}
9. TokenUtils.class
๋๋ง์ Token ์์ฑ ํด๋์ค์ ๋๋ค,,์ ์ฒด ์์ค์ ๋๋ค.
@RequiredArgsConstructor
@Service
public class TokenUtils {
private final String SECRET_KEY = "secretKey";
private final String REFRESH_KEY = "refreshKey";
private final String DATA_KEY = "userId";
public String generateJwtToken(UsersEntity usersEntity) {
return Jwts.builder()
.setSubject(usersEntity.getUserId())
.setHeader(createHeader())
.setClaims(createClaims(usersEntity))
.setExpiration(createExpireDate(1000 * 60 * 5))
.signWith(SignatureAlgorithm.HS256, createSigningKey(SECRET_KEY))
.compact();
}
public String saveRefreshToken(UsersEntity usersEntity) {
return Jwts.builder()
.setSubject(usersEntity.getUserId())
.setHeader(createHeader())
.setClaims(createClaims(usersEntity))
.setExpiration(createExpireDate(1000 * 60 * 10))
.signWith(SignatureAlgorithm.HS256, createSigningKey(REFRESH_KEY))
.compact();
}
public boolean isValidToken(String token) {
System.out.println("isValidToken is : " +token);
try {
Claims accessClaims = getClaimsFormToken(token);
System.out.println("Access expireTime: " + accessClaims.getExpiration());
System.out.println("Access userId: " + accessClaims.get("userId"));
return true;
} catch (ExpiredJwtException exception) {
System.out.println("Token Expired UserID : " + exception.getClaims().getSubject());
return false;
} catch (JwtException exception) {
System.out.println("Token Tampered");
return false;
} catch (NullPointerException exception) {
System.out.println("Token is null");
return false;
}
}
public boolean isValidRefreshToken(String token) {
try {
Claims accessClaims = getClaimsToken(token);
System.out.println("Access expireTime: " + accessClaims.getExpiration());
System.out.println("Access userId: " + accessClaims.get("userId"));
return true;
} catch (ExpiredJwtException exception) {
System.out.println("Token Expired UserID : " + exception.getClaims().getSubject());
return false;
} catch (JwtException exception) {
System.out.println("Token Tampered");
return false;
} catch (NullPointerException exception) {
System.out.println("Token is null");
return false;
}
}
private Date createExpireDate(long expireDate) {
long curTime = System.currentTimeMillis();
return new Date(curTime + expireDate);
}
private Map<String, Object> createHeader() {
Map<String, Object> header = new HashMap<>();
header.put("typ", "ACCESS_TOKEN");
header.put("alg", "HS256");
header.put("regDate", System.currentTimeMillis());
return header;
}
private Map<String, Object> createClaims(UsersEntity usersEntity) {
Map<String, Object> claims = new HashMap<>();
claims.put(DATA_KEY, usersEntity.getUserId());
return claims;
}
private Key createSigningKey(String key) {
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(key);
return new SecretKeySpec(apiKeySecretBytes, SignatureAlgorithm.HS256.getJcaName());
}
private Claims getClaimsFormToken(String token) {
return Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(SECRET_KEY))
.parseClaimsJws(token)
.getBody();
}
private Claims getClaimsToken(String token) {
return Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(REFRESH_KEY))
.parseClaimsJws(token)
.getBody();
}
}
private final String SECRET_KEY = "secretKey";
private final String REFRESH_KEY = "refreshKey";
private final String DATA_KEY = "userId";
์ ๋ณ์๋ token์ ๋ง๋ค ๋ ์ฌ์ฉํ ์ํธ์ ๋๋ค. DATA_KEY๋ ์ด๋ค ๋ด์ฉ์ผ๋ก token์ ๋ง๋ค์ด์ง ์ ํด์ค๋๋ค.
์ด๋ถ๋ถ์ JWT์ด๋ก ์ ์์ธํ ๊ณต๋ถํด๋ด์ผ ํ๋๋ฐ์ ๊ฐ๋จํ ์ค๋ช ํ์๋ฉด ์๋์ ๊ฐ์ด Payload์ ๋ค์ด๊ฐ ๋ด์ฉ์ด DATA_KEY์ ๋๋ค. ์ ๋ userId๋ง ์ค์ ํ ์์ ์ด๊ธฐ ๋๋ฌธ์ 6. ์ ๋ณด์๋ฉด ์์๊ฒ ์ง๋ง userEntity์์ userId๋ง์ ๊ฐ์ง๊ณ ์์ claims.put์ ํด์ค๋๋ค.
๋ฌผ๋ก ์ฌ๋ฌ๊ฐ๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. ํ์ง๋ง token์ ์์ฒญ๋ง๋ค header์ ๋ถ์ด ๋ค๋๊ธฐ ๋๋ฌธ์ ๋ง์ฝ ํ์ทจ๋นํ๋ค๋ฉด putํด์ค ๋ชจ๋ ์ ๋ณด๋ค์ด ๋ ธ์ถ๋ฉ๋๋ค,,
๊ทธ๋ฆฌ๊ณ token๊ธธ์ด๋ ๊ธธ์ด์ง๋๋ค! (์๊ด์๋..)
1.generateJwtToken
AceessToken ์์ฑ
2.saveRefreshToken
ResfeshToken ์์ฑ
3.isValidToken, isValidRefreshToken
ํด๋น Token ์ ์ ํจ์ฑ ํ์ธ
4. createExpireDate
์ ํจ์๊ฐ ์ค์
5. createHeader
Token ์์ฑ์ Header ๋ถ๋ถ์ ์ ์ ํด์ค๋ค
6. createClaims
Token ์์ฑ์ Payload ๋ถ๋ถ์ ์ ํด์ค๋์ค๋ค
7.createSigningKey
ํด๋น key๋ก ์ํธํ
8.getClaimsFormToken,getClaimsToken
์ ํจ์ฑ ๊ฒ์์ ์ํด token ์ ๋ณด๋ฅผ ์ฝ์ด์จ๋ค
Token์ ์ ํจ์ฑ ํ์ธ๊ณผ Token ์ ๋ณด๋ฅผ ์ฝ์ด์ค๋ 3,8๋ฒ์ ์ค๋ณต์ฝ๋๊ฐ ๋ฐ์ํ๋ค... ์ด๊ฑด ๋์ค์ ๋ค์ ๋ณด๊ณ ์์ ํด์ผ ํ ๊ฒ ๊ฐ๋ค ใ ใ ,,
10. UserService.class
@Service
@RequiredArgsConstructor
public class UserService {
private final UsersRepository usersRepository;
private final TokenUtils tokenUtils;
private final AuthRepository authRepository;
public Optional<UsersEntity> findByUserId(String userId) {
return usersRepository.findByUserId(userId);
}
@Transactional
public TokenResponse signUp(UserRequest userRequest) {
UsersEntity usersEntity =
usersRepository.save(
UsersEntity.builder()
.pw(userRequest.getUserPw())
.userId(userRequest.getUserId())
.build());
String accessToken = tokenUtils.generateJwtToken(usersEntity);
String refreshToken = tokenUtils.saveRefreshToken(usersEntity);
authRepository.save(
AuthEntity.builder().usersEntity(usersEntity).refreshToken(refreshToken).build());
return TokenResponse.builder().ACCESS_TOKEN(accessToken).REFRESH_TOKEN(refreshToken).build();
}
@Transactional
public TokenResponse signIn(UserRequest userRequest) {
UsersEntity usersEntity =
usersRepository
.findByUserIdAndPw(userRequest.getUserId(), userRequest.getUserPw())
.orElseThrow(() -> new IllegalArgumentException("์กด์ฌํ์ง ์๋ ํ์์
๋๋ค."));
AuthEntity authEntity =
authRepository
.findByUsersEntityId(usersEntity.getId())
.orElseThrow(() -> new IllegalArgumentException("Token ์ด ์กด์ฌํ์ง ์์ต๋๋ค."));
String accessToken = "";
String refreshToken= authEntity.getRefreshToken();
if (tokenUtils.isValidRefreshToken(refreshToken)) {
accessToken = tokenUtils.generateJwtToken(authEntity.getUsersEntity());
return TokenResponse.builder()
.ACCESS_TOKEN(accessToken)
.REFRESH_TOKEN(authEntity.getRefreshToken())
.build();
} else {
refreshToken = tokenUtils.saveRefreshToken(usersEntity);
authEntity.refreshUpdate(refreshToken);
}
return TokenResponse.builder().ACCESS_TOKEN(accessToken).REFRESH_TOKEN(refreshToken).build();
}
public List<UsersEntity> findUsers() {
return usersRepository.findAll();
}
}
Service๋ ์ฒ์์ ๋งํ๋ ํ๋ก์ฐ์ ๊ฐ๋ค! ํ์๊ฐ์ ์ ACCESS_TOKEN๊ณผ REFRESH_TOKEN์ ๋ฐ๊ธํ๊ณ
๋ฆฌ์์ค ์์ฒญ์ ํ๋ค๊ฐ ์ฐ๊ฒฐ์ด ๋์ด์ง ๊ฒฝ์ฐ ๋ก๊ทธ์ธ์ ๋ค์ ์ํค๋๋ฐ ์ด๋ refreshToken์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํด์ค ๋ค์์ ํต๊ณผ๋๋ฉด AccessToken์ ์ฌ๋ฐ๊ธํด์ค๋ค! ๋ง์ฝ refreshToken์ด ์ ํจํ์ง ์๋ค๋ฉด refreshToken๋ ์ ๋ฐ์ดํธ ํด์ค๋๋ค!
์์งํ ์ด ๋ก์ง์ด ๋ง๋์ง ์ ๋ชจ๋ฅด๊ฒ ๋ค์,, ์ฐ์ POSTMAN์ผ๋ก ์๋์ด ์๋๋ ๋์ด๊ฐ๊ฒ ์ฉ๋๋ค!
ํ์๊ฐ์ TEST
ํ์๊ฐ์ ํ ํด๋น AccessToken์ผ๋ก ์์ฒญํ์ฌ ๋ฆฌ์์ค๋ฅผ ๋ฐ๋ TEST
์ฌ ๋ก๊ทธ์ธ ์ AccessToken ์ฌ๋ฐ๊ธ, RefreshToken ์ ํจ์ฑ ํ์ธ
๋ถ์กฑํ ์ค๋ช ์ด๋ ํ๋ฆฐ ๋ถ๋ถ ๋๊ธ ๋จ๊ฒจ์ฃผ์๋ฉด ๊ฐ์ฌ๋๋ฆฝ๋๋ค!
'๐ WEB > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Interceptor ํ์ฉํ๊ธฐ ( feat . ArgumentResolver, Custom Annotation ) (2) | 2022.03.25 |
---|---|
Spring boot์์ JSP ์ฌ์ฉํ๊ธฐ (0) | 2021.01.25 |
Mapper Interface? (0) | 2020.07.23 |
Spring ์ด์ ๋ฆฌ 3.Annotation (0) | 2020.06.13 |
Spring ์ด์ ๋ฆฌ 2.Spring 3๋ ์๋์๋ฆฌ (0) | 2020.06.06 |