새소식

반응형
250x250
🔲 Framework/🔒 Spring Security

[Spring Security]로그인 커스터마이징(왕초보)(1) - context-security 뜯어보기, 비밀번호 암호화(BCrypt)

  • -
728x90
반응형

전자정부 표준프레임워크(v3.10.0)로 진행했고, 시큐리티 관련 설정은 이 글 참고

https://hyewonkim1996.tistory.com/19

 

[eGov]전자정부 프레임워크(v3.10.0)와 Spring Security(스프링 시큐리티) 연동

전자정부 스프링 시큐리티 연동 1.pom.xml에서 스프링 시큐리티 라이브러리 설정(호환되는 버전인지 확인 필수) 이 글에서는 v3.10.0 기준 호환되는 스프링 시큐리티 버전 4.2.13.릴리즈로 설정했다.

hyewonkim1996.tistory.com

 

시큐리티로 로그인 구현 실습하기에 앞서, 시큐리티는 구조와 흐름을 꼭 알아야 하니 모른다면 이 글을 참고하고 오자.

https://hyewonkim1996.tistory.com/23

 

[Spring Security]스프링 시큐리티란? 스프링 시큐리티 개념, 구조, 흐름

✅ 스프링 시큐리티란? 스프링 기반 애플리케이션의 인증과 인가를 담당하는 스프링 하위 프레임워크 🔹 인증 : 로그인, 회원가입 🔹 인가 : 인증된 사용자의 권한에 따라 접근 허락 ✅ 스프링

hyewonkim1996.tistory.com

 

시큐리티를 적용한 로그인 흐름을 요약하면 다음과 같다.

1.회원가입 시 사용자가 입력한 비밀번호를 시큐리티 인코더로 암호화해 저장

2.시큐리티 설정 파일에서 설정한 로그인 페이지에서 사용자가 로그인 시도

3.사용자가 로그인 폼에 입력한 아이디와 비밀번호(역시 인코더로 암호화)를 가지고 스프링 시큐리티에서 자체 검증

4.로그인 실패, 성공 여부에 따른 로직 실행

 

여기서 1-4에 대한 설정을 context-security.xml 파일에서 하는데, 중요한 부분만 찬찬히 뜯어 보도록 하자.

<http auto-config="true" use-expressions="true">
		<!-- 세션관리 -->
		<session-management invalid-session-url="/">
			<!-- 동일 ID의 세션 최대 개수가 한개, 그 이상일 경우는 기존 세션 무효화 -->
			<concurrency-control max-sessions="1"
				error-if-maximum-exceeded="false" />
		</session-management>
		<intercept-url pattern="/admin**"
			access="hasRole('ROLE_ADMIN')" />
		<intercept-url pattern="/edu_project/myPage**"
			access="hasAnyRole('ROLE_USER','ROLE_ADMIN')" />
		<intercept-url pattern="/**" access="permitAll" />
		<csrf disabled="true" />
		<form-login username-parameter="m_id"
			password-parameter="m_pw" login-processing-url="/login"
			login-page="/home" default-target-url="/login_success"
			authentication-failure-url="/login_fail" />
		<remember-me key="remember-key"
			token-validity-seconds="604800"
			remember-me-parameter="remember-me-param" />
		<logout logout-url="/logout" logout-success-url="/logout_After"
			invalidate-session="true"
			delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE" />
	</http>

	<authentication-manager>
		<authentication-provider>
			<jdbc-user-service data-source-ref="dataSource"
				users-by-username-query="SELECT m_id AS m_id, m_pw AS m_pw, enabled FROM member WHERE m_id = ?"
				authorities-by-username-query="SELECT m_id AS m_id, levels AS levels FROM member WHERE m_id = ?" />
			<password-encoder ref="bcryptPasswordEncoder" />
		</authentication-provider>
	</authentication-manager>
	<beans:bean id="bcryptPasswordEncoder"
		class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
</beans:beans>

 

 

이 부분은 세션에 대한 설정 부분으로, 유효하지 않은 세션이 접속할 경우 리다이렉트할 url과 세션의 최대 개수를 설정해 놓았다. 여러분 프로젝트에서 세션을 사용하는 게 아니라면 이 부분은 굳이 필요없으므로 생략해도 된다.

 

 

