@RestController → text/plain출력됨 → json포맷생성 → React.js, Vue.js 활용 → 모바일앱(하이브리드=웹+앱)
return String타입이지만 페이지 이동처리 불가
@Controller - 둘 다 가능(페이지처리와 text/plain:@ResponseBody)
HomeController.java
@GetMapping(”/”) → index.jsp이동하기 → @Controller 선택
루트에 대해서는 모두에게 허용하기
인증을 거쳐 들어온 사용자를 구분해서 웹페이지 서비스를 제공하려면 role에 정의가 필요함 → ROLE_ADMIN, ROLE_USER
필터체인을 적용할 필요가 있음
SecurityConfig.java를 추가하는 것만으로 디폴트 로그인을 피할 수 있다
인가에 대한 정책 설정이 필요하다
1. 로그인 요청 - username, password 입력받음
2. 서버 측으로 전송(username, password)이 오면 AuthenticationFilter로 요청이 오게되고 username, password를 기반으로 UsernamePasswordAuthenticationToken을 발급해주어야한다.
3. 위에서 발급된 토큰을 AuthenticationManager에게 전달
AuthenticationManager는 인증을 처리할 여러 개의 AuthenticationProvider를 가지고 있다
4. AuthenticationManager는 전달받은 정보를 가지고 인증과정을 수행해야 하며 mysql에서 데이터(username)를 조회한 다음 비번의 일치여부를 검사한다.
5. PrincipalDetailsService로 조회할 username전달
6. username을 가지고 데이터조회해서 PrincipalDetails가 그 정보를 쥐게 된다.
PrincipalDetails는 UserDetails타입이어야만 한다
Authentication이 세션정보를 받을 수 있는 타입이 반드시 UserDetails타입 이어야 함
7. 인증처리 후 인증된 토근을 AuthenticationManager에게 반환
비번은 암호화가 되어있어서 PasswordEncoder를 통해 암호화하여 DB에 저장된 값과 매칭되는지 확인을 해줌
이때 비번이 매칭되지 않으면 BadCredentialException 발생
8. 인증된 토큰을 AuthenticationFilter에게 전달
AuthenticationProvider에서 인증이 완료된 UsernamePasswordAuthenticationToken을 AuthenticationFilter로 반환하고 다시 LoginSuccessHandler로 전달해 줌
9. 인증된 토큰은 SecurityContextHolder에 저장됨
사용자가 요청을 하면 AuthenticationFilter가 최초로 그 요청을 받음
→ AuthenticationManager에게 바로 넘어가지 않고 UsernamePasswordAuthenticationToken을 통해서 Manager에게 전달됨
→ AuthenticationManager는 전달받은 내용을 AuthenticationProvider에게 전달
~여기까지는 시스템이 물밑에서 작업해 주는 것들~
→ UserDetailsService로 username을 넘김
→ UserDetails(User)
→ 다시 UserDetailsService가 Provider로 Manager로 Filter로 UserDetails의 정보를 넘겨준다
→ SecurityContextHolder 저장소에 가져온 정보를 담는다
SecurityContextHolder 안에 SecurityContext가 있고 그 안에 Authentication이 있다
그 Authentication에서 UserDetails의 정보를 가지고 있고, Authentication이 가지고 있는 세션 scope가 따로 있어서 정보를 꺼내서 비교하면 된다
<스프링 시큐리티 구글 로그인 회원가입 - PrincipalOauth2UserService.java>
package com.example.demo.oauth;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import com.example.demo.auth.PrincipalDetails;
import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;
/*
구글 로그인 버튼 클릭 -> 구글 로그인 창 -> 로그인 완료 -> 코드 리턴(OAuth2-Client라이브러리가 받음)
-> AccessToken 발급, 요청(userRequest에대한 정보) -> loadUser호출 -> 구글로부터 회원프로필을 받음
super.loadUser(userRequest).getAttributes(): Map<String, Object>으로 프로필정보 담아줌
*/
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService{
Logger logger = LogManager.getLogger(PrincipalOauth2UserService.class);
@Autowired
private UserRepository userRepository;
// 구글로부터 받은 userRequest데이터에 대한 후처리 메소드 구현 - Profile정보 수집
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
// 액세스 토큰 출력해보기
logger.info("벤더정보: " + userRequest.getClientRegistration().getRegistrationId());
logger.info("구글에서 발급하는 토큰값: " + userRequest.getAccessToken().getTokenValue());
logger.info("구글에서 발급하는 id값: " + userRequest.getClientRegistration().getRegistrationId());
logger.info("구글에서 발급하는 clientid값: " + userRequest.getClientRegistration().getClientId());
// 구글 인증 후 프로필정보를 가져와서 OAuth2User에 담기
OAuth2User oauth2User = super.loadUser(userRequest);
logger.info(oauth2User.getAttribute("sub").toString());
logger.info(oauth2User.getAttribute("email").toString());
String vender = userRequest.getClientRegistration().getRegistrationId();
String sub = oauth2User.getAttribute("sub").toString();
String username = vender + "_" + sub;
String password = "123";
String email = oauth2User.getAttribute("email").toString();
String role = "ROLE_USER";
// 구글 프로필을 가지고 회원가입 전에 이미 되어있는지 체크하기
User userEntity = userRepository.findByUsername(username);
// username이 없을 경우 - 값 저장하기
if(userEntity == null) {
logger.info("구글계정으로 회원가입한 이력이 없습니다.");
userEntity = User.builder()
.username(username)
.password(password)
.email(email)
.role(role)
.build();
userRepository.save(userEntity);
}
// username이 있을 경우
else {
logger.info("구글계정으로 회원가입이 되어 있습니다.");
}
return new PrincipalDetails(userEntity, oauth2User.getAttributes());
}
}
<스프링 시큐리티 구글 로그인 회원가입 - User.java>
package com.example.demo.model;
import java.sql.Timestamp;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.hibernate.annotations.CreationTimestamp;
import lombok.Builder;
import lombok.Data;
@Entity // jpa사용시 테이블 생성해줌 - 확인할것. application.yml에 create모드인지 체크
@Data // getter, setter생성해줌
public class User {
@Id // primary key
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private String email;
private String role; // ROLE_USER, ROLE_MANAGER, ROLE_ADMIN
@CreationTimestamp
private Timestamp createDate;
// 회원가입에 사용할 생성자 추가
// @Builder하면 안됨! - 들고온 정보를 모두 초기화하기에! 문법상 써놓은 생성자임!
public User() {}
// UserRepository의 findByUsername으로 찾아낸 정보로 초기화가됨
@Builder
public User(String username, String password, String email
, String role, Timestamp createDate) {
this.username = username;
this.password = password;
this.email = email;
this.role = role;
this.createDate = createDate;
}
}
<스프링 시큐리티 구글 로그인 회원가입 - PrincipalDetailsService.java>
package com.example.demo.auth;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;
@Service
public class PrincipalDetailsService implements UserDetailsService {
Logger logger = LogManager.getLogger(PrincipalDetailsService.class);
@Autowired
private UserRepository userRepository; // mysql에서 꺼내와야하니까
// 아래 파라미터 username은 화면에서 사용하는, 즉 input type의 name과 반드시 일치해야함
// -> /login 요청되면 시큐리티가 인터셉트해서 자동으로 진행
// 만약 다르게 하려면 SecurityCongif에서 .usernameParameter("mem_name") 추가할 것!
// loadUserByUsername 메소드의 리턴은 어디로 가는가?
// 시큐리티 session(Authentication(내부 userDetails))
// 메소드 종료시 @AuthentidationPrincipal 어노테이션 생성됨
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info(username); // 사용자가 입력한 이름
// jpa query method로 검색하기 - mysql
// mysql서버에 요청하는 코드
// User타입은 Authentication에 직접 담을 수 없다
User userEntity = userRepository.findByUsername(username); // 검색하기-select문 대신 생성
logger.info(userEntity);
logger.info(userEntity.getRole());
if(userEntity != null) { // DB에서 찾아온 정보를 들고 있으면
return new PrincipalDetails(userEntity); // Authentication에 담을 수 있는 타입으로 변환
}
return null;
}
}
<스프링 시큐리티 구글 로그인 회원가입 - PrincipalDetails.java>
package com.example.demo.auth;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import com.example.demo.model.User;
import lombok.Data;
/*
* Authentication을 제공하는 인증제공자는 여러개가 동시에 존재할 수 있고
* 인증 방식에따라 ProviderManager도 복수로 존재할 수 있음
* Authentication은 인터페이스로 아래와 같은 정보를 들고있음
* Set<GrantedAuthority> authorities: 인증된 권한 정보
* principal: 인증 대상에 관한 정보로 UserDetails 타입이 옴
* credentials: 인증 확인을 위한 정보 주로 비번이 오지만 인증 후에는 보안을 위해 삭제함
* details: 그 밖에 정보 IP, 세션정보, 기타 인증 요청에서 사용했던 정보들
* UserDetails와는 다른 정보를 가짐
* boolean authenticated: 인증이 되었는지를 체크함
*
* 필터들 중 일부 필터는 인증 정보에 관여함
* 필터가 하는 역할은 AuthenticationManager를 통해 Authentication을 인증하고
* 그 결과를 SecurityContextHolder에 넣어주는 일을 한다
* SecurityContextHolder는 인증 보관함 보관소이다
*
* AuthenticationManager가 인증관리인데
* 이것의 구현체 클래스가 ProviderManager이고 이 클래스는 여러개 사용이 가능하다
*
* 인증 토큰(Authentication)을 제공하는 필터들
* : Authentication은 인터페이스이고 이것의 구현체 클래스들이 접미어에 Token이라는 이름을 사용하고 있으니
* 인증 토큰이라고 해도 되지 않을까?
* 폼 로그인
* UsernamePasswordAuthenticationFilter -> UsernamePasswordAuthenticationToken
* AnonymousAuthenticationFilter : 로그인을 하지 않았다는 인증 -> AnonymousAuthenticitionToken
* SecurityContextPersistenceFilter : 기존 로그인을 유지함(기본적으로 session 이용)
* Oauth2LoginAuthenticationFilter : 소셜로그인 -> Oauth2LoginAuthenticationToken
*/
@Data
public class PrincipalDetails implements UserDetails, OAuth2User{ //오버라이드 해줌
Logger logger = LogManager.getLogger(PrincipalDetails.class);
private User user; //콤포지션
//구글 로그인시 구글서버에서 넣어주는 정보가 Map의 형태인데 그것을 받을 Map변수 선언하는 것임
private Map<String,Object> attributes;
//일반로그인시 사용하는 생성자
public PrincipalDetails(User user) {
this.user = user;
}
//OAuth로그인시 사용하는 생성자임
//그런데 어떻게 User정보를 갖게 되냐면 attributes를 통해서 User를 생성해 준다
// 2번째 파라미터 자리는 구글 인증 후 프로필 정보를 담음
public PrincipalDetails(User user, Map<String,Object> attributes) {
this.user = user;
this.attributes = attributes;
}
//해당 User의 권한을 리턴하는 곳
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collect = new ArrayList<>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collect;
}
//아래 정보들이 데이터베이스 쪽과 매칭이 안되면 loginFail.jsp페이지 호출됨
@Override
public String getPassword() {
// TODO Auto-generated method stub
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
//세션에 담을 다른 컬럼 정보도 추가 가능함
public int getId() {
return user.getId();
}
@Override
public boolean isAccountNonExpired() {
//계정이 파괴되지 않았니? 네
return true;
}
@Override
public boolean isAccountNonLocked() {
//계정이 잠겨 있는지 유무 체크함
return true;
}
@Override
public boolean isCredentialsNonExpired() {
//계정 사용 기간이 지났는지, 비번을 너무 오래 사용한거 아닌지 물어보는 것임
return true;
}
//네 계정이 활성화 되어 있니? 물어보는 거임
@Override
public boolean isEnabled() {
//그럼 이걸 언제 false하면 되는 건가? 만일 1년 동안 회원이 로그인을 안하면
//휴면 계정으로 처리하기
return true;
}
// 구글 로그인 요청 후 콜백URL을 통해서 개인 프로필 정보를 Map에 담아주는 메소드를 재정의했음
@Override
public Map<String, Object> getAttributes() {
logger.info("getAttributes");
return attributes;
}
// 개인 프로필 정보를 getAttributes에서 Map에 담아주니까 아래 메소드는 사용값이 없는 경우로
// 리턴을 null로 처리함
@Override
public String getName() {
logger.info("getName");
//return attributes.get("sub").toString();//이러면 되는데 사용하는 값이 아니므로 null로 함
return null;
}
}
<useEffect훅 활용하기 - TentMain.jsx>
import axios from 'axios'
import React, { useEffect, useState } from 'react'
const TentMain = () => {
const [message, setMessage] = useState(true)
const [tentList, setTentList] = useState([])
const [tent, setTent] = useState({
"id": 3,
"title": "텐트-3",
"content": "2-3인용 텐트",
"price": 30000,
"img": "tent1.jpg"
})
const getTentList = () => {
axios.get('/tent.json').then((res) => {
// console.log(res.data)
setTentList(res.data)
console.log(tentList)
})
}
console.log(tentList)
// useEffect는 컴포넌트의 라이프사이클을 지원하는 훅
// html이 렌더링이 다 된 다음에 내부 코드가 실행됨
// 시간이 오래걸리는 작업을 useEffect안에 넣어서 처리함
// 리액트버전 18에서는 automatic batching기능이 추가됨
// state변경함수를 쓸때마다 재렌더링이 안되게 추가됨
// 근처에 있는 state변경함수 까지 다 처리 후 마지막에 한번만 렌더링됨
useEffect(() => {
console.log('컴포넌트가 mount, update시 여기 코드 실행됨')
let timer = setTimeout(() => {setMessage(false)}, 2000)
console.log('cleanup function 후')
getTentList()
return () => { // 일명 cleanup function이라고 부름
// 기존 코드를 치우는 용도로 사용됨
// mount시에는 실행이 안됨, unmount시에 실행됨
console.log('cleanup function 전')
clearTimeout(timer)
}
}, [])
return (
<div>
{
message === true ?
<div>
2초이내 구매시 할인
<ul>
{tentList.map((tent, index) => (
<li key={index}>{`상품명: ${tent.title}, 가격: ${tent.price}`}</li>
))}
</ul>
<button>장바구니 담기</button>
</div>
: null
}
<hr />
이벤트 종료 후 <br />
{`상품명: ${tent.title}, 설명: ${tent['content']} 가격: ${tent.price}`}
</div>
)
}
export default TentMain
/*
정리
1. 재렌더링 될때마다 코드를 실행하고 싶으면
useEffect(() => {})
2. mount시 1회 코드 실행하고 싶으면
useEffect(() => {}, [])
3. unmount시 1회 코드 실행하고 싶으면
useEffect(() => {
return() => {
}
}, [])
*/
<useEffect훅 활용하기 - tent.json>
[
{
"id": 4,
"title": "텐트-4",
"content": "2-3인용 텐트",
"price": 20000,
"img": "tent1.jpg"
},
{
"id": 5,
"title": "텐트-5",
"content": "4~5인용 캠핑패키지",
"price": 50000,
"img": "tent2.jpg"
},
{
"id": 6,
"title": "텐트-6",
"content": "10인용 텐트",
"price": 120000,
"img": "tent3.jpg"
}
]
<depth가 깊은 데이터 꺼내기 - FoodMain.jsx>
import axios from 'axios'
import React, { useEffect, useState } from 'react'
const FoodMain = () => {
const [foodList, setFoodList] = useState([])
useEffect(() => {
axios.get('food.json').then(res => {
console.log(res.data)
const imsi = JSON.stringify(res.data)
const jsonDoc = JSON.parse(imsi)
// console.log(jsonDoc['mealServiceDietInfo'][0])
console.log(jsonDoc.mealServiceDietInfo[0])
console.log(jsonDoc.mealServiceDietInfo[0].head)
console.log(jsonDoc.mealServiceDietInfo[0].head[0])
console.log(jsonDoc.mealServiceDietInfo[0].head[0].list_total_count)
console.log(jsonDoc.mealServiceDietInfo[0].head[1])
console.log(jsonDoc['mealServiceDietInfo'][1])
console.log(jsonDoc['mealServiceDietInfo'][1].row)
setFoodList(jsonDoc['mealServiceDietInfo'][1].row)
})
}, [])
return (
<div>
<ul>
{foodList.map((item,index) => (
<li key={index}>{`${item.SCHUL_NM} => ${item.DDISH_NM} => ${item.NTR_INFO}`}</li>
))
}
</ul>
</div>
)
}
export default FoodMain
<depth가 깊은 데이터 꺼내기 - food.json>
{
"mealServiceDietInfo": [
{
"head": [
{
"list_total_count": 863
},
{
"RESULT": {
"CODE": "INFO-000",
"MESSAGE": "정상 처리되었습니다."
}
}
]
},
{
"row": [
{
"ATPT_OFCDC_SC_CODE": "J10",
"ATPT_OFCDC_SC_NM": "경기도교육청",
"SD_SCHUL_CODE": "7530620",
"SCHUL_NM": "효양고등학교",
"MMEAL_SC_CODE": "1",
"MMEAL_SC_NM": "조식",
"MLSV_YMD": "20210310",
"MLSV_FGR": "56",
"DDISH_NM": "흑미밥(H)<br/>설렁탕1.5.6.13.16.18.<br/>메추리알장조림1.5.6.13.18.<br/>시금치무침5.6.13.18.<br/>배추김치9.13.<br/>딸기샤벳2.13.",
"ORPLC_INFO": "쌀 : 국내산<br/>김치류 : 국내산<br/>고춧가루(김치류) : 국내산<br/>쇠고기(종류) : 국내산(한우)<br/>돼지고기 : 국내산<br/>닭고기 : 국내산<br/>오리고기 : 국내산<br/>쇠고기 식육가공품 : 국내산<br/>돼지고기 식육가공품 : 국내산<br/>닭고기 식육가공품 : 국내산<br/>오리고기 가공품 : 국내산<br/>낙지 : 국내산<br/>고등어 : 국내산<br/>갈치 : 국내산<br/>오징어 : 국내산<br/>꽃게 : 국내산<br/>참조기 : 국내산<br/>콩 : 국내산",
"CAL_INFO": "1065.2 Kcal",
"NTR_INFO": "탄수화물(g) : 140.9<br/>단백질(g) : 52.1<br/>지방(g) : 31.8<br/>비타민A(R.E) : 705.2<br/>티아민(mg) : 0.5<br/>리보플라빈(mg) : 1.0<br/>비타민C(mg) : 89.3<br/>칼슘(mg) : 380.4<br/>철분(mg) : 10.5",
"MLSV_FROM_YMD": "20210310",
"MLSV_TO_YMD": "20210310"
},
{
"ATPT_OFCDC_SC_CODE": "J10",
"ATPT_OFCDC_SC_NM": "경기도교육청",
"SD_SCHUL_CODE": "7530620",
"SCHUL_NM": "효양고등학교",
"MMEAL_SC_CODE": "1",
"MMEAL_SC_NM": "조식",
"MLSV_YMD": "20210311",
"MLSV_FGR": "91",
"DDISH_NM": "쌀밥(H)<br/>돈개장1.5.6.9.10.13.15.16.17.18.<br/>오리불고기2.5.6.8.12.13.15.18.10.<br/>참나물무침5.6.13.18.<br/>배추김치9.13.<br/>뽀로로요구르트2.5.13.",
"ORPLC_INFO": "쌀 : 국내산<br/>김치류 : 국내산<br/>고춧가루(김치류) : 국내산<br/>쇠고기(종류) : 국내산(한우)<br/>돼지고기 : 국내산<br/>닭고기 : 국내산<br/>오리고기 : 국내산<br/>쇠고기 식육가공품 : 국내산<br/>돼지고기 식육가공품 : 국내산<br/>닭고기 식육가공품 : 국내산<br/>오리고기 가공품 : 국내산<br/>낙지 : 국내산<br/>고등어 : 국내산<br/>갈치 : 국내산<br/>오징어 : 국내산<br/>꽃게 : 국내산<br/>참조기 : 국내산<br/>콩 : 국내산",
"CAL_INFO": "714.2 Kcal",
"NTR_INFO": "탄수화물(g) : 101.2<br/>단백질(g) : 35.4<br/>지방(g) : 18.5<br/>비타민A(R.E) : 162.8<br/>티아민(mg) : 0.8<br/>리보플라빈(mg) : 0.5<br/>비타민C(mg) : 19.2<br/>칼슘(mg) : 152.7<br/>철분(mg) : 5.1",
"MLSV_FROM_YMD": "20210311",
"MLSV_TO_YMD": "20210311"
},
{
"ATPT_OFCDC_SC_CODE": "J10",
"ATPT_OFCDC_SC_NM": "경기도교육청",
"SD_SCHUL_CODE": "7530620",
"SCHUL_NM": "효양고등학교",
"MMEAL_SC_CODE": "1",
"MMEAL_SC_NM": "조식",
"MLSV_YMD": "20210312",
"MLSV_FGR": "91",
"DDISH_NM": "야채소라죽13.18.<br/>아몬드후레이크&우유1.2.5.6.13.<br/>열무무침5.6.13.18.<br/>닭꼬치1.2.5.6.12.13.15.18.<br/>나박김치9.13.",
"ORPLC_INFO": "쌀 : 국내산<br/>김치류 : 국내산<br/>고춧가루(김치류) : 국내산<br/>쇠고기(종류) : 국내산(한우)<br/>돼지고기 : 국내산<br/>닭고기 : 국내산<br/>오리고기 : 국내산<br/>쇠고기 식육가공품 : 국내산<br/>돼지고기 식육가공품 : 국내산<br/>닭고기 식육가공품 : 국내산<br/>오리고기 가공품 : 국내산<br/>낙지 : 국내산<br/>고등어 : 국내산<br/>갈치 : 국내산<br/>오징어 : 국내산<br/>꽃게 : 국내산<br/>참조기 : 국내산<br/>콩 : 국내산",
"CAL_INFO": "515.1 Kcal",
"NTR_INFO": "탄수화물(g) : 77.2<br/>단백질(g) : 27.2<br/>지방(g) : 13.9<br/>비타민A(R.E) : 655.4<br/>티아민(mg) : 0.8<br/>리보플라빈(mg) : 2.9<br/>비타민C(mg) : 75.1<br/>칼슘(mg) : 272.5<br/>철분(mg) : 7.7",
"MLSV_FROM_YMD": "20210312",
"MLSV_TO_YMD": "20210312"
}
]
}
]
}
<쿠키실습 - index.html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://code.jquery.com/jquery-3.6.4.js" integrity="sha256-a9jBBRygX1Bh5lt8GZjXDzyOB+bWve9EiO7tROUtj/E=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js" integrity="sha512-3j3VU6WC5rPQB4Ld1jnLV7Kd5xr+cq9avvhwqzbH/taCRNURoeEpoPBK9pDyeukwSxwRPJ8fDgvYXd6SkaZ2TA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<title>쿠키실습 - </title>
<script>
const del = () => {
// alert("삭제")
let res = $.removeCookie('cmem_name', {path: '/cookie'})
console.log(res)
let res2 = $.removeCookie('cmem_id', {path: '/'})
console.log(res2)
}
</script>
</head>
<body>
쿠키실습
<hr />
<!--
쿠키 쓰기
$.cookie('키', '값', {expires: 7, path:'/'}) 유효기간과 도메인 -> 패스까지 일치해야 저장됨
쿠키 읽기
$.cookie('키')
쿠키 삭제
$.removeCookie('키', {path: '/'}) 도메인 -> 패스까지 일치해야 삭제됨
-->
<script>
$.cookie('cmem_name', '이순신', {expires: 7, path: '/cookie'}) // 한글이면 글자 깨짐
$.cookie('cmem_id', 'tomato', {expires: 10, path: '/'})
console.log($.cookie('cmem_name'))
console.log($.cookie('cmem_id'))
</script>
<input type="button" onclick="del()" value="쿠키삭제" />
<!-- 주의: button태그 사용시 submit이슈가 있으니 주의할 것! 새로고침이 일어남 -->
<!--
expires: -1 이나 0을 줘도 삭제가 됨 / 1을주면 하루(24시간) - 시간, 초단위는 max-age(표준)지원
-->
</body>
</html>
'국비학원 > 수업기록' 카테고리의 다른 글
국비 지원 개발자 과정_Day97 (0) | 2023.04.18 |
---|---|
국비 지원 개발자 과정_Day96 (0) | 2023.04.17 |
국비 지원 개발자 과정_Day94 (0) | 2023.04.13 |
국비 지원 개발자 과정_Day93 (0) | 2023.04.12 |
국비 지원 개발자 과정_Day92 (0) | 2023.04.11 |
댓글