마이페이지 - 비밀번호 변경하기

변현섭·2023년 9월 2일
0
post-thumbnail

닉네임 변경에 이어 비밀번호 변경 기능도 추가해보도록 하겠습니다.

3. 비밀번호 변경하기

1) 백엔드

① user > dto 패키지 하위로 PatchPasswordReq를 추가한다.

@Getter 
@Setter 
@AllArgsConstructor 
@NoArgsConstructor
public class PatchPasswordReq {
    private String exPassword; // 이전 비밀번호
    private String newPassword; // 새 비밀번호
    private String newPasswordChk; // 새 비밀번호 확인
}

② UserController에 아래의 API를 추가한다.

	/**
     * 유저 비밀번호 변경
     */
    @PatchMapping("/password")
    public BaseResponse<String> modifyPassword(@RequestBody PatchPasswordReq patchPasswordReq) {
        try {
            Long userId = jwtService.getUserIdx();
            return new BaseResponse<>(userService.modifyPassword(userId, patchPasswordReq));
        } catch (BaseException exception) {
            return new BaseResponse<>(exception.getStatus());
        }
    }

③ UserService에 아래의 메서드를 추가한다.

    /**
     *  유저 비밀번호 변경
     */
    @Transactional
    public String modifyPassword(Long userId, PatchPasswordReq patchPasswordReq) throws BaseException {
        try {
            User user = utilService.findByUserIdWithValidation(userId);
            String password;
            try {
                password = new AES128(Secret.USER_INFO_PASSWORD_KEY).decrypt(user.getPassword());
            } catch (Exception ignored) {
                throw new BaseException(BaseResponseStatus.PASSWORD_DECRYPTION_ERROR);
            }
            // 이전 비밀번호가 일치하지 않는 경우
            if (!patchPasswordReq.getExPassword().equals(password)) {
                throw new BaseException(BaseResponseStatus.EX_PASSWORD_MISSMATCH);
            }
            // 이전 비밀번호와 새 비밀번호가 일치하는 경우
            if(patchPasswordReq.getNewPassword().equals(patchPasswordReq.getExPassword())) {
                throw new BaseException(BaseResponseStatus.CANNOT_UPDATE_PASSWORD);
            }
            // 새 비밀번호와 새 비밀번호 확인이 일치하지 않는 경우
            if(!patchPasswordReq.getNewPassword().equals(patchPasswordReq.getNewPasswordChk())) {
                throw new BaseException(BaseResponseStatus.PASSWORD_MISSMATCH);
            }

            String pwd;
            try{
                pwd = new AES128(Secret.USER_INFO_PASSWORD_KEY).encrypt(patchPasswordReq.getNewPassword()); // 암호화코드
            }
            catch (Exception ignored) { // 암호화가 실패하였을 경우 에러 발생
                throw new BaseException(BaseResponseStatus.PASSWORD_ENCRYPTION_ERROR);
            }
            user.setPassword(pwd);
            return "비밀번호 변경이 완료되었습니다.";
        } catch (BaseException exception) {
            throw new BaseException(exception.getStatus());
        }
    }

2) 프론트엔드

① mypage 패키지 하위로, PasswordActivity를 생성한다.

② activity_password.xml 파일에 아래의 내용을 입력한다.

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/main_border"
    android:orientation="vertical"
    tools:context=".mypage.PasswordActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:layout_marginBottom="50dp"
            android:src="@drawable/mypage"/>

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:counterMaxLength="12"
            app:counterEnabled="true"
            android:layout_marginHorizontal="40dp"
            android:layout_marginVertical="10dp">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/oldPassword"
                android:layout_width="match_parent"
                android:layout_height="70dp"
                android:background="@drawable/main_border"
                android:hint="현재 비밀번호"
                android:inputType="textPassword"
                android:padding="5dp"
                android:textColorHint="#808080"
                android:textSize="25sp" />

        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:counterMaxLength="12"
            app:counterEnabled="true"
            android:layout_marginHorizontal="40dp"
            android:layout_marginVertical="10dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/newPassword"
                android:layout_width="match_parent"
                android:layout_height="70dp"
                android:background="@drawable/main_border"
                android:hint="새 비밀번호"
                android:inputType="textPassword"
                android:padding="5dp"
                android:textColorHint="#808080"
                android:textSize="25sp" />

        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:counterMaxLength="12"
            app:counterEnabled="true"
            android:layout_marginHorizontal="40dp"
            android:layout_marginVertical="10dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/newPasswordChk"
                android:layout_width="match_parent"
                android:layout_height="70dp"
                android:background="@drawable/main_border"
                android:hint="새 비밀번호 확인"
                android:inputType="textPassword"
                android:padding="5dp"
                android:textColorHint="#808080"
                android:textSize="25sp" />

        </com.google.android.material.textfield.TextInputLayout>

        <Button
            android:id="@+id/newPasswordBtn"
            android:layout_width="match_parent"
            android:layout_height="70dp"
            android:layout_marginVertical="50dp"
            android:layout_marginHorizontal="40dp"
            android:background="@color/skyBlue"
            android:text="변경"
            android:textSize="35sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

    </LinearLayout>

