개인 보안 노트 서비스
요구사항
1. 유저는 본인의 노트(게시글)만 저장하고 삭제하고 볼 수 있다.
2. 다른 유저의 노트는 볼 수 없다.
3. 어드민은 관리 차원에서 유저들의 노트 제목 리스트는 볼 수 있지만 내용은 볼 수 없다.
4. 어드민은 공지사항을 작성 할 수 있고 일반 유저들은 이 공지사항을 볼 수 있다.
기술 스택
1. Spring WebMVC
2. Spring Security
3. Thymeleaf
4. Lombok
5. Spring Jpa
6. H2 database
7. Gradle
오류 리포트
- java: variable em might not have been initialized
- user/UserService
- private final UserRepository userRepository;
- UserRepository 의존성 주입을 위해 final로 선언했을 때 에러 발생
- 해결 : @RequiredArgsConstructor를 사용하여 final 필드를 파라미터로 받는 생성자를 생성해서 해결
- login 페이지 뷰 반환이 안되는 오류 발생
- 400 에러 발생
- registry.addViewController("/login").setViewName("login");
- 해결 : @Configuration으로 등록 해서 해결
1. 프로젝트 Setting
- 의존성 추가
dependencies {
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
// web mvc 추가
implementation 'org.springframework.boot:spring-boot-starter-web'
// security 추가
implementation 'org.springframework.boot:spring-boot-starter-security'
// thymeleaf 추가
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
// thymeleaf에서 springsecurity 사용하기 위한 의존성 추가
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
// lombok 추가
implementation 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// jpa 추가
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// h2 db 추가
runtimeOnly 'com.h2database:h2'
// starter test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// spring security test
testImplementation 'org.springframework.security:spring-security-test'
// junit test
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}
- main 설정
@SpringBootApplication
public class Personal_Note_Application {
public static void main(String[] args) {
SpringApplication.run(Personal_Note_Application.class, args);
}
}
2. View 구현
- 관리자
- sec:authorize="hasAnyRole('ROLE_ADMIN')"
- Spring security의 sec을 사용해서 "ROLE_ADMIN"이면 링크에 접근 가능하게 설정
- 유저
- sec:authorize="isAuthenticated()"
- sec:authorize="hasAnyRole('ROLE_USER')"
- isAuthenticated()를 통해 로그인 인증을 통과한 유저는 접근 가능하게 설정
- "hasAnyRole('ROLE_USER')"이면 유저로써 링크에 접근 가능하게 설정
- 비로그인
- sec:authorize="!isAuthenticated()"
- !isAuthenticated() 을 통해 비로그인 및 접근 권한이 없게 설정
<li class="nav-item">
<p
class="nav-link active"
sec:authorize="isAuthenticated()"
sec:authentication="name">
</p>
</li>
<li class="nav-item">
<a class="nav-link active" th:href="@{/}">홈</a>
</li>
<li class="nav-item">
<!-- 관리자만 -->
<a
class="nav-link active"
sec:authorize="hasAnyRole('ROLE_ADMIN')"
th:href="@{/admin}"
>
관리자 페이지
</a>
</li>
<li class="nav-item">
<!-- 로그인한 사람 모두 -->
<a
class="nav-link active"
sec:authorize="isAuthenticated()"
th:href="@{/notice}"
>
공지사항
</a>
</li>
<li class="nav-item">
<!-- 유저만 -->
<a
class="nav-link active"
sec:authorize="hasAnyRole('ROLE_USER')"
th:href="@{/note}"
>
개인노트
</a>
</li>
<li class="nav-item">
<!-- 로그인 안한 사람만 -->
<a
class="nav-link active"
sec:authorize="!isAuthenticated()"
th:href="@{/login}"
>
로그인
</a>
</li>
<li class="nav-item">
<!-- 로그인 안한 사람만 -->
<a
class="nav-link active"
sec:authorize="!isAuthenticated()"
th:href="@{/signup}"
>
회원가입
</a>
</li>
<li class="nav-item">
<!-- 로그인한 사람만 -->
<a
class="nav-link active"
sec:authorize="isAuthenticated()"
th:href="@{/logout}"
>
로그아웃
</a>
</li>
2. 서비스 로직 구현
- user/UserService
@Service // 스프링에게 이 클래스가 서비스임을 알려주는 어노테이션입니다.
@RequiredArgsConstructor // Lombok 어노테이션으로, final 필드를 파라미터로 받는 생성자를 생성합니다.
// 사용자 서비스를 나타내는 클래스입니다.
public class UserService {
private final UserRepository userRepository; // UserRepository 의존성 주입을 위한 필드
private final PasswordEncoder passwordEncoder; // PasswordEncoder 의존성 주입을 위한 필드
// 사용자 등록 메서드입니다.
public User signup(String username, String password) {
// 이미 등록된 사용자명이 있는 경우 예외를 발생시킵니다.
if (userRepository.findByUsername(username) != null) {
throw new AlreadyRegisteredUserException();
}
// 비밀번호를 암호화(인코딩)하여 사용자를 저장합니다.
return userRepository.save(new User(username, passwordEncoder.encode(password), "ROLE_USER"));
}
// 관리자 등록 메서드입니다.
public User signupAdmin(String username, String password) {
// 이미 등록된 사용자명이 있는 경우 예외를 발생시킵니다.
if (userRepository.findByUsername(username) != null) {
throw new AlreadyRegisteredUserException();
}
// 비밀번호를 암호화(인코딩)하여 관리자를 저장합니다.
return userRepository.save(new User(username, passwordEncoder.encode(password), "ROLE_ADMIN"));
}
// 사용자명으로 사용자를 찾는 메서드입니다.
public User findByUsername(String username) {
return userRepository.findByUsername(username);
}
}
- note/NoteService
// 노트 관련 비즈니스 로직을 처리하는 서비스 클래스입니다.
@Service // 스프링 서비스로 등록됨을 나타내는 어노테이션입니다.
@Transactional // 메서드 실행 시 트랜잭션 처리를 위해 사용되는 어노테이션입니다.
// Lombok의 RequiredArgsConstructor 어노테이션으로, final 필드에 대한 생성자를 자동으로 생성합니다.
@RequiredArgsConstructor
public class NoteService {
// NoteRepository 의존성 주입을 위한 필드
private final NoteRepository noteRepository;
// 사용자가 소유한 노트 목록을 조회하는 메서드입니다.
@Transactional(readOnly = true) // 읽기 전용 트랜잭션으로 설정합니다.
public List<Note> findByUser(User user) {
// 사용자가 null이면 예외를 발생시킵니다.
if (user == null) {
throw new UserNotFoundException();
}
// 사용자가 관리자인 경우 모든 노트를 조회합니다.
if (user.isAdmin()) {
return noteRepository.findAll(Sort.by(Sort.Direction.DESC, "id"));
}
// 사용자가 일반 사용자인 경우 해당 사용자의 노트 목록을 최신순으로 조회합니다.
return noteRepository.findByUserOrderByIdDesc(user);
}
// 새로운 노트를 저장하는 메서드입니다.
public Note saveNote(User user, String title, String content) {
// 사용자가 null이면 예외를 발생시킵니다.
if (user == null) {
throw new UserNotFoundException();
}
// 새로운 노트를 저장하고 반환합니다.
return noteRepository.save(new Note(title, content, user));
}
// 사용자가 소유한 특정 ID의 노트를 삭제하는 메서드입니다.
public void deleteNote(User user, Long noteId) {
// 사용자가 null이면 예외를 발생시킵니다.
if (user == null) {
throw new UserNotFoundException();
}
// 사용자가 소유한 특정 ID의 노트를 조회합니다.
Note note = noteRepository.findByIdAndUser(noteId, user);
// 노트가 존재하면 삭제합니다.
if (note != null) {
noteRepository.delete(note);
}
}
}
'개발 > Spring' 카테고리의 다른 글
Spring security Architecture, Filter (0) | 2024.02.29 |
---|---|
Spring security Test (0) | 2024.02.28 |
Spring Security (0) | 2024.02.27 |
Querydsl (1) | 2024.02.27 |
JPA 테스트 (1) | 2024.02.27 |