본문 바로가기
개발/Spring

Spring Security 구현

by BellOne4222 2024. 2. 27.

개인 보안 노트 서비스

 

요구사항

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