</ScrollView>

③ 마이페이지에서 비밀번호 변경 버튼을 클릭했을 때, 비밀번호 변경 페이지로 전환되도록 MyPageFragment의 onCreateView에 아래의 내용을 추가한다.

val password = view.findViewById<Button>(R.id.passwordBtn)
password.setOnClickListener {
    val intent = Intent(requireActivity(), PasswordActivity::class.java)
    startActivity(intent)
}

④ api > dto 패키지 하위로 PatchPasswordReq data class를 추가한다.

data class PatchPasswordReq(
    @SerializedName("exPassword")
    val exPassword : String,

    @SerializedName("newPassword")
    val newPassword : String,

    @SerializedName("newPasswordChk")
    val newPasswordChk : String
)

⑤ MyPageApi 인터페이스에 아래의 API를 추가한다.

@PATCH("/users/password")
suspend fun modifyPassword(
    @Header("Authorization") accessToken : String,
    @Body patchPasswordReq : PatchPasswordReq
): BaseResponse<String>

⑥ PasswordActivity를 아래와 같이 수정한다.

class PasswordActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_password)

        val newPasswordBtn = findViewById<Button>(R.id.newPasswordBtn)
        newPasswordBtn.setOnClickListener {
            val oldPassword = findViewById<TextInputEditText>(R.id.oldPassword)
            val oldPasswordStr = oldPassword.text.toString()

            val newPassword = findViewById<TextInputEditText>(R.id.newPassword)
            val newPasswordStr = newPassword.text.toString()

            val newPasswordChk = findViewById<TextInputEditText>(R.id.newPasswordChk)
            val newPasswordChkStr = newPasswordChk.text.toString()

            if(oldPasswordStr.isEmpty()) {
                Toast.makeText(this, "현재 비밀번호를 입력해주세요", Toast.LENGTH_SHORT).show()
            }
            
            else if(newPasswordStr.isEmpty()) {
                Toast.makeText(this, "새 비밀번호를 입력해주세요", Toast.LENGTH_SHORT).show()
            }
            
            else if(newPasswordStr != newPasswordChkStr) {
                Toast.makeText(this, "비밀번호와 비밀번호 확인의 입력 값이 다릅니다.", Toast.LENGTH_SHORT).show()
            }
            
            else {
                val patchPasswordReq = PatchPasswordReq(oldPasswordStr, newPasswordStr, newPasswordChkStr)
                getAccessToken { accessToken ->
                    if (accessToken.isNotEmpty()) {
                        CoroutineScope(Dispatchers.IO).launch {
                            val response = modifyPassword(accessToken, patchPasswordReq)

                            if (response.isSuccess) {
                                withContext(Dispatchers.Main) {
                                    Toast.makeText(this@PasswordActivity, "비밀번호 변경이 완료되었습니다", Toast.LENGTH_SHORT).show()
                                    val intent = Intent(this@PasswordActivity, MainActivity::class.java)
                                    startActivity(intent)
                                }
                            }

                           else {
                                Log.d("PasswordActivity", "비밀번호 변경 실패")
                                val message = response.message
                                Log.d("PasswordActivity", message)
                                withContext(Dispatchers.Main) {
                                    Toast.makeText(this@PasswordActivity, message, Toast.LENGTH_SHORT).show()
                                }
                            }
                        }
                    } else {
                        Log.e("PasswordActivity", "Invalid Token")
                    }
                }

            }
        }
    }

    private suspend fun modifyPassword(accessToken : String, patchPasswordReq : PatchPasswordReq): BaseResponse<String> {
        return RetrofitInstance.myPageApi.modifyPassword(accessToken, patchPasswordReq)
    }

    private fun getAccessToken(callback: (String) -> Unit) {
        val postListener = object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                val data = dataSnapshot.getValue(com.chrome.chattingapp.authentication.UserInfo::class.java)
                val accessToken = data?.accessToken ?: ""
                callback(accessToken)
            }

            override fun onCancelled(databaseError: DatabaseError) {
                Log.w("PasswordActivity", "onCancelled", databaseError.toException())
            }
        }

        FirebaseRef.userInfo.child(FirebaseAuthUtils.getUid()).addListenerForSingleValueEvent(postListener)
    }
}

코드를 실행시켜보면, 현재 비밀번호와 새비밀번호를 입력했을 때 비밀번호 변경이 잘 이루어지는 것을 확인할 수 있을 것이다.

profile
Java Spring, Android Kotlin, Node.js, ML/DL 개발을 공부하는 인하대학교 정보통신공학과 학생입니다.

0개의 댓글