20220928 [Spring Boot, H2DB, MyBatis]

Yeoonnii·2022년 9월 28일
0

TIL

목록 보기
36/52
post-thumbnail

로그인시 DB에 토큰 저장

백엔드는 jwtfilter 에서 토큰 발행여부를 확인하고 토큰으로 사용자의 정보를 확인한다
발행된 토큰 정보를 DB에 보관하여 필요할때마다 꺼내써야한다
추후에 소셜로그인 사용시에도 일반 로그인과 동일하게 토큰발행 정보를 저장하여 보관해야 한다
사용자가 로그아웃시는 저장된 토큰은 삭제 처리 해준다

토큰 DB에 저장시
➡️ DB에 이미 userid가 존재하면 새로 발행된 token 정보와 발행시간을 갱신하여 update
DB에 이미 userid가 존재하지 않으면 useridtoken, 발행된 시간을 insert
token이 DB에 저장되는 과정이 REST에서 세션과 같다

H2DB

토큰 정보 보관용 테이블 생성

  • 사용자아이디 = 기본키
  • 토큰정보보관
  • 토큰발행일
**CREATE TABLE TOKENTBL(
    USERID VARCHAR2(30) CONSTRAINT PK_TOKEN_ID PRIMARY KEY,
    TOKEN VARCHAR2(200) NOT NULL,
    REGDATE TIMESTAMP DEFAULT CURRENT_DATE,
    CONSTRAINT FK_TOKENTBL_USERID FOREIGN KEY(USERID) REFERENCES MEMBERTBL(USERID)
);**

TokenDTO.java

package com.example.dto;

import java.util.Date;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
@NoArgsConstructor
public class TokenDTO {
    String userid;
    String token;
    Date regdate;
}

MemberRestController.java

로그인시 발행된 토큰 저장하기
➡️ 로그인시 DB에 토큰 저장하지 않았을땐 생성된 토큰을 map에 바로 넣어 리턴해줬었는데,
DB에 저장하여 사용하기 위해 DB에 먼저 저장하고 토큰을 리턴해준다
retMap.put("token",jwtUtil.generateToken(member.getUserid()));

로그인시 TokenMapper 호출해서 정보 담기
DB에서 발행된 토큰의 userid , token정보, token발행일을 확인할 수 있다

토큰 저장

	TokenDTO obj = new TokenDTO();
		obj.setUserid(member.getUserid());
		obj.setToken(token);

		int ret = tmapper.upsertToken(obj);

토큰 DB저장 후 토큰 리턴

if(ret ==1){
		// 토큰발행 성공한 경우
		retMap.put("token", token);
	}
    // 로그인하기
    //  127.0.0.1:8080/BOOT1/api/member/login.json
    // {"userid":"c1", "userpw":"c1", "role":"CUSTOMER"}
    @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);
            String token = jwtUtil.generateToken(member.getUserid());
            // TOKENTBL에 추가한 다음 map에 반환
            // token이 존재하지 않는경우 insert, token이 존재하는 경우 update

            TokenDTO obj = new TokenDTO();
            obj.setUserid(member.getUserid());
            obj.setToken(token);

            int ret = tmapper.upsertToken(obj);

            retMap.put("status", 200);
            if(ret ==1){
                // 토큰발행 성공한 경우
                retMap.put("token", token);
            }
        } catch (Exception e) {
            // 토큰발행 실패한 경우
            e.printStackTrace();
            retMap.put("status", -1);
        }
        return retMap;
    }

TokenMapper.java

@Mapper
public interface TokenMapper {

    // 토큰 정보를 추가 또는 수정하기
    public int upsertToken(TokenDTO obj);
}

tokenMapper.xml

USERID 조건이 있다면 UPDATE수행 ➡️ TOKEN과 REGDATE 정보 갱신
USERID 조건이 없다면 INSERT수행 ➡️ USERID 와 TOKEN, REGDATE정보 입력
MERGE INTO 구문 사용하여 SQL문 작성한다

