
DTO로 데이터를 받을 때 보내는 타입을 정하는게 중요하다!
반드시 출력하여 확인해서 데이터가 잘 오는지 보고 진행해야함
@ModelAttribute
➡️ 컨텐츠 타입이 multipart/formdata = 이미지를 받을 수 있음
@RequestBody
➡️ 컨텐츠 타입이 application/json ⇒ 이미지를 받을 수 없음
System.out.println(itemImage.toString());
System.out.println(file.getOriginalFilename());
⇒ ItemImage번호와 파일명 확인용으로 두가지 정보를 확인 할 수 있다
이때 이미지에 대한 4개의 정보는 비어있다
➡️ 이미지 정보를 DTO에 담아준다
itemImage.setImagedata(file.getBytes());
이미지 데이터는 byte 배열형태 = byte[]로 넣어준다
📢 컨트롤러에서 itemImage.setImagename로 이미지 이름 지정시
file.getName()로 넣어주면 postman에서 지정한 키값이 들어가버린다!
➡️ file.getOriginalFilename(); 이용하면 첨부이미지 이름을 사용하여 이미지이름을 지정할 수 있다

이미지 번호를 전달하면 해당하는 이미지의 URL을 전송
이미지 가져오기 = 이미지 url을 가져오는 개념
➡️ 실제 이미지를 꺼내어 사용도 가능하지만 byte배열 형태라 읽어오는데 시간이 오래걸리기 때문에 url을 가져오는게 더 효율적이다
// 127.0.0.1:8080/BOOT1/api/itemimage/image?no=8
@GetMapping(value = "/image")
public ResponseEntity<byte[]> imageGET(
@RequestParam(name = "no") Long no) throws IOException {
System.out.println(no);
// 아이템 이미지 번호가 존재하는 경우
if (no > 0L) {
ItemImageDTO item = imageMapper.selectImageOne(no);
// System.out.println(item.toString());
if (item.getImagesize() > 0L) { // 이미지 파일이 존재하는 경우
// 타입설정 png인지 jpg인지 gif인지
HttpHeaders headers = new HttpHeaders();
headers.setContentType(
MediaType.parseMediaType(item.getImagetype()));
// 실제이미지데이터, 타입이포함된 header, status 200
ResponseEntity<byte[]> response = new ResponseEntity<>(
item.getImagedata(), headers, HttpStatus.OK);
return response;
} else { // 이미지 파일이 존재하지 않는경우 = default이미지 설정
InputStream is = resourceLoader.getResource("classpath:/static/image/noimage.jpg")
.getInputStream();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
// 실제이미지데이터, 타입이포함된 header, status 200
ResponseEntity<byte[]> response = new ResponseEntity<>(
is.readAllBytes(), headers, HttpStatus.OK);
return response;
}
} else { // 아이템 이미지 번호가 존재하지 않는경우 = default이미지 설정
InputStream is = resourceLoader.getResource("classpath:/static/image/noimage.jpg")
.getInputStream();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
// 실제이미지데이터, 타입이포함된 header, status 200
ResponseEntity<byte[]> response = new ResponseEntity<>(
is.readAllBytes(), headers, HttpStatus.OK);
return response;
}
}
// 아이템 이미지 수정
// 127.0.0.1:8080/BOOT1/api/itemimage/update.json
@PostMapping(value = "/update.json")
public Map<String, Object> updatePUT(
@ModelAttribute ItemImageDTO itemImage,
@RequestParam(name = "file") MultipartFile file,
@RequestParam(name = "itemno") Long itemno,
@RequestParam(name = "no") Long no) {
// DTO에 정보 모아서 보내기 => @ModelAttribute에 ItemImageDTO를 담아 보낸다
Map<String, Object> retMap = new HashMap<>();
try {
// 첨부한 이미지의 정보가 잘 담기는지 확인용 출력
System.out.println(itemImage.toString());
System.out.println(file.getOriginalFilename());
itemImage.setNo(no);
itemImage.setItemno(itemno);
itemImage.setImagedata(file.getBytes());
itemImage.setImagename(file.getOriginalFilename());
itemImage.setImagesize(file.getSize());
itemImage.setImagetype(file.getContentType());
System.out.println("=======" + itemImage.getImagename().toString());
int ret = imageMapper.updateImageOne(itemImage);
// int ret = imageMapper.insertImage(itemImage);
retMap.put("status", 200);
retMap.put("result", ret);
} catch (Exception e) {
e.printStackTrace();
retMap.put("status", -1);
}
return retMap;
}
아이템 이미지 삭제
= 1개 이미지 삭제하는 경우 ITEMIMAGETBL의 기본키 NO를 조건으로 둔다
// 127.0.0.1:8080/BOOT1/api/itemimage/deleteOneImage.json
@PostMapping(value = "/deleteOneImage.json")
public Map<String, Object> imageDeleteOnePOST(
@RequestParam(name = "no") Long no) {
Map<String, Object> retMap = new HashMap<>();
try {
int ret = imageMapper.deleteImageOne(no);
retMap.put("status", 200);
retMap.put("result", ret);
} catch (Exception e) {
e.printStackTrace();
retMap.put("status", -1);
}
return retMap;
}
public int deleteAllItemImage(Long itemno);
<delete id="deleteAllItemImage" parameterType="Long">
DELETE FROM ITEMIMAGETBL WHERE ITEMNO=#{itemno}
</delete>
Controller에서 PostMapping > RequestParam으로
itemno보내줬으니
postman에서 > body > form-data로itemno보내야 한다
// 127.0.0.1:8080/BOOT1/api/itemimage/deleteAllItemImage.json
@PostMapping(value = "/deleteAllItemImage.json")
public Map<String, Object> imageDeleteAllPOST(
@RequestParam(name = "itemno") Long itemno
) {
Map<String, Object> retMap = new HashMap<>();
try {
int ret = imageMapper.deleteAllItemImage(itemno);
retMap.put("status", 200);
retMap.put("result", ret);
} catch (Exception e) {
e.printStackTrace();
retMap.put("status", -1);
}
return retMap;
}
삭제 전/삭제후 리턴결과/삭제 후