이 부분은 사용자의 권한에 따라 접속할 수 있는 페이지를 제한하는 부분이다. 예를 들어 ROLE_USER나 ROLE_ADMIN 권한을 가진 사용자만 마이페이지 주소에 접근할 수 있게 하여 비회원은 접근하지 못하도록 설정할 수 있는 것이다.

이 부분은 csrf 토큰에 관한 설정을 하거나 해제하는 부분인데, 여기서는 csrf 토큰을 쓰지 않으므로 csrf 토큰에 대한 설명은 추후 다른 포스팅에서 해 보겠다.

 

여기가 로그인에 관한 설정을 하는 부분이다.

username-parameter : 사용자가 입력한 아이디의 파라미터명(DB 컬럼명과 일치시켜야 한다)

password-parameter : 사용자가 입력한 비밀번호의 파라미터명(DB 컬럼명과 일치시켜야 한다)

login-processing-url="/login" : 로그인 인증을 실행하는 주소로 사용자가 로그인 버튼을 눌렀을 때 여기서 로그인 검증을 실시한다. /login은 스프링 시큐리티 자체에서 제공하는 로그인 처리 url이다.

login-page : 사용자가 아이디, 비밀번호를 입력하는 로그인 커스터마이징 페이지의 url 주소

default-target-url : 로그인 성공 시 요청하는 url 주소

authentication-failure-url : 로그인 실패 시 요청하는 url 주소

 

authenticationFailureHandler를 자바 클래스로 따로 만드는 경우를 본 적이 있을 텐데, authentication 실패시 원인 디버깅을 위해 커스텀으로 클래스를 따로 만들기도 한다. 이뿐만 아니라 DB에서 받아온 데이터가 담긴 UserDetails 객체 등도 커스텀으로 직접 설정할 수가 있다. 스프링 시큐리티에 대해 서치하다 보면 대부분의 블로그 글이 위 내용을 커스텀한 글인데, 왕초보인 내가 처음 공부했을 때 이것 때문에 코드 이해하기가 너무너무 복잡하고 어려웠다ㅠㅠ.. 이 글은 왕초보를 위한 글이므로 ONLY 로그인 페이지만 커스터마이징!

 

 

이 부분은 사용자의 로그인 정보를 쿠키에 저장해 사용자가 브라우저를 닫았다 열어도 로그인을 유지하게 설정하는 부분으로, 필수적이진 않지만 사용자 편의를 위해 사용한다.

 

 

로그아웃 설정을 하는 부분이다.

logout-url="/logout" : /login과 마찬가지로 스프링 시큐리티에서 제공하는 로그아웃 처리 url이다.

logout-success-url : 로그아웃 후 요청하는 url 주소다.

그 밑은 세션을 무효화하고 등록했던 쿠키를 삭제하는 설정이다.

 

 

로그인 검증을 실질적으로 실행하는 authenticaion-provider를 설정하는 부분이다.

<jdbc-user-service> : 데이터베이스에 저장된 사용자 정보를 기반으로 인증을 수행하는 서비스

data-source-ref : 사용할 데이터 소스로, DB 연동할 때 설정한 datasource bean 변수명을 쓰면 된다.

users-by-username- query : 사용자 아이디를 기반으로 사용자 정보를 조회하는 쿼리

authorities-by-username-query : 사용자 아이디를 기반으로 사용자 권한 정보를 조회하는 쿼리

password-encoder ref="" : 비밀번호 인코딩 설정으로 ref에 명시한 bean을 참조한다는 의미

 

시큐리티에서 사용할 비밀번호 인코더를 bean으로 등록한 부분이다.

 

이로써 설정 파일에 대한 설명은 끝났고아유 힘들어.. 이제 다시 이 흐름에 따라 시큐리티로 로그인을 구현해보자!

 

1.회원가입 시 사용자가 입력한 비밀번호를 시큐리티 인코더로 암호화해 저장

2.시큐리티 설정 파일에서 설정한 로그인 페이지에서 사용자가 로그인 시도

3.사용자가 로그인 폼에 입력한 아이디와 비밀번호(역시 인코더로 암호화)를 가지고 스프링 시큐리티에서 자체 검증

4.로그인 실패, 성공 여부에 따른 로직 실행

 

 

