공부/그룹 스터디

[4회차 01] 인증메일 구조 만들기 - 인증번호 만들어 DB에 넣기

junani0v0 2024. 4. 23. 22:46

수업내용

  • 회원가입하면 이메일 보내기
  • 이메일 인증하기

이메일 인증

  • 이메일 인증 장점
    • 인증을 하지 않으면 메인화면 접속을 막을 수 있다
  • 인증코드는 유니크 해야한다
    • 메일발송에 링크는 난수여야한다
    • 겹치면 A라는 사용자 인증해야하는데 B사용자가 인증이 될 수도 있다
    • 링크 클릭시 이게 A사용자인지 어떻게 아는가?
    • 링크와 사용자를 엮어줘야한다
  • 링크에 유효기간을 설정
    • 링크를 보냈지만 안누를 경우를 대비해 링크에 유효기간을 설정
    • 이 유효시간은 어떻게 관리할 것인가?
    • 사용자가 이 링크를 눌렀을때 유효한지 어떻게 체크할 것인가?

Table (member_auth)

  • auth_seq : 인증 시퀀스 올라가는 곳
  • member_seq : 회원 번호
  • auth_num : 인증 문자
  • auth_uri : 인증용 난수
  • reg_dtm : 등록 일자
  • expire_dtm : 인증완료 일자
  • auth_yn : 인증 여부

인증 링크는 언제 만들어야 하나?

  • 회원가입 후 생성(member table에 등록이 되면)

전에 강의 수정사항

JoinDao -> MemberDao

Dao는 Table기준으로 이름을 만들기에 수정함

JoinDao 을 드레그 후 Alt + Shift + r 을 누르고 변경하면 JoinDao와 관련된 곳이 전부 바뀝니다

context-root에 bean으로 등록한 joinDao와 JoinDao는 자동으로 바뀌지 않기에 수동으로 바꾸어야합니다.

JoinService에 setter로 설정한 setJoinDao 수동으로 바꿔줍니다.

인증메일 구조 만들기

  • 기대값이 1일때 인증메일 발송(JoinService)
public int join(HashMap<String, String> params) {
		String passwd = params.get("passwd");
		String encPasswd = BCrypt.withDefaults().hashToString(12, passwd.toCharArray());
		System.out.println("encPasswd >>>>>>>>> " + encPasswd);
		BCrypt.Result result = BCrypt.verifyer().verify(passwd.toCharArray(), encPasswd);
		System.out.println("result.verified >>>>>>> " + result.verified);
		
		params.put("passwd", encPasswd);
		
		int cnt = memberDao.join(params);
		if (cnt == 1) {
		// 인증 메일 구조 만들기
		// 1일때 인증메일 발송
			
		}
	return cnt;
}
  • dto 만들기
    • dto패키지와 MemberAuthDto 생성

MemberAuthDto

DB테이블 에서 컬럼 전부 드레그 복사해서 붙여 넣은 후

)

카멜형으로 바꿔주고 앞에 private와 타입 작성

)

Getter & Setter 생성

인증코드(auth_uri) 난수 만들기

JoinService

image-4

UUID.randomUUID() 

UUID를 활용하여 난수 생성

UUID.randomUUID().toString().replaceAll("-", "")

UUID에 "-"를 빼기
그러고 authUri에 set해줍니다

만료일시(expire_dtm)

인증코드 시작일시(reg_dtm)는 java가 아닌 쿼리에서 넣어줍니다
그러므로 만료일시(expire_dtm)도 쿼리에서 생성해주겠습니다

SELECT DATE_FORMAT(DATE_ADD(NOW(), INTERVAL 30 MINUTE),'%Y%m%d%H%i%s'); 

시간을 지정하여 현재시간의 30분뒤까지의 시간으로 설정해 주었습니다

image-7

MemberAuthDao 생성

이제 만들어준 값들을 DB에 넣어보겠습니다
com.portfolio.www.dao에 MemberAuthDao.java 파일을 생성해줍니다 (Dao는 Table명과 맞추어줍니다)

public class MemberAuthDao extends JdbcTemplate{

}

JdbcTemplate을 상속받습니다

    public int addAuthInfo(MemberAuthDto dto) {
        String sql =""

        return 0;
    }

값을 넣어주는 메서드 addAuthInfo를 만들고 파라미터는 전에 만들어 놓은 MemberAuthDto를 사용합니다

image-8