JSON Web Token = JWT
토큰 발행시 어떤 라이브러리를 사용해도 상관없다
➡️ JSON Web Token 이용해보기
토큰발행을 위한 JSON Web Token(JWT) 라이브러리 설치
<!-- jjwt -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>2.3.2</version>
</dependency>
org.glassfish.jaxb
jaxb-runtime
2.3.2
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
package com.example.jwt;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@Component
public class JwtUtil {
// 토큰 생성용 보안키
private final String SECRETKEY = "fekjkfe43jfe";
// 정보 추출용 메소드
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = Jwts.parser().setSigningKey(SECRETKEY).parseClaimsJws(token).getBody();
return claimsResolver.apply(claims);
}
// 토큰 생성(아이디 정보를 이용한 토큰 생성)
public String generateToken(String username) {
System.out.println(username);
// 만료시간 1초=1000 ex) 30분 => 1000 * 60 * 30
long tokenValidTime = 1000 * 60 * 60 * 4; // 4시간
Map<String, Object> claims = new HashMap<>();
String token = Jwts.builder().setClaims(claims).setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + tokenValidTime))
.signWith(SignatureAlgorithm.HS256, SECRETKEY).compact();
return token;
}
// 토큰 검증
public Boolean validateToken(String token, String userid) {
// 토큰에서 아이디 정보 추출
final String username = this.extractUsername(token);
if (username.equals(userid) && !isTokenExpired(token)) {
return true;
}
return false;
}
// 토큰에서 아이디 정보 추출하기
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
// 토큰에서 만료 시간 추출하기
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
// 토큰의 만료시간이 유효한지 확인
public Boolean isTokenExpired(String token) {
// 만료시간 가져와서 현재시간보다 이전인지 확인
return this.extractExpiration(token).before(new Date());
}
}
생성한 jwt파일 Application에 등록하여 사용하기
"com.example.jwt"
BCryptPasswordEncoder bcpe = new BCryptPasswordEncoder();
➡️@Autowired하여 사용한다@Autowired PasswordEncoder bcpe;
// @RequestBody => postman 에서 Body > raw로 보내기
// 127.0.0.1:8080/BOOT1/api/member/join.json
// 회원가입
@PostMapping(value = "/join.json")
public Map<String, Object> joinPost(@RequestBody MemberDTO member){
Map<String, Object> retMap = new HashMap<>();
try {
//BCryptPasswordEncoder bcpe = new BCryptPasswordEncoder(); => @Autowired하여 사용
member.setUserpw( bcpe.encode(member.getUserpw()) );
int ret = mmapper.joinMember(member);
retMap.put("status", 200);
retMap.put("result", ret);
} catch (Exception e) {
e.printStackTrace();
retMap.put("status", 1);
}
return retMap;
}
View에서 던져준 로그인 아이디 암호를 받아서 토큰 생성
postman 에서 보낼때 폼데이터인지 row를 보내는지 잘 확인해야한다
SecurityConfig에서 생성된 로그인 방식은 CDN방식이다 (Thymeleaf를 이용한 로그인 방식)
➡️ AuthenticationManager 를 이용한 REST용 인증방식 추가// REST용 인증방식 // @Bean => 객체 생성 (Autowired를 통해서 사용가능) // @Autowired AuthenticationManager @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception{ return authenticationConfiguration.getAuthenticationManager(); }
토큰 발행시
UsernamePasswordAuthenticationToken(아이디, 비밀번호, 권한)메서드를 사용한다
JwtUtil 컴포넌트 객체 생성 ➡️
@Autowired하여 사용@Autowired JwtUtil jwtUtil;
// 로그인하기
// 127.0.0.1:8080/BOOT1/api/member/login.json
@PostMapping(value = "/login.json")
public Map<String, Object> loginPost(
@RequestBody MemberDTO member){
System.out.println(member.toString());
Map<String, Object> retMap = new HashMap<>();
try {
// String권한으로 collection으로 변경
String[] strRole = { member.getRole() };
Collection<GrantedAuthority> role
= AuthorityUtils.createAuthorityList(strRole);
// CustomDetailsService와 같은 역할
UsernamePasswordAuthenticationToken upat = new UsernamePasswordAuthenticationToken(member.getUserid(), member.getUserpw(), role);
authenticationManager.authenticate(upat);
retMap.put("status", 200);
retMap.put("token", jwtUtil.generateToken(member.getUserid()));
} catch (Exception e) {
e.printStackTrace();
retMap.put("status", -1);
}
return retMap;
}
로그인 성공시 토큰발행/로그인 실패하는 경우 -1