1.회원가입 시 사용자가 입력한 비밀번호를 시큐리티 인코더로 암호화해 저장

먼저 시큐리티로 비밀번호를 암호화하고 로그인 검증 시에는 DB에 튜플을 직접 넣지 않고 반드시 자바에서 암호화 로직을 거쳐야 오류가 나지 않는다.(나도 알고 싶지 않았어요..) 때문에 회원가입 기능을 먼저 구현하도록 하겠다!

 

1)테이블 생성

여기서 levels는 권한 컬럼이고 사용자 권한을 디폴트로 설정했다.

enabled는 계정 활성화 상태로 0이 비활성화, 1이 활성화인데 1을 디폴트로 설정했다.

나는 기존에 있던 테이블을 사용해서 이름이나 연락처 컬럼도 있지만 귀찮다면 생략해도 무방하다.

 

2)회원 정보를 매핑할 VO 생성

 

3)mapper에서 회원의 권한, 계정 상태 추가

만약 ADMIN 권한도 따로 설정하고 싶을 경우 2)과정에서 VO 필드에 levels를 추가하고, mapper에서 default를 #{levels}로 수정하여 컨트롤러에서 VO의 아이디를 불러와 아이디에 따라 levels를 설정하면 되겠다.

 

4)회원가입하는 jsp 파일 생성

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" crossorigin="anonymous"></script>
<form class="row g-3" action="joinDone" method="post">
        <div class="col-md-6">
	<br><br><br><br><br><br><br>
          <label for="inputEmail4" class="form-label">아이디(이메일)</label>
          <input type="text" class="form-control" name="m_id" placeholder="이메일" id="user_id">
          <span id="id_warn" style="font-size:12px; color:blue;"></span>
        </div>
          <div class="col-md-8">
            <label for="inputCity" class="form-label">비밀번호</label>
            <input type="password" class="form-control" name="m_pw" id="user_pw" placeholder="비밀번호">
			<span id="pw_warn" style="font-size:12px; color:blue;"></span>         
          </div>
          <br>
          <div class="col-md-8">
            <label for="inputCity" class="form-label">비밀번호 확인</label>
            <input type="password" class="form-control" id="user_confirm" placeholder="비밀번호 확인">
            <span id="pw_confirm" style="font-size:12px; color:blue;"></span>
          </div>
          <br>
        <div class="col-md-8">
          <label for="inputZip" class="form-label">이름</label>
          <input type="text" class="form-control" name="m_name" id="user_name" placeholder="이름">
          <span id="name_warn" style="font-size:12px; color:blue;"></span>
        </div>
        <div class="col-md-8">
            <label for="inputCity" class="form-label">연락처</label>
            <input type="text" class="form-control" name="m_phone" id="user_phone" placeholder="01011112222">
            <span id="phone_warn" style="font-size:12px; color:blue;"></span>
          </div>
        <div class="col-12">
          <button type="button" id="signup_btn" class="btn btn-primary" style="background-color: #f94327;
          border:#f94327;">회원가입</button>
        </div>
      </form>

폼에서 이름, 연락처는 필요에 따라 쓰거나 생략하면 된다.

 

5)컨트롤러에 비밀번호 암호화 인코더 의존성 주입 & 회원가입 기능 구현

먼저 컨트롤러에 비밀번호를 암호화하는 BCryptPasswordEncoder 객체 주소를 주입해 준다.

다음으로 회원가입 기능을 구현하면 되는데.. 나는 팀프로젝트에서 사용한 소스를 가져와서 비동기로 구현되어 있지만귀찮아서..만약 비동기가 아니라면

@RequestMapping(value = "/joinDone", method = RequestMethod.POST)
	public String joindone(@ModelAttribute MemberVO mvo) throws IOException {
		memberService.insertOne(mvo);
		return "redirect:/home";
	}

이 코드에 VO 비번 재설정하는 부분만 추가하면 되겠다.

암튼 비밀번호 암호화는 클라이언트가 입력한 데이터가 매핑된 VO를 불러와 재설정한 후 insert하는 방식으로 진행했다.

 

6)DB 확인

 

회원가입 진행 후 DB를 확인하면 비번이 이렇게 암호화된 것을 확인할 수 있다.

 

글이 길어져서 로그인 설정은 다음 포스팅에..

728x90
반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.