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;
}