회원가입, 로그인, 회원정보수정
➡️ 회원가입과 로그인은 토큰 발행여부와 관계 없지만,
로그인 이후에 진행되어야 하는 부분들 (= 회원정보 수정, 암호변경, 회원탈퇴)은
토큰 검증이 완료된 이후에 진행되어야 한다
JwtFilter사용하여 토큰검증진행하는JwtFilter.java파일 생성
- 필터를 적용하고자 하는 url 설정한다 ➡️ 로그인과 회원가입은 필터적용 제외시킨다
- 토큰 검증 과정에서 토큰이 없는경우는 예외처리를 해준다
- 오류시에는
filterChain.doFilter(request, response);가 작동하지 않으며,
restcontroller로 넘어갈 수 없다// filterChain 라인이 실행되어야 restcontroller로 넘어감 filterChain.doFilter(request, response);
package com.example.jwt;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
// 필터를 적용하고자 하는 url 설정
@WebFilter(urlPatterns = {
"/api/member/update.json", // 회원정보 수정
"/api/member/updatepw.json", // 암호변경
"/api/member/delete.json" // 회원탈퇴
})
public class JwtFilter extends OncePerRequestFilter {
@Autowired
JwtUtil jwtUtil;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
System.out.println("=======filter============");
System.out.println(request.getRequestURI());
System.out.println("=======filter============");
// Header에 TOKEN심기
String token = request.getHeader("TOKEN");
if (token == null || token.length() == 0) { // TOKEN이 없는 경우
// 강제로 오류발생 시킴
throw new Exception();
}
String userid = jwtUtil.extractUsername(token);
if (jwtUtil.validateToken(token, userid) == false) {
throw new Exception();
}
// 아래 filterChain 라인이 실행되어야 restcontroller로 넘어감
filterChain.doFilter(request, response);
} catch (Exception e) {
e.printStackTrace();
// 에러인 경우 에러메세지 전송
response.sendError(-1, "token error");
}
}
}
생성된 TOKEN을 이용하여 사용자 정보를 조회하여 userid를 가져온다
토큰에서 가져오는 userid는 세션과 다르고 세션이 아니다!
로직
1. 생성된token을 이용하여userid를 가져온다
2.userid를MemberDTO member에 넣어준다
3.mmapper에MemberDTO member를 담아 회원의 정보를 update한다updateinfoMember
// 회원정보 수정
@PutMapping(value = "/update.json")
public Map<String, Object> updatePUT(
@RequestHeader(name = "TOKEN") String token,
@RequestBody MemberDTO member){
Map<String, Object> retMap = new HashMap<>();
try {
// 생성된 TOKEN을 이용하여 userid를 가져옴
String userid = jwtUtil.extractUsername(token);
System.out.println("tokenUsername => " + userid);
System.out.println("====member===="+member.toString());
member.setUserid(userid);
int ret = mmapper.updateinfoMember(member);
System.out.println("=========ret======== : " + ret);
retMap.put("status", 200);
retMap.put("result", ret);
} catch (Exception e) {
e.printStackTrace();
retMap.put("status", -1);
}
return retMap;
}
암호변경시 필요한 정보
1. 암호변경창에서 입력한 기존 비밀번호(rowpw)
2. DB에 암호화 되어있는 기존 비밀번호(userpw)
3. 사용자가 입력한 변경할 비밀번호(newpw)
➡️MemberDTO에 임시변수newpw를 생성해 준다
➡️ 새 변수 생성대신 Map에 변경할 비밀번호를 넣어 보내도 된다
로직
1. token을 이용하여 userid를 가져온다
2. userid로 기존 회원정보를 조회한다mmapper.selectMemberOne(userid)
3. 조회한 회원정보에서 암호화된 비밀번호 정보를 가져온다
4.bcpe.matches(현재암호, DB암호)를 이용해 현재암호와 DB암호가 일치하는지 확인한다
5. 현재암호와 DB암호가 일치하는 경우map에
해당 사용자의 아이디userid+ 암호화된 변경할 암호bcpe.encode(member.getNewpw())를 넣어 비밀번호를 변경해준다updateMemberPw(map)
// 암호변경 /updatepw.json
@PutMapping(value = "/updatepw.json")
public Map<String, Object> updatepwPUT(
@RequestHeader(name = "TOKEN") String token, @RequestBody MemberDTO member
){
Map<String, Object> retMap = new HashMap<>();
try {
// 생성된 TOKEN을 이용하여 userid를 가져옴
String userid = jwtUtil.extractUsername(token);
System.out.println("=========" + userid);
// 기존 회원정보 조회
MemberDTO member1 = mmapper.selectMemberOne(userid);
BCryptPasswordEncoder bcpe = new BCryptPasswordEncoder();
// 조회한 회원정보에서 비밀번호 가져오기 = DB암호
String memberpw = member1.getUserpw();
// bcpe.matches("rawPassword = 현재암호", "encodedPassword = DB암호")
if(bcpe.matches( member.getUserpw() , memberpw )){
Map<String, Object> map = new HashMap<>();
map.put("userid", userid);
map.put("userpw", bcpe.encode(member.getNewpw()));
mmapper.updateMemberPw(map);
retMap.put("status", 200);
}
} catch (Exception e) {
e.printStackTrace();
retMap.put("status", -1);
}
return retMap;
}