왕초보일지

240125 TIL |

다시은 2024. 1. 25. 22:05

스프링에서 구글 이메일 메일 발송하기

 

의존성

	implementation("org.springframework.boot:spring-boot-starter-mail:3.0.5")

 

application.yml

spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: 
    password: 
    properties:
      mail:
        smtp:
          auth: true
          starttls:

환경변수로 메일과 비밀번호 설정

 

메일 설정 (안해줘도됨)

더보기
@Configuration
@PropertySource("classpath:application.yml")
class EmailConfig {
    @Value("\${spring.mail.host}")
    private val host: String? = null

    @Value("\${spring.mail.port}")
    private val port = 0

    @Value("\${spring.mail.username}")
    private val username: String? = null

    @Value("\${spring.mail.password}")
    private val password: String? = null

    @Value("\${spring.mail.properties.mail.smtp.auth}")
    private val auth = false

    @Value("\${spring.mail.properties.mail.smtp.starttls.enable}")
    private val starttlsEnable = false




    @Bean
    fun javaMailSender(): JavaMailSender {
        val mailSender = JavaMailSenderImpl()
        mailSender.host = host
        mailSender.port = port
        mailSender.username = username
        mailSender.password = password
        mailSender.defaultEncoding = "UTF-8"
        mailSender.javaMailProperties = mailProperties

        return mailSender
    }

    private val mailProperties: Properties
        get() {
            val properties = Properties()
            properties["mail.smtp.auth"] = auth
            properties["mail.smtp.starttls.enable"] = starttlsEnable

            return properties
        }
}

 

메일 서비스

더보기
@Service
class MailService(
    private val javaMailSender: JavaMailSender,
//    private val authCodeRepository: AuthCodeRepository
) {

    @Transactional
    fun sendMail(email: String): String {
        val mail = createMail(email)
        javaMailSender.send(mail)
        return "입력된 이메일로 인증코드가 발송되었습니다."
    }

    fun createMail(email: String): SimpleMailMessage {
        val mail = SimpleMailMessage()
        val code = createAuthCode()
        mail.setTo(email)
        mail.setSubject("인증요청")
        mail.setText("인증 코드: $code")
//        authCodeRepository.save(AuthCodeEntity(email = email, code = code))
        return mail

    }

    fun createAuthCode(): String {
        return RandomStringUtils.random(5, true, true)
    }

//    fun checkAuthCode(email: String, code: String): String {
//        val authCode = authCodeRepository.findByEmail(email) ?: throw RuntimeException("가입된 이메일이 아닙니다.")
//        if(authCode.code != code) throw RuntimeException("인증 코드를 다시 입력하십시오.")
//        return "인증되었습니다."
//
//    }
}

엔티티

@Entity
@Table(name = "auth_code")
class AuthCodeEntity(
    @Column(name = "member_id")
    val email: String,

    @Column(name = "code")
    val code: String
) {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null
    }

레포지토리

@Repository
interface AuthCodeRepository: JpaRepository<AuthCodeEntity, Long> {
    fun findByEmail(email: String): AuthCodeEntity
}

 

 

 

의존성  추가하니 자동으로  javamailsender 호출이 됐고

이메일 수신지, 제목, 내용 정도만 설정해도 잘 실행되었다.

멤버 엔티티에 이메일 인증여부에 따른 컬럼도 생성하고

로그인 시 그 컬럼 값에 따라 로그인 성공 여부 결정나게 할 수도 있다.!

에러메시지가 로그가 아닌 화면상에 보여지도록 해야하는데 시큐리티 진짜 헷갈린다 🤯🤯

 

 


access token 과 refresh token

 

로그인을 하면 발급되는 access토큰으로 내가 만들어놓은 api를 실행할 수 있었다.

이 통신과정에서 토큰은 쉽게 탈취당할 수 있다.

(세션과 다르게 토큰은 서버가 상태를 보관하고 있지 않다 == 서버는 발급한 토큰에 대한 제어권을 가지고 있지 않다.)

access 토큰이 만료되기 전까지 획득한 사람은 누구나 해당하는 권한에 접근이 가능하다.

토큰에 유효기간을 부여함으로써 보안을 강화할 수 있지만 이는 결국 사용자가 잦은 로그인을 해야 한다는 문제가 있다.

 

=> 로그인했을 때 유효기간이 짧은 access 토큰과 긴 refresh 토큰 두 개를 발급하고 서버는 db에 refresh 토큰을 저장한다.

access 토큰으로 api 통신을 하는데

access 토큰이 만료됐을 때 사용자는 권한이 없는 사용자가 되어 401 에러코드를 받는다.

이 때 access 토큰 대신 refresh 토큰으로 다시 api 요청을 한다.

refresh 토큰의 정보로 사용자를 확인한 서버는 새로운 access 토큰을 발급한다.

refresh 토큰도 만료되었을 때 사용자는 다시 로그인을 하여야 한다.

access 토큰은 유효한데 refresh 토큰은 만료되었다면 access 토큰을 통해 refresh 토큰을 재발급할 수도 있다.

access 와 마찬가지로 사용자가 로그아웃을 하면 refresh 토큰도 삭제한다.

 

=> 자주 재발급하도록 만들어 보안을 강화하고 잦은 로그아웃 경험을 주지 않도록

 

access 토큰을 탈취한 사람이 만료시간을 변경할 순 없나?

토큰은 별도로 지정한 secret key 를 통해 암호화해서 만든다.

탈취한 사람이 payload 의 만료시간을 변경한다고 해도

토큰을 받은 서버가 signature 에서 파싱한 payload 와 다르다는 것을 알 수 있다.

그래서 secret key 보안이 중요한 것

 

운 좋게 refresh 토큰으로 통신할 때 refresh 토큰을 탈취해서 오랜기간 사용할 수 있게 된다면?

=> access 가 만료되어 재발급할 때 refresh 도 재발급하는 방법 +  refresh 토큰의 한 번만 사용할 수 있게 사용횟수를 지정해놓는 방법

 

 


 

함수를 따로 파일에 빼서 선언하면 클래스 주입없이 바로 호출할 수 있다!