관리 메뉴

Just Do it

[개인 프로젝트] SJBoard (댓글 및 파일 업로드 게시판) 6일차 개발 일지 본문

개인프로젝트/[스프링] SJBoard 개발일지

[개인 프로젝트] SJBoard (댓글 및 파일 업로드 게시판) 6일차 개발 일지

Seojoo21 2022. 3. 15. 21:15

2022.03.15.화요일 6일차 

 

드디어 지난 사흘 동안 Spring Web Security를 이용한 로그인 처리를 학습하여 SJBoard 에 로그인 기능을 구현해보았다. 

 

1.  로그인 & 로그아웃 기능 구현 설계

1) 사용자 권한별 이용 가능 서비스 

    회원  비회원
게시물 조회 O O
작성 O X
수정 only 작성자 X
삭제 only 작성자 X
댓글 작성 O X
수정 only 작성자 X
삭제 only 작성자 X

 

2) 회원 권한 분류

분류 분류 코드 테스트용 id, pw, userName 
일반회원 ROLE_MEMBER member0, member0, 일반회원0
관리자 ROLE_ADMIN admin0, admin0, 관리자0

 

2. 제작 과정

1) 오라클 데이터베이스 내 회원 정보를 가진 테이블 TBL_MEMBER, TBL_MEMBER_AUTH 생성 

테이블 명  설명 
TBL_MEMBER 아이디, 비밀번호, 사용자이름, 회원가입일, 회원정보수정일, 유효회원여부 정보를 가지고 있는 인증 테이블
TBL_MEMBER_AUTH 아이디, 권한 정보를 가지고 있는 권한 테이블 

- TBL_MEMBER의 아이디(userid)와 TBL_AUTH의 아이디(userid)는 서로 외래키(FK)로 연결

 

 

2) security-context.xml 생성 및 BCryptPasswordEncoder으로 테스트용 회원 정보 생성 

 

- 로그인 처리를 담당하는 security-context.xml을 별도로 생성 및 로그인 작업에 필요한 빈 등록 및 기타 설정 처리 

- com.seojoo21.security 패키지 아래 MemberTests 클래스를 작성하여 테스트용 아이디의 정보와 암호화된 비밀번호를 데이터베이스에 저장 

tbl_member에 비밀번호가 Bcrypt 방식으로 변환되어 저장됨
tbl_member_auth에 권한이 저장됨

 

3) 로그인 영속 영역 구현 

- com.seojoo21.domain 패키지 아래 회원 인증 정보를 담당하는 MemberVO, 회원 권한 정보를 담당하는 AuthVO 클래스 작성 

- com.seojoo21.mapper 패키지 아래 회원 인증 정보 및 권한 정보를 가져오는 MemberMapper 인터페이스 작성

- src/main/resources/com/seojoo21/mapper 폴더 아래 SQL문을 처리하는 MemberMapper.xml 작성

- MemberMapperTests 클래스를 생성하여 MemberMapper.xml 동작 여부 확인  

 

4) 스프링 시큐리티의 UserDetailsService 및 LoginController 구현 

- 회원 인증 및 권한 처리를 위해 스프링 시큐리티의 UserDetailsService를 구현하고자 SjBoardUserDetailsService 클래스 및 SjBoardUser 클래스 생성

* SjBoardUserDetailsService: UserDetailsService 구현 

* SjBoardUser: User 상속 

- 로그인 처리를 위한 LoginController 및 AccessDeniedHandler, SuccessHandler 구현

* SjBoardAccessDeniedHandler: AccessDeniedHandler 구현 

* SjBoardLoginSuccessHandler: AuthenticationSuccessHandler 구현

 

 

3. 발생 에러 및 해결 방법

1)
- 에러: 게시물 작성 페이지 register.jsp에 로그인한 사용자의 이름이 출력되도록 <sec:authentication property="" /> 태그를 입력해서 테스트 하는데 500 코드와 함께 아래 메세지가 나타났다. 
 
 javax.servlet.ServletException: javax.servlet.jsp.JspException: org.springframework.beans.NotReadablePropertyException: Invalid property 'principal.userName' of bean class [org.springframework.security.authentication.UsernamePasswordAuthenticationToken]: Bean property 'principal.username' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
 
getter/setter가 없어 속성값으로 principal.username이 불러지지 않는다는 것 같아 로그인 관련 클래스 파일과 security-context.xml까지 모두 확인해보았다.
 
 
- 원인 :
 
나는 이번 프로젝트에서 UserDetailsService를 구현할 때 회원 정보 관련 변수들이 있는 MemberVO에 직접 구현하지 않고 SjBoardUserDetailsService 라는 별도의 클래스를 만들어 UserDetailsService를 implements 하고, UsesrDetails의 return값을 객체로 가져오고자 해당 클래스의 return 값으로 SjBoardUser 클래스를 설정하였다. 그리고 SjBoardUser 클래스는 User를 extends 하도록 구현하였다. 
 
관련 파일들을 확인해보니 문제는 SjBoardUser에 있었다. 알고보니 스프링 시큐리티의 User 클래스를 상속하여 구현한 SjBoardUser에 get() 메서드가 없어서 UserDetailsService를 구현한 SjBoardUserDetailsService 의 return 값으로 객체가 제대로 불러와지지 않았던 것 
 
- 해결: Lombok 라이브러리의 @Getter 어노테이션을 추가하였다. 
 
 
도움 받은 사이트 출처:

https://velog.io/@jonah/javax.servlet.ServletException-javax.servlet.jsp.JspException-org.springframework.beans.NotReadablePropertyException-Invalid-property-principal.userid-of-bean-class-qzh3un0w

 

4. 오늘 프로젝트 진행하면서 추가로 배운 내용

1) 위의 3-1에 적은 에러를 해결하면서 jsp 페이지에 시큐리티 값을 불러오기 위해 쓰는 <sec:authentication property="principal.xxxxxx" /> 의 principal은 Authentication.getPrincipal()로 얻을 수 있고 return 되는 객체가 2가지가 있다는 것을 알게 되었다. 

 

- 인증되지 않은 상태를 return하는 String 

- 인증에 성공한 정보가 담긴 Object객체

 

***

<sec:authentication property="principal"/>을 이용했을 때 의미하는 것은 UserDetailsService에서 반환된 객체이다. 즉 나의 프로젝트에서는 principal이 SjBoardUser를 의미하므로 property="principal.member"로 쓴다면 SjBoardUser 객체의 getMember()를 호출한다는 것을 알 수 있다. 

 

2) 컨트롤러에 사용하는 스프링 시큐리티의 어노테이션을 활성화하기 위해서는 security-context.xml이 아닌 스프링 MVC설정을 담당하는 servlet-context.xml에 security 네임스페이스를 추가해야한다. XML에 스프링 시큐리티의 네임스페이스가 추가될 때 5.0 버전으로 추가되는 것은 4.2 버전으로 낮추거나 버전 정보를 지워야 에러없이 작동한다. 

 

추가된 security 네임스페이스를 이용해서 global-method-security를 지정한다. 어노테이션은 기본으로 'disabled'되어 있으므로 'enabled'로 설정한다.

<security:global-method-security pre-post-annotations="enabled" secured-annotations="enabled"></security:global-method-security>

 

3) 너무 세세한 기능과 디자인을 구현하려는데 시간을 많이 뺏기지 말 것. 가장 중요한 기능부터 구현하고 세부적인 것들을 정리하는 것이 중요하다!