member_auth에 값을 넣어주기 위해 DB로가서
member_auth 우클릭 -> SQL 생성 -> INSERT 클릭

image-9

생성된 INSERT SQL문 Copy클릭 -> MemberAuthDao sql에 붙이기

public int addAuthInfo(MemberAuthDto dto) {

    String sql ="INSERT INTO forum.member_auth "
            + "(member_seq, auth_num, auth_uri, reg_dtm, expire_dtm, auth_yn) "
            + "VALUES(" + dto.getMemberSeq()
            + ", '', '" + dto.getAuthUri()
            + "', DATE_FORMAT(NOW()  ,'%Y%m%d%H%i%s'), "
            + "DATE_FORMAT(DATE_ADD(NOW(), INTERVAL 30 MINUTE),'%Y%m%d%H%i%s'), 'N'); ";

    return update(sql);
}

복사한 sql문에 \r\n 삭제 & sql문 끝" "안에 띄어쓰기 한칸씩 넣어 에러를 방지합니다

auth_seq도 자동으로 생성되기에 value값과 함께 삭제
넣을 ''자리에 enter 후 전에 만든 값들을 넣어줍니다

  • dto.getMemberSeq()
  • dto.getAuthUri()
  • DATE_FORMAT(DATE_ADD(NOW(), INTERVAL 30 MINUTE),'%Y%m%d%H%i%s')
    • <주의> ' ' 을 지워줘야합니다
  • dto.getExpireDtm()
  • return은 update(sql)로 해줍니다
    • 반환값을 int로 한 이유는 update 메서드의 리턴값이 int기 때문입니다 (영향받은 row의 개수 리턴)

< 주의 >

int cnt = memberDao.join(params);

JoinService에 memberDao에 join을 넣어주는데
넣은 친구의 seq가 필요한데
반환값 int라 알수가 없어 한번더 select가 필요하며
member_id가 유니크하니까 member_id로 select해옵니다

MemberDao

MemberDao에 위에 말한 select 쿼리문을 만들겠습니다

    public int getMemberSeq(String memberId) {
        String sql = "SELECT member_seq FROM member WHERE member_id='" +memberId + "'";
        return queryForObject(sql, Integer.class);
    }

select 쿼리를 실행할때는 query로 시작하는 메서드를 사용하면 이번에는 queryForObject를 사용하겠습니다
queryForObject을 보면 이름은 같은데 매개변수가 다른게 많이 나오는걸 볼 수 있으며 이걸 "오버로딩" 이라고 합니다

  • 오버로딩 : 처음에 1개 만들었다가 사용할 수 있는 케이스가 많아진 경우 오버로딩 사용(케이스를 다 살리기 위해)
  • 오버라이딩 : 케이스 1개 구현 마음대로(오버로딩이 좀더 자유로움)

queryForObject중에서 queryForObject(String sql, Class requiredType): T 를 사용하겠습니다

  • queryForObject : 하나의 로우 또는 하나의 싱글 컬럼이 기대가 된다
    return타입은 뒤에 적어준 Object 타입에 즉각 맵핑을 한다

JoinService

이제 MemberDao에 만든 getMemberSeq를 호출해 줍니다

int cnt = memberDao.join(params);
int memberSeq = memberDao.getMemberSeq(params.get("memberId"));
    if (cnt == 1) {
        MemberAuthDto authDto = new MemberAuthDto();
        authDto.setMemberSeq(memberSeq);
        authDto.setAuthUri(UUID.randomUUID().toString().replaceAll("-", ""));
    }

JoinService의 join매서드 안에 memberSeq를 추가해주고 만든 이 memberSeq를 setMemberSeq에 넣어줍니다

context-root.xml

<bean id="joinService"
    class="com.portfolio.www.service.JoinService">
    <property name="memberDao" ref="memberDao" />
    <property name="memberAuthDao" ref="memberAuthDao" />
</bean>

<bean id="memberDao" class="com.portfolio.www.dao.MemberDao">
    <constructor-arg ref="dataSource" />
</bean>

<bean id="memberAuthDao" class="com.portfolio.www.dao.MemberAuthDao">
    <constructor-arg ref="dataSource" />
</bean>
  • MemberAuthDao를 수동 bean등록
  • joinService에 DI 수동으로 넣기

MemberAuthDao