<mapper namespace="com.example.mapper.TokenMapper">

    <!-- 토큰 정보를 추가 또는 수정하기 -->
    <insert id="upsertToken" parameterType="com.example.dto.TokenDTO">
        MERGE INTO
            TOKENTBL
        USING DUAL
            ON USERID=#{userid}
        WHEN MATCHED THEN
            UPDATE SET TOKEN=#{token}, REGDATE=CURRENT_DATE
        WHEN NOT MATCHED THEN
            INSERT (USERID, TOKEN, REGDATE) VALUES(#{userid}, #{token}, CURRENT_DATE )
    </insert>
</mapper>

TokenMapper.java

userid입력시 토큰정보 반환

// 토큰 정보 조회하기
    public TokenDTO selectTokenOne(String userid);

tokenMapper.xml

토큰정보 조회

<select id="selectTokenOne" parameterType="String" resultType="com.example.dto.TokenDTO">
        SELECT T.* FROM TOKENTBL T WHERE USERID=#{userid}
    </select>

JwtFilter.java

mapper에서 생성한 selectTokenOne을 이용하여 ➡️ 토큰 일치 여부 확인

// 토큰 호출시 DB에 존재하는지 여부 확인
@Autowired
    TokenMapper tmapper;
	// DB에 저장된 토큰과 현재 토큰이 같은 것인지 확인
            TokenDTO obj = tmapper.selectTokenOne(userid);
            // !=not 같지 않은경우
            if(!obj.getToken().equals(token)){
                throw new Exception(); //오류로 보내기
            }

회원정보수정시 일부수정

회원정보 수정시 업데이트 하지 않는 항목은 그대로 유지하기

MemberMapper.xml

조건 = 나이는 필수변경! 연락처/성별은 필수변경 사항이 아님
➡️ 1개 이상의 항목은 반드시 변경해주어야한다
1개도 변경 안하면 update 하는 의미가 없으니..!

UPDATE MEMBERTBL 
		SET
			AGE=#{age}, PHONE=#{phone}, GENDER=#{gender}
		WHERE
			USERID=#{userid}

위의 쿼리문은 update정보를 입력하지 않은 항목에 대해 기존정보를 null로 바꿔버린다
💡 if문 사용하여 update정보 미입력시 기존정보 유지하도록 해준다
쿼리문 작성시 if문을 사용하면 동적으로 DB변경 가능!
➡️ if문에 해당하면 쿼리문에 포함되고 해당하지 않으면 쿼리문에 포함되지 않는다

<update id="updateMember" parameterType="com.example.dto.MemberDTO">
          UPDATE MEMBERTBL SET 
               AGE=#{age} 
               <if test="phone != null">
                    , PHONE=#{phone}
               </if>
               <if test="gender != null">
                    , GENDER=#{gender}
               </if>     
          WHERE
               USERID=#{userid}
     </update>

📌 서비스시에는 userid 기준으로 잡고 나머지 변경된 항목만 저장되게 해도 된다!

<!-- 회원정보 수정하기 -->
    <update id="updateinfoMember" parameterType="com.example.dto.MemberDTO">
        UPDATE MEMBERTBL SET
            USERID=#{userid}
                <if test="age != null">
                    , AGE=#{age}
                </if>
                <if test="phone != null">
                    , PHONE=#{phone}
                </if>
                <if test="gender != null">
                    , GENDER=#{gender}
                </if>                
        WHERE
            USERID=#{userid}
    </update>

실제로 수행해보니 고정된 ID 정보 제외하고 사용자가 변경한 항목만 정보 수정이 가능하고,
변경되지 않은 나머지 항목들은 기존값을 유지하고 있다


map vs DTO

하이버네이트 = JPA에서는 DTO에 새 암호 변수를 생성하는 경우 DB에 컬럼이 하나 생기는 문제가 발행한다

반면에 마이바티스는 쿼리문에 의해 컬럼이 만들어진다

마이바티스에서 DTO = map으로 생각하면 된다
= 즉, 값을 보관하는 객체일 뿐, 아무 의미 없으며, DTO로 DB연동이 되지 않는다!

map보다 DTO사용하는 이유는?

map 사용시 장점

⇒ 변수 지정할 필요 없음! 새로 변수를 만들지 않아도 사용가능하다

map 사용시 단점

⇒ 사용시 형변환을 해야 함

DTO 사용시 장점

⇒ type이 정의되어 있어 편리함

DTO 사용시 단점

⇒ 필요시마다 DTO에 변수를 생성해야 함

lombok이 없었던 라떼는… getter/setter 지우며 썼다..
소스 코드의 혁신은 번거로움⇒짜증의 증가?.. 새로운 기능이 개발에서 생성됨

map vs DTO 결과는?

➡️ 상황에 따라 다르지만 보편적으로 DTO가 좋다!(권장)

소규모, 혼자한다면 map사용해도 무방하지만
대규모, 여러사람이 같이 작업 하는 경우 명확한 기준이 필요하기 때문에 DTO 사용을 권장한다


암호변경

MemberRestController.java

로직
1. token에서 가져온 userid넣어주기
2. 변경할 비밀번호 암호화 하여 member.setNewpw에 넣기
3. 기존 회원정보 조회하여 회원정보에서 DB에 저장된 암호 가져오기
4. 사용자가 view에 입력한 암호와 DB에 존재하는 해당 사용자의 암호가 일치하는지 비교
= bcpe.matches("rawPassword = 현재암호", "encodedPassword = DB암호")
5. 4.가 일치하는 경우 사용자가 입력한 변경할 암호를 기존userpw에 넣어 암호를 변경해준다

//  127.0.0.1:8080/BOOT1/api/member/updatepw.json
    // 암호변경 /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);
            BCryptPasswordEncoder bcpe = new BCryptPasswordEncoder();

            member.setUserid(userid); //token에서 가져온 userid넣어주기
            member.setNewpw(bcpe.encode(member.getNewpw())); //바꿀 비밀번호 암호화 하여 넣어주기
            System.out.println("===================member.toString()" + member.toString());
            
            // 기존 회원정보 조회 
            // = 로그인된 사용자의 토큰을 이용하여, 아이디를 가져온 후 정보조회 
            MemberDTO member1 = mmapper.selectMemberOne(userid);
            
            // 입력한 암호, hash된 DB암호 비교
            if(bcpe.matches( member.getUserpw() , member1.getUserpw() )){
                System.out.println("=======member.getUserpw()" + member.getUserpw());
                System.out.println("=======member1.getUserpw()" + member1.getUserpw());
                int ret = mmapper.updateMemberPw(member);

                // 암호가 변경된 경우
                retMap.put("result", ret);
            }
            // if문이 실행된 후 상태(status)값 전달
            retMap.put("status", 200);
            } catch (Exception e) {
                e.printStackTrace();
            retMap.put("status", -1);
            }
            return retMap;
        }

