토큰 인증 방식
➡️ 백엔드 서버로부터 받은 JSESSIONID를 저장했어도 다음 요청 시 쿠키를 가지고 가지 못하는 문제 해결 방법
- 클라이언트가 로그인 요청을 보냄
- 서버가 사용자 정보를 검증하고 토큰을 발급
- 클라이언트는 토큰을 저장하고 요청 시 포함하여 전송
- 서버는 토큰을 검증하여 사용자 인증 수행
쇼핑몰 실습 - 토큰 로그인 수행
로그인 테이블 생성
use ureca;
drop table if exists login;
CREATE TABLE `ureca`.`login` (
`email` VARCHAR(50) NOT NULL primary key,
`token` VARCHAR(256) NOT NULL unique,
`logintime` TIMESTAMP NOT NULL DEFAULT current_timestamp);
해싱/암호화를 통한 토큰 생성
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class OpenCrypt {
public static byte[] getSHA256(String source, String salt) {
byte byteData[]=null;
try{
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(source.getBytes());
md.update(salt.getBytes());
byteData= md.digest();
System.out.println("원문: "+source+ " SHA-256: "+
byteData.length+","+byteArrayToHex(byteData));
}catch(NoSuchAlgorithmException e){
e.printStackTrace();
}
return byteData;
}
public static byte[] generateKey(String algorithm,int keySize) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
keyGenerator.init(keySize);
SecretKey key = keyGenerator.generateKey();
return key.getEncoded();
}
public static String aesEncrypt(String msg, byte[] key) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
String iv = "AAAAAAAAAAAAAAAA";
cipher.init(Cipher.ENCRYPT_MODE,
skeySpec,
new IvParameterSpec(iv.getBytes()));
byte[] encrypted = cipher.doFinal(msg.getBytes());
return byteArrayToHex(encrypted);
}
public static String aesDecrypt(String msg,byte[] key ) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
String iv = "AAAAAAAAAAAAAAAA";
cipher.init(Cipher.DECRYPT_MODE,
skeySpec,
new IvParameterSpec(iv.getBytes()));
byte[] encrypted = hexToByteArray(msg);
byte[] original = cipher.doFinal(encrypted);
return new String(original);
}
public static byte[] hexToByteArray(String hex) {
if (hex == null || hex.length() == 0) {
return null;
}
byte[] ba = new byte[hex.length() / 2];
for (int i = 0; i < ba.length; i++) {
ba[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
}
return ba;
}
// byte[] to hex
public static String byteArrayToHex(byte[] ba) {
if (ba == null || ba.length == 0) {
return null;
}
StringBuffer sb = new StringBuffer(ba.length * 2);
String hexNumber;
for (int x = 0; x < ba.length; x++) {
hexNumber = "0" + Integer.toHexString(0xff & ba[x]);
sb.append(hexNumber.substring(hexNumber.length() - 2));
}
return sb.toString();
}
}
Salt 생성/Email 해싱/DB에 토큰 저장
package com.shop.cafe.service;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.shop.cafe.dao.LoginDao;
import com.shop.cafe.dao.MemberDao;
import com.shop.cafe.dto.Login;
import com.shop.cafe.dto.Member;
import com.shop.cafe.util.OpenCrypt;
@Service
public class MemberService {
@Autowired
MemberDao memberDao;
@Autowired
LoginDao loginDao;
public Login tokenLogin(Member m) throws Exception {
m=memberDao.login(m);
if(m!=null) {
String nickname=m.getNickname();
if(nickname!=null && !nickname.trim().equals("")) {
//member table에서 email과 pwd가 확인된 상황 즉 login ok
String email=m.getEmail();
//1. salt를 생성한다
String salt=UUID.randomUUID().toString();
System.out.println("salt:"+salt);
//2. email을 hashing 한다
byte[] originalHash=OpenCrypt.getSHA256(email, salt);
//3. db에 저장하기 좋은 포맷으로 인코딩한다
String myToken=OpenCrypt.byteArrayToHex(originalHash);
System.out.println("myToken : "+myToken);
//4. login table에 token 저장
Login loginInfo=new Login(email, myToken, nickname, null);
loginDao.insertToken(loginInfo);
return loginInfo;
}
}
return null;
}
public Member login(Member m) throws Exception {
return memberDao.login(m);
}
public void insertMember(Member m) throws Exception{
memberDao.insertMember(m);
}
public void updateMember(Member m) throws Exception{
memberDao.updateMember(m);
}
public void deleteMember(String email) throws Exception{
memberDao.deleteMember(email);
}
public void logout(String authorization) throws Exception {
loginDao.deleteToken(authorization);
}
}
Login.java
package com.shop.cafe.dto;
import java.util.Date;
public class Login {
private String email, token, nickname;
private Date loginTime;
public Login(String email, String token, String nickname, Date loginTime) {
super();
this.email = email;
this.token = token;
this.nickname = nickname;
this.loginTime = loginTime;
}
public Login() {
super();
// TODO Auto-generated constructor stub
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public Date getLoginTime() {
return loginTime;
}
public void setLoginTime(Date loginTime) {
this.loginTime = loginTime;
}
@Override
public String toString() {
return "Login [email=" + email + ", token=" + token + ", nickname=" + nickname + ", loginTime=" + loginTime
+ "]";
}
}
LoginDao.java
package com.shop.cafe.dao;
import org.apache.ibatis.annotations.Mapper;
import com.shop.cafe.dto.Login;
@Mapper
public interface LoginDao {
public void insertToken(Login login) throws Exception;
public void deleteToken(String token) throws Exception;
}
login.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shop.cafe.dao.LoginDao">
<insert id="insertToken" parameterType="Login">
insert into login(email, token) values(#{email},#{token})
</insert>
<delete id="deleteToken" parameterType="String">
delete from login where token=#{token}
</delete>
</mapper>
Response Body에 nickname과 token을 넣어 응답
package com.shop.cafe.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
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.RestController;
import com.shop.cafe.dto.Login;
import com.shop.cafe.dto.Member;
import com.shop.cafe.service.MemberService;
@RestController
@CrossOrigin("http://127.0.0.1:5500/")
public class MemberController {
@Autowired
MemberService memberService;
@PostMapping("logout")
public void logout(@RequestHeader String authorization) {
System.out.println(authorization);
try {
memberService.logout(authorization);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@PostMapping("tokenLogin")
public Map<String,String> tokenLogin(@RequestBody Member m) {
System.out.println(m);
Map<String,String> responseMap=new HashMap<>();
try {
Login loginInfo=memberService.tokenLogin(m);
if(loginInfo!=null && loginInfo.getNickname()!=null && loginInfo.getToken()!=null) {
responseMap.put("nickname", loginInfo.getNickname());
responseMap.put("Authorization", loginInfo.getToken());
}else {
responseMap.put("msg", "다시 로그인 해주세요");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
responseMap.put("msg", "다시 로그인 해주세요");
}
return responseMap;
}
@PostMapping("login")
public Map<String,String> login(@RequestBody Member m) {
System.out.println(m);
Map<String,String> responseMap=new HashMap<>();
try {
m=memberService.login(m);
String nickname=m.getNickname();
if(m!=null && nickname!=null && !nickname.trim().equals("")) {
responseMap.put("nickname", nickname);
}else {
responseMap.put("msg", "다시 로그인 해주세요");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
responseMap.put("msg", "다시 로그인 해주세요");
}
return responseMap;
}
@PostMapping("insertMember")
public String insertMember(@RequestBody Member m) {
System.out.println(m);
try {
memberService.insertMember(m);
return m.getNickname()+"님 가입을 환영합니다";
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "회원 가입 실패";
}
}
@PostMapping("updateMember")
public String updateMember(@RequestBody Member m) {
System.out.println(m);
try {
memberService.updateMember(m);
return "ok";
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "email과 pwd 확인해 주세요";
}
}
@PostMapping("deleteMember")
public String deleteMember(@RequestBody String email) {
System.out.println(email);
try {
memberService.deleteMember(email);
return "ok";
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "email과 pwd 확인해 주세요";
}
}
}
프론트엔드
- 로그인 ok 시에 nicknmae과 token을 session storage에 저장
- 전역 axios의 header에 token을 저장해 놓으면 모든 axios 요청 시 해당 token이 전송됨
- logout 시에 해당 token을 삭제
document.getElementById("signupBtn").addEventListener("click", async ()=>{
const email=document.getElementById("email").value;
const pwd=document.getElementById("pwd").value;
const nickname=document.getElementById("nickname").value;
const data={email,pwd,nickname};
const response=await axios.post("http://localhost:8080/insertMember" , data);
document.getElementById("effetMsg").innerHTML = response.data;
});
// document.getElementById("loginBtn").addEventListener("click", async () => {
// const email = document.getElementById("loginEmail").value;
// const pwd = document.getElementById("loginPwd").value;
// const data = { email, pwd };
// const response=await axios.post("http://localhost:8080/login" , data);
// console.log(response.data);
// alert(response.data.nickname+"님 반갑습니다");
// });
document.getElementById("loginBtn").addEventListener("click", async () => {
const email = document.getElementById("loginEmail").value;
const pwd = document.getElementById("loginPwd").value;
const data = { email, pwd };
const response=await axios.post("http://localhost:8080/tokenLogin" , data);
document.getElementById("loginSpan").innerHTML=`${response.data.nickname}
<button class="btn btn-danger btn-sm" id="logoutBtn">Logout</button>`;
const token = response.data.Authorization;
console.log('Authorization:', token);
sessionStorage.setItem('Authorization', token);
sessionStorage.setItem('nickname', response.data.nickname);
axios.defaults.headers.common['Authorization'] = token; // Authorization 헤더 설정
// 모달 닫기
const modal = bootstrap.Modal.getInstance(document.getElementById("loginModal"));
loginModal.setAttribute("aria-hidden", "true");
modal.hide();
});
const Authorization = sessionStorage.getItem("Authorization");
const nickname = sessionStorage.getItem("nickname");
if (Authorization && nickname) {
axios.defaults.headers.common['Authorization'] = Authorization; // Authorization 헤더 설정
document.getElementById("loginSpan").innerHTML = `${nickname}
<button class="btn btn-danger btn-sm" id="logoutBtn">Logout</button>`;
}
document.getElementById("loginSpan").addEventListener("click", async (event)=>{
if(event.target.id=='logoutBtn'){
await axios.post("http://localhost:8080/logout");
sessionStorage.removeItem("nickname");
sessionStorage.removeItem("Authorization");
axios.defaults.headers.common['Authorization'] = ''; // Authorization 헤더에서 삭제
window.location.reload();
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<title>Bootstrap 5 Website Example</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<style>
.fakeimg {
height: 200px;
background: #aaa;
}
</style>
</head>
<body>
<div
class="px-3 py-1 bg-info text-white text-center d-flex justify-content-between"
>
<span id="effetMsg">effect!</span>
<span id="loginSpan">
<a
href="#"
data-bs-toggle="modal"
data-bs-target="#loginModal"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6"
width="24"
height="24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15M12 9l-3 3m0 0 3 3m-3-3h12.75"
/>
</svg>
</a>
</span>
</div>
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
<div class="container-fluid">
<ul class="navbar-nav">
<li class="nav-item" id="signupLi">
<a
class="nav-link active"
href="#"
data-bs-toggle="modal"
data-bs-target="#signupModal"
>SignUp</a
>
</li>
<li class="nav-item"><a class="nav-link" href="#">Link</a></li>
<li class="nav-item"><a class="nav-link" href="#">Link</a></li>
<li class="nav-item">
<a class="nav-link disabled" href="#">Disabled</a>
</li>
</ul>
</div>
</nav>
<div class="container mt-5">
<div class="row" id="productListDiv"></div>
</div>
<div class="mt-5 p-4 bg-dark text-white text-center">
<p>Footer</p>
</div>
<!-- signupModal -->
<div class="modal" id="signupModal">
<div class="modal-dialog">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title">회원가입</h4>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
></button>
</div>
<!-- Modal body -->
<div class="modal-body">
<div class="mb-3 mt-3">
<label for="nickname" class="form-label">Nickname:</label>
<input
type="text"
class="form-control"
id="nickname"
placeholder="Enter nickname"
name="nickname"
/>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email:</label>
<input
type="email"
class="form-control"
id="email"
placeholder="Enter email"
name="email"
/>
</div>
<div class="mb-3">
<label for="pwd" class="form-label">Password:</label>
<input
type="password"
class="form-control"
id="pwd"
placeholder="Enter password"
name="pswd"
/>
</div>
<button type="submit" class="btn btn-primary" id="signupBtn">
가입
</button>
</div>
<!-- Modal footer -->
<div class="modal-footer">
<button
type="button"
class="btn btn-danger"
data-bs-dismiss="modal"
>
Close
</button>
</div>
</div>
</div>
</div>
<!-- loginModal -->
<div class="modal" id="loginModal">
<div class="modal-dialog">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title">로그인</h4>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
></button>
</div>
<!-- Modal body -->
<div class="modal-body">
<div class="mb-3">
<label for="email" class="form-label">Email:</label>
<input
type="email"
class="form-control"
id="loginEmail"
placeholder="Enter email"
name="email"
/>
</div>
<div class="mb-3">
<label for="pwd" class="form-label">Password:</label>
<input
type="password"
class="form-control"
id="loginPwd"
placeholder="Enter password"
name="pswd"
/>
</div>
<button type="submit" class="btn btn-primary" id="loginBtn">
로그인
</button>
</div>
<!-- Modal footer -->
<div class="modal-footer">
<button
type="button"
class="btn btn-danger"
data-bs-dismiss="modal"
>
Close
</button>
</div>
</div>
</div>
</div>
<!-- commentModal -->
<div class="modal fade" id="commentModal" tabindex="-1" aria-labelledby="commentModalLabel">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="commentModalLabel">후기</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="modal-body" id="commentModalBody">
<!-- 최근 리뷰가 여기에 표시됩니다. -->
</div>
<textarea id="commentTextarea" placeholder="후기를 입력하세요..." rows="3" class="form-control"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
<button type="button" class="btn btn-primary" id="submitComment">후기 남기기</button>
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="./js/index.js"></script>
<script src="./js/member.js"></script>
</body>
</html>
팀 챌린지 - 쇼핑몰 장바구니 담기 구현해보기
Cart 테이블 생성
use ureca;
drop table if exists cart;
create table cart (
cartId int primary key NOT NULL auto_increment,
email varchar(50) NOT NULL,
prodcode int NOT NULL,
amount int default 0
);
백엔드
1️⃣ 로그인 상태 체크하기 (로그인한 경우에 장바구니 담기 위함)
- login.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shop.cafe.dao.LoginDao">
<!-- ... 생략 -->
<select id="isLogin" resultType="String" parameterType="String">
select email from login where token = #{token}
</select>
</mapper>
- LoginDao.java
package com.shop.cafe.dao;
import org.apache.ibatis.annotations.Mapper;
import com.shop.cafe.dto.Login;
@Mapper
public interface LoginDao {
public void insertToken(Login login) throws Exception;
public void deleteToken(String authorization) throws Exception;
public String isLogin(String token) throws Exception;
}
- MemberService.java
package com.shop.cafe.service;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.shop.cafe.dao.LoginDao;
import com.shop.cafe.dao.MemberDao;
import com.shop.cafe.dto.Login;
import com.shop.cafe.dto.Member;
import com.shop.cafe.util.OpenCrypt;
@Service
public class MemberService {
@Autowired
MemberDao memberDao;
@Autowired
LoginDao loginDao;
public String isLogin(String token) throws Exception {
return loginDao.isLogin(token);
}
// ... 생략
}
- CartController.java
package com.shop.cafe.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
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.RestController;
import com.shop.cafe.dto.Cart;
import com.shop.cafe.service.CartService;
import com.shop.cafe.service.MemberService;
@RestController
@CrossOrigin("http://127.0.0.1:5500/")
public class CartController {
@Autowired
MemberService memberService;
@Autowired
CartService cartService;
@PostMapping("addCart")
public String addCart(@RequestHeader String authorization, @RequestBody Cart c) {
System.out.println(authorization);
try {
String email = memberService.isLogin(authorization);
if(email != null) {
c.setEmail(email); // 추출한 email을 Dto에 설정
cartService.addCart(c);
return "장바구니에 추가되었습니다";
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "장바구니 담기 실패";
}
return "장바구니 담기 실패";
}
}
2️⃣ 장바구니 상태 확인 및 추가(어떤 유저의 특정 상품이 이미 담겨있는 경우에는 수량만 업데이트)
- cart.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shop.cafe.dao.CartDao">
<!-- 장바구니에 해당 상품이 있는지 확인 -->
<select id="findCartItem" resultType="Cart" parameterType="Cart">
select * from cart
where email = #{email} and prodcode = #{prodcode}
</select>
<!-- 장바구니 수량 업데이트 -->
<update id="updateCartAmount" parameterType="Cart">
update cart
set amount = amount + #{amount}
where email = #{email} and prodcode = #{prodcode}
</update>
<!-- 장바구니에 상품 추가 -->
<insert id="insertCartItem" parameterType="Cart">
insert into cart(email, prodcode, amount) values(#{email},#{prodcode},#{amount})
</insert>
</mapper>
- Cart.java
package com.shop.cafe.dto;
public class Cart {
private int cartId, prodcode, amount;
private String email;
public Cart(int cartId, int prodcode, int amount, String email) {
super();
this.cartId = cartId;
this.prodcode = prodcode;
this.amount = amount;
this.email = email;
}
public Cart() {
super();
// TODO Auto-generated constructor stub
}
public int getCartId() {
return cartId;
}
public void setCartId(int cartId) {
this.cartId = cartId;
}
public int getProdcode() {
return prodcode;
}
public void setProdcode(int prodcode) {
this.prodcode = prodcode;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Cart [cartId=" + cartId + ", prodcode=" + prodcode + ", amount=" + amount + ", email=" + email + "]";
}
}
- CartDao.java
package com.shop.cafe.dao;
import org.apache.ibatis.annotations.Mapper;
import com.shop.cafe.dto.Cart;
@Mapper
public interface CartDao {
public Cart findCartItem(Cart c) throws Exception;
public void updateCartAmount(Cart c) throws Exception;
public void insertCartItem(Cart c) throws Exception;
}
- CartService.java
package com.shop.cafe.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.shop.cafe.dao.CartDao;
import com.shop.cafe.dto.Cart;
@Service
public class CartService {
@Autowired
CartDao cartDao;
public void addCart(Cart c) throws Exception {
Cart existCart = cartDao.findCartItem(c);
if(existCart != null) {
cartDao.updateCartAmount(c);
} else {
cartDao.insertCartItem(c);
}
}
}
프론트엔드
- 장바구니 담기 버튼에 prodcode를 바탕으로 id를 부여
- 클릭 이벤트를 추가하여 버튼 클릭 시 장바구니 담기 수행
- session storage에 저장되어 있는 토큰을 가져와서 장바구니 담기 post 요청
- index.js
window.onload = async () => {
let response = await axios.get("http://localhost:8080/getAllProducts");
console.log(response);
let productList = response.data;
let productListDiv = ``;
productList.forEach((item) => {
productListDiv += `<div class="card m-3" style="width: 10rem;">
<img src="img/${item.pimg}" class="card-img-top" alt="...">
<div class="card-body">
<b class="card-title">${item.prodname}</b>
<a href="#" class="review-link" data-product-id="${item.prodcode}"
data-bs-toggle="modal" data-bs-target="#commentModal">
<img src="img/review.png" alt="review">
</a>
<p class="card-text text-danger">${item.price}원</p>
<a href="#" class="btn btn-outline-info" id="add-cart-${item.prodcode}">장바구니 담기</a>
</div>
</div>`;
});
document.getElementById("productListDiv").innerHTML = productListDiv;
productList.forEach((item) => {
document
.getElementById(`add-cart-${item.prodcode}`)
.addEventListener("click", async () => {
const Authorization = sessionStorage.getItem("Authorization");
if (!Authorization) alert("로그인 필요");
else {
const data = {
prodcode: item.prodcode,
amount: 1,
};
const response = await axios.post(
"http://localhost:8080/addCart",
data
);
alert(response.data);
}
});
});
// ... 생략
};
결과 화면
728x90
반응형
'LG 유플러스 유레카 SW > Spring' 카테고리의 다른 글
[#35] KakaoOAuth 로그인 (0) | 2025.03.18 |
---|---|
[#33] RESTful 방식 + Swagger 사용 (0) | 2025.03.14 |
[#30] XSS/Tabnabbing 공격 (0) | 2025.03.10 |
[#29] 쇼핑몰 연동 실습 - SQL Injection + Connection Pool (1) | 2025.03.07 |
[#28] 쇼핑몰 연동 실습 - 다중 서버 세션 문제 해결 + 사용자 인증 (1) | 2025.03.06 |