public class MemberAuthDao extends JdbcTemplate{
    public MemberAuthDao() {}    //기본생성자
    public MemberAuthDao(DataSource dataSource) { //데이터소스받는 생성자
        super(dataSource);
        System.out.println("-----------------------"+dataSource);    //주소값 나오나 확인
    }

JoinService

public class JoinService {
    private MemberDao memberDao;
    private MemberAuthDao authDao;

    public void setMemberDao(MemberDao memberDao) {
        System.out.println("------------ " + memberDao + " in joinService");
        this.memberDao = memberDao;
    }

    public void setMemberAuthDao(MemberAuthDao authDao) {
        System.out.println("------------ " + authDao + " in joinService");
        this.authDao = authDao;
    }

< 주의 >

context-root.xml property name하고 JoinService에서 MemberAuthDao 이름을 authDao로 서로 다르게하면 어떻게 될까요??

<bean id="joinService"
    class="com.portfolio.www.service.JoinService">
    <property name="memberDao" ref="memberDao" />
    <property name="memberAuthDao" ref="memberAuthDao" />
</bean>
public class JoinService {
    private MemberAuthDao authDao;

    public void setMemberAuthDao(MemberAuthDao authDao) {
    System.out.println("------------ " + authDao + " in joinService");
    this.authDao = authDao;
    }

맨처음 property name에 해당하는 변수를 찾는데 같은 이름은 없고
setter인 setMemberAuthDao이 있을 경우
코드상으론 에러가 발생하지 않습니다

하지만 spring은 Service에 변수이름(property name)으로 찾고 이게 있어야 setter를 만듦니다
그렇기에 setter가 있다고 값이 들어가지는 않습니다
그렇기에 변수명을 맞춰 수정해줍니다

JoinService

public class JoinService {
    private MemberDao memberDao;
    private MemberAuthDao memberAuthDao;

    public void setMemberDao(MemberDao memberDao) {
        System.out.println("------------ " + memberDao + " in joinService");
        this.memberDao = memberDao;
    }

    public void setMemberAuthDao(MemberAuthDao memberAuthDao) {
        System.out.println("------------ " + memberAuthDao + " in joinService");
        this.memberAuthDao = memberAuthDao;
    }

수동으로 할때는 이렇게 변수명을 맞춰줘야하는 규약이 있습니다
JoinService에 memberAuthDao를 가지고 왔으니 이제 memberAuthDao를 추가할 수 있습니다

public class JoinService {
    private MemberDao memberDao;
    private MemberAuthDao memberAuthDao;

    public void setMemberDao(MemberDao memberDao) {
        System.out.println("------------ " + memberDao + " in joinService");
        this.memberDao = memberDao;
    }

    public void setMemberAuthDao(MemberAuthDao memberAuthDao) {
        System.out.println("------------ " + memberAuthDao + " in joinService");
        this.memberAuthDao = memberAuthDao;
    }

    public int join(HashMap<String, String> params) {
        String passwd = params.get("passwd");

        String encPasswd = BCrypt.withDefaults().hashToString(12, passwd.toCharArray());
        System.out.println("encPasswd >>>>>>>>> " + encPasswd);

        BCrypt.Result result = BCrypt.verifyer().verify(passwd.toCharArray(), encPasswd);
        System.out.println("result.verified >>>>>>> " + result.verified);

        params.put("passwd", encPasswd);

        int cnt = memberDao.join(params);
        int memberSeq = memberDao.getMemberSeq(params.get("memberId"));
        if (cnt == 1) {
            // 인증 메일 구조 만들기
            MemberAuthDto authDto = new MemberAuthDto();
            authDto.setMemberSeq(memberSeq);
            authDto.setAuthUri(UUID.randomUUID().toString().replaceAll("-", ""));

            memberAuthDao.addAuthInfo(authDto);

            // 인증 메일 발송
        }
        return cnt;
    }
}

이제 서버를 시작해 member_auth에 INSERT가 되는지 확인합니다

image-10image-11

회원가입 페이지로가서 값을 넣어 가입 후
(아이디가 같으면 에러나니 주의)

image-12

member_auth에 값이 잘 들어갔는지 확인합니다

아이디 중복을 방지하기위해 DB 설정 변경

image-13 0

member -> Properties -> Constraints -> 우클릭 -> Create New Constraint

image-13

Type : UNIQUE KEY -> member_id 체크 확인

< 주의 >

Table에 값이 있으면 에러가 발생하니 테이블안에 내용 삭제
member_auth는 member를 참조하기에 member보다 먼저 삭제

image-14