MemberMapper.java

public int updateMemberPw(MemberDTO member);

memberMapper.xml

변경될 암호 newpw 값을 USERPW에 넣어주어야 한다 ➡️ USERPW=#{newpw}

<update id="updateMemberPw" parameterType="com.example.dto.MemberDTO">
        UPDATE MEMBERTBL SET USERPW=#{newpw} WHERE USERID=#{userid}
    </update>

회원탈퇴

  • 다른 테이블에 외래키가 연결되어있어 회원을 완전히 삭제 할 수는 없다
    사용자에게 삭제한것처럼만 보이면 된다
    ➡️ 회원의 정보를 타입에 따라null 또는 0으로 바꿔준다
  • BLOCK값도 0으로 날려줘야 한다
    BLOCK값이 1인경우 = 차단안됨 / 0인경우 = 차단됨
  • 회원탈퇴 조건 ➡️ 회원탈퇴시 사용자가 암호를 입력한다고 가정한다

MemberRestController.java

//  127.0.0.1:8080/BOOT1/api/member/delete.json
    // 회원탈퇴 /delete.json
    // 암호 입력한다고 가정
    @PutMapping(value="/delete.json")
    public Map<String, Object> deletePUT(
        @RequestBody MemberDTO member,
        @RequestHeader(name = "TOKEN") String token) {
        Map<String, Object> retMap = new HashMap<>();
        try {
            String userid = jwtUtil.extractUsername(token);
            member.setUserid(userid);

            System.out.println("=====================");
            System.out.println(member.toString());

            // 아이디를 이용해서 현재 암호를 가져옴.
            MemberDTO member1 = mmapper.selectMemberOne(userid);

            // 입력한 암호, hash된 DB암호 비교
            if(bcpe.matches(member.getUserpw(), member1.getUserpw())){
                // MEMBERTBL에서 사용자 정보를 변경 = null,0 으로 변경 (update)
                int ret = mmapper.deleteMemberOne(member);
                // TOKENTBL에 userid에 해당하는 항목 삭제 (delete)
                int ret1 = tmapper.deleteToken(userid);
                
                retMap.put("result", ret);
            }
            retMap.put("status", 200);
        }
        catch(Exception e) {
            e.printStackTrace();
            retMap.put("status", -1);
        }
        return retMap;  
    }

MemberMapper.java

// 회원 탈퇴
    public int deleteMemberOne(MemberDTO member);

memberMapper.xml

사용자에게 삭제한것처럼만 보여야 하니
➡️ 회원의 정보를 null 또는 0으로 변경한다(BLOCK값도 0으로 변경)

<!-- 회원 탈퇴하기 -->

    <update id="deleteMemberOne" parameterType="com.example.dto.MemberDTO">
        UPDATE MEMBERTBL SET USERPW=null, AGE=0, PHONE=null,
        REGDATE=null, ROLE=null, GENDER=null, BLOCK=0
        WHERE USERID=#{userid}
    </update>

✔️ 결과
회원 탈퇴 전

회원 탈퇴 후

0개의 댓글