2023.11.11 - [공부해야할 것/java] - [SpringBoot] 웹개발(6) - 로그인 기능 만들기(1)
여기서 이어진다
|
이제 실제 join페이지에서 join을 하면 DB 안에 accountinfo 테이블에 입력되겠금 하는 기능을 만들것이다.
그렇다면 이기능을 만들라면 무엇을 해야할까 내가 당장 생각나는것은 크게 3가지이다.
- 회원정보를 담을 자바 클래스 만들어주기
- 실제 DB에 입력되겠금 SQL문 작성및 동작되겠금 로직개발
- 비밀번호 암호화
하나하나씩 해보자 우선 auth폴더 안에 DAO라는 폴더를 하나 추가해주자. 그안에 CustomUserDetail.java를 새로 생성해주자 그리고 다음과 같이 CutomerUserDetail.java를 작성해준다.
package com.kkkkim.forStudy.auth.dao;
import org.springframework.security.core.userdetails.UserDetails;
public class CustomUserDetail implements UserDetails {
}
UserDetails을 가져다가 쓰겠다라는 얘기이다. 그렇기에 당장 이렇게 쓰면 빨간줄이 뜨는걸 확인할 수 있을것이다.
UserDetails을 가져다 쓰는것이기때문에 UserDetail에 있는 모든 요소가 이 CustomUserDetail에 선언 되있어야한다.
UserDeatil.java를 보면 여러 메소드가 있음을 확인 할 수 있다. 해당부분을 확인하고 안에 있는 모든 메소드를 가져다 써줘야한다.
userDetail.java는 다음과 같이 구성되있다.
package org.springframework.security.core.userdetails;
import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
주석을 다 제거한 부분이다. 안에는 7개의 함수가 선언되있는걸 볼수 있는데 이를 다 넣어준다면 customUserDetail을 어떻게 지지고 볶든 상관 없다는 소리이다.
이전에 우리는 다음과 같은 테이블을 만들었다.
CREATE TABLE accountInfo(
acct_uid VARCHAR(36) DEFAULT UUID() PRIMARY KEY COMMENT '아이디 생성시 자동으로 생성되는 계정 유니크 아이디'
,acct_usr_id VARCHAR(50) NOT NULL UNIQUE KEY COMMENT '사용자 계정아이디 해당 아이디도 중복값이 없도록'
,acct_usr_pwd VARCHAR(255) NOT NULL COMMENT '사용자 비밀번호 암호화 되서 저장된다'
,acct_usr_email VARCHAR(255) NOT NULL COMMENT '사용자의 이메일 주소'
,acct_fail_cnt INT(2) NOT NULL DEFAULT 0 COMMENT '사용자가 정보를 잘못입력했을때 숫자가 늘어남'
,acct_lock_yn CHAR(2) NOT NULL DEFAULT 'N' COMMENT '사용자 계정 잠금 여부'
,acct_use_yn CHAR(2) NOT NULL DEFAULT 'Y' COMMENT '사용자 계정 사용여부'
,acct_reg_dt DATETIME NOT NULL DEFAULT NOW() COMMENT '계정 등록일시'
,acct_last_mod_dt DATETIME COMMENT '계정 마지막 수정일자'
) COMMENT='계정테이블';
출처: https://kkkkims.tistory.com/56 [낌X4:티스토리]
이를 기반으로 변수 설정을 해줄것이다. 자바에서는 변수선언할때 캐멀케이스(단어가 바뀔때 구분으로 대문자 사용)를 많이쓰지만 여기서는 편의를 위해 db컬럼명과 동일하게 변수를 컬럼을 선언해줄것이다.
그리고 편의를 위해 롬복을 이용하여 getter,setter가 자동으로 생성되겠금 할것이다.
DB에서 자동으로 생성되는 acct_uid를 제외하고 모두 getterSetter를 지정해준다.
package com.kkkkim.forStudy.auth.dao;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.userdetails.UserDetails;
import java.time.LocalDateTime;
public class CustomUserDetail implements UserDetails {
@Getter
private String acct_uid;
@Getter
@Setter
private String acct_usr_id;
@Getter
@Setter
private String acct_usr_pwd;
@Getter
@Setter
private String acct_usr_email;
@Getter
@Setter
private int acct_fail_cnt;
@Getter
@Setter
private String acct_lock_yn;
@Getter
@Setter
private String acct_use_yn;
@Getter
@Setter
private LocalDateTime acct_reg_dt;
@Getter
@Setter
private LocalDateTime acct_last_mod_dt;
}
이제 userDetail에 있는 메소드를 가져다가 정의할것이다.그전에 다소 의아한 구문이 있을것이다.
Collection<? extends GrantedAuthority> getAuthorities();
Collection<? extends GrantedAuthority>이게 뭐지 싶겠지만 당장은 권한 담는 리스트라 생각하고 GrantedAuthority에 대해 선언을 해주자. 아래 의 것을 추가해주자
@Getter
@Setter
private List<SimpleGrantedAuthority> authorities;
autorities에 getter setter를 붙여줌으로써 자동으로 getAuthorities가 생성이 된다.
이제 Userdetail에 있던 함수들을 옮겨와서 다음과 같이 써주자
package com.kkkkim.forStudy.auth.dao;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.time.LocalDateTime;
import java.util.List;
public class CustomUserDetail implements UserDetails {
@Getter
private String acct_uid;
@Getter
@Setter
private String acct_usr_id;
@Getter
@Setter
private String acct_usr_pwd;
@Getter
@Setter
private String acct_usr_email;
@Getter
@Setter
private int acct_fail_cnt;
@Getter
@Setter
private String acct_lock_yn;
@Getter
@Setter
private String acct_use_yn;
@Getter
@Setter
private LocalDateTime acct_reg_dt;
@Getter
@Setter
private LocalDateTime acct_last_mod_dt;
@Getter
@Setter
private List<SimpleGrantedAuthority> authorities;
/**
* 비밀번호 불러오기
**/
@Override
public String getPassword() {
return this.acct_usr_pwd;
};
/**
* 유저 아이디 불러오기
*/
@Override
public String getUsername() {
return this.acct_usr_id;
};
/**
* 만역에 acc_use_yn이 Y이면 사용가능않은 계정이고 N이면
* 사용불가한 상태인것이다.
* @return
*/
@Override
public boolean isEnabled() {
if(this.acct_lock_yn.equals("Y")){
return false;
}else {
return true;
}
}
/**
* 계정의 acct_lock_yn 컬럼이 "Y"이면 계정이 잠금상태여서 사용이 불가능하고
* N인경우 사용못한다.
* @return
*/
@Override
public boolean isAccountNonLocked(){
if(this.acct_lock_yn.equals("Y")){
return true;
}else {
return false;
}
}
/**
* 자격증명을 확인하는 부분인데 해당 부분은 사용하지 않을거기때문에 default 값 false를 리턴한다.
* @return
*/
@Override
public boolean isCredentialsNonExpired() {
return false;
}
/**
* 자격증명을 확인하는 부분인데 해당 부분은 사용하지 않을거기때문에 default 값 false를 리턴한다.
* @return
*/
@Override
public boolean isAccountNonExpired(){
return false;
}
}
아래와 같이 작성해주면 빨간줄이 사라지는 것을 확인할수 있을것이다. 여기다가 하나 더 추가해주자. 계정이 로그인에 성공하거나 비밀번호 찾기를 할때 lock count와 잠금당한 계정을 풀어줄 함수와 가입시 초기값을 넣어줄 함수를 CustomUserDetail 클래스내에 추가해주자
/**
* 비밀번호 리셋함수
*/
public void pwdReset() {
setAcct_fail_cnt(0);
setAcct_lock_yn("N");
setAcct_last_mod_dt(LocalDateTime.now());
}
/** 회원가입시 기본 값 자동지정*/
public void joinInit(){
setAcct_reg_dt(LocalDateTime.now());
setAcct_lock_yn("N");
setAcct_use_yn("Y");
setAcct_fail_cnt(0);
}
이제 회원가입 할 SQL문과 그것과 관련된 데이터를 저장해줄 repository를 만들것이다. 이 레파지토리는 AuthRepository라 지을 것이며 이녀석은 class가아닌 interface로 선언해준다. 인터페이스로 선언해준뒤에 구체적인 내용은 xml파일을 만들어서 넣을것이다.
AuthRepository 내용
package com.kkkkim.forStudy.auth.repository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Repository;
@Repository
public interface AuthRepository {
/** 회원가입 void **/
void joinAccount(UserDetails userDetail);
}
이제 해당 부분에 대한 SQL부분을 작성해보자 /resources/mybatis/mapper 경로에 auth 디렉토리를 추가해주고 거기에 AUTH_SQL.XML을 만들어준다. 이제 이안에 AuthRepository와 joinAccount를 연결해줄 mapper를 만들어 줄것이다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper
namespace="com.kkkkim.forStudy.auth.repository.AuthRepository">
</mapper>
mapper안에 namespace는 아래와 같은 Repository와 매핑하겠다는 뜻이다.해당 부분안에 무엇이 있는가
joinAccount부분이 있다. 그부분을 만들어주자. 그리고 그안에 들어갈 데이터의 형태는 customUserDetail.java와 동일하기때문에 파라메터 타입을 "customUserDetail"로 선언해준다. 그리고 해당부분에 insert문을 넣자
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper
namespace="com.kkkkim.forStudy.auth.repository.AuthRepository">
<insert id="joinAccount" parameterType="com.kkkkim.forStudy.auth.dao.CustomUserDetail">
INSERT INTO accountinfo
(acct_usr_id, acct_usr_pwd, acct_usr_email, acct_fail_cnt,
acct_lock_yn, acct_use_yn, acct_reg_dt)
values
(#{acct_usr_id}, #{acct_usr_pwd}, #{acct_usr_email}, #{acct_fail_cnt},
#{acct_lock_yn}, #{acct_use_yn}, #{acct_reg_dt})
</insert>
</mapper>
SQL문 입력하는 것도 어느정도 완성되었다. 그렇다면 이제 해당 것을 실제 수행할 서비스를 만들어주자. auth폴더에 service 폴더를 따로 만들어 주고 해당폴더에 CustomUserDetailService라는 자바 파일을 만들어주자. 이아이도 UserDetailService이란 애를 인용해 와서 쓸것이다. 해당 인터페이스 안에는 loadUserByUserName이라는 함수가 있는데 로그인할때 쓰는 기능이고 당장은 쓰지 않기때문에 null값을 리턴하겠금 선언하겠다.
package com.kkkkim.forStudy.auth.service;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
@Service("customerUserDetailService")
public class CustomUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
}
이제 가입하는 void를 만들어보자. 이함수에서 할것은 authRepository의 joinAccount를 수행하는 기능일 것이다. 하려면 우선 AuthRepository를 불러오고 이후에 함수를 수행해야할 것이다. authRepository를 선언해준뒤 joinNewUser라는 메소드를 추가해주자.
package com.kkkkim.forStudy.auth.service;
import com.kkkkim.forStudy.auth.repository.AuthRepository;
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;
@Service("customerUserDetailService")
public class CustomUserDetailService implements UserDetailsService {
@Autowired
private AuthRepository authRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
public void joinNewUser(UserDetails userDetails){
authRepository.joinAccount(userDetails);
}
}
이제 이 함수를 사용할 url부분을 매핑하도록 AuthController에 추가해주자.이때 우리가 사용할 customUserDetailSerivce와 조인정보를 담을 customUserDetail 클래스를 같이 불러오자
package com.kkkkim.forStudy.auth.service;
import com.kkkkim.forStudy.auth.dao.CustomUserDetail;
import com.kkkkim.forStudy.auth.repository.AuthRepository;
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;
@Service("customUserDetailService")
public class CustomUserDetailService implements UserDetailsService {
@Autowired
private AuthRepository authRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
public void joinNewUser(CustomUserDetail userDetails) throws Exception{
authRepository.joinAccount(userDetails);
}
}
joinNewMember void에서 paramMap은 웹에서 보낸 변수들이 담긴 부분이고 response는 그에 대한 답례를 쏴줄 부분이다.
일단 변수를 해당 함수가 잘받는지 확인하기 위해 System.out.println(paramMap);을 추가하자
@PostMapping("joinNewMember")
public void joinNewMember(@RequestBody HashMap<String, Object> paramMap,
HttpServletResponse response){
System.out.println("잘되는지 체크");
System.out.println(paramMap);
/**
* 조인이 필요한 로직이 들어갈 부분
*/
/**이후 회원가입 */
}
이제 웹페이지로 돌아가자. 해당 페이지에서 입력한 값을 자바단으로 보내주는 ajax 함수를 작성할 것이다. 이글은 java 관련된 글이기때문에 이함수에 대해서는 구체적으로 다루진 않겠다. 대충 data란 변수에 input값들을 다 넣어서 joinNewMember에 보내주는 식이다.
function join(){
let data ={
"acct_usr_id": $("#username").val()
,"acct_usr_email": $("#email").val()
,"acct_usr_pwd": $("#password").val()
}
$.ajax({
type: "POST",
url: "/auth/joinNewMember",
datatype: "json",
contentType: 'application/json',
data: JSON.stringify(data),
success: function (res) {
console.log(res);
},
error: function (request, status, error) {
},
beforeSend: function () {
$("#username").val("");
$("#email").val("");
$("#password").val("");
},
complete: function () {
alert("성공");
}
});
}
sign up버튼을 누를때 해당 함수가 동작하도록 onclick 함수를 추가해주자. 나는 button을 누르면 form이날라가는 불편함때문에 button을 span으로 바꿨다. springboot를 켜보고 실제 동작하는지 확인해보자
<span class="btn btn-primary d-grid w-100" onclick="join()">Sign up</span>
자바 콘솔단에서 변수가 잘찍힌다면 다음단계로 넘어가자
이제 CustomerUserDetail을 새로 선언해줘서 그 값을 넣고 해당 부분에 대해 아까 변수받은것을 넣어주고 db에 입력해줄것이다. 이를 정리하면 joinNewMember 보이드는 다음과 같은 구성을 갖는다.
@PostMapping("joinNewMember")
public void joinNewMember(@RequestBody HashMap<String, Object> paramMap,
HttpServletResponse response) throws Exception{
/** 회원정보 이쁘게 담기 */
CustomUserDetail userInfoDetail = new CustomUserDetail();
userInfoDetail.setAcct_usr_id(paramMap.get("acct_usr_id").toString());
userInfoDetail.setAcct_usr_email(paramMap.get("acct_usr_email").toString());
userInfoDetail.setAcct_usr_pwd(paramMap.get("acct_usr_pwd").toString());
userInfoDetail.joinInit();
/** 담은 정보 DB에 넣어주기*/
customUserDetailService.joinNewUser(userInfoDetail);
}
이제 수행해보면 정상적으로 값이 넘어가면서 db에 들어가는걸 확인할 수 있다.
근데 생각해봐라....누가 비밀번호를 곧이 곧대로 저장하는가. 해당부분을 암호하는 부분이 필요하다.
우선 build.gradle에 다음과 같은 implements를 추가한다.
implementation 'org.springframework.security:spring-security-crypto:6.1.4'
그리고 해당 자바패키지를 다운받은후 cutomerUserDetailService로 이동하자
해당부분은 cutomerUserDetailService를 다음과 같이 변경한다면 암호화되서 저장될것이다.
package com.kkkkim.forStudy.auth.service;
import com.kkkkim.forStudy.auth.dao.CustomUserDetail;
import com.kkkkim.forStudy.auth.repository.AuthRepository;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service("customUserDetailService")
public class CustomUserDetailService implements UserDetailsService {
@Autowired
private AuthRepository authRepository;
private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
public void joinNewUser(CustomUserDetail userDetails) throws Exception {
String encryptPwd = passwordEncoder.encode(userDetails.getAcct_usr_pwd());
userDetails.setAcct_usr_pwd(encryptPwd);
authRepository.joinAccount(userDetails);
}
}
org.springframework.security.crypto.password.PasswordEncoder 패키지를 불러와서 입력한 암호를 암호화 해서 입력해줄것이다. 이제 스프링을 재기동 하고 가입을 해보자. 암호화가 제대로 적용되는것을 확인할 수 있을것이다.
이후에 필요한 기능들은 아래에 추가하도록 하겠다 (추가예정)
추가할 내용
1. 아이디 가입한적 있는지 체크
2. 중복아이디 있는지 체크
3. 메일양식 맞는지 체크
4. 비밀번호 작성규칙 지정
'공부해야할 것 > java' 카테고리의 다른 글
[SpringBoot] 웹 개발(8) - 로그인 기능 만들기(3)(로그인 관련 설정) (1) | 2023.11.11 |
---|---|
[SpringBoot] 웹개발(6) - 로그인 기능 만들기(1) (0) | 2023.11.11 |
[SpringBoot] 웹개발(5) - thymleaf 적용 (2) | 2023.11.09 |
[SpringBoot] 웹개발(4) - 다운받은 템플릿 적용 (0) | 2023.11.09 |
[springBoot] 웹개발(3) - bootstrap적용 (0) | 2023.11.04 |