본문 바로가기

SpringBoot

[연재] Spring Boot diary : 로그인정보 조회

728x90
반응형

원문은 Spring Boot: study.diary : 로그인정보 조회를 참고하세요.

 

diary 프로그램은 회원가입 기능이 완료되었다. 로그인 기능이 완료되었다.

이제 로그인해서 일기를 쓸 때 로그인정보를 가져와서 해당 일기의 주인을 표시하는 기능을 구현해야 한다.

로그인한 사용자의 정보는 아래 코드를 통해서 가져올 수가 있다.

SecurityContextHolder.getContext().getAuthentication().getName()

위 코드로 구해지는 값을 테스트하기 위해서 DiaryService 의 테스트 클래스를 작성해볼께.

package com.woohahaapps.study.diary.service;

import com.woohahaapps.study.diary.domain.Diary;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;

import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.http.RequestEntity.post;

@SpringBootTest
//@Transactional
class DiaryServiceTest {

    private final DiaryService diaryService;

    @Autowired
    DiaryServiceTest(DiaryService diaryService) {
        this.diaryService = diaryService;
    }

    @Test
    @WithMockUser(username="woohaha@gmail.com")
    void createDiary() {
        diaryService.CreateDiary("2024-01-01", "diary create test222");
    }
   ...
}

createDiary 테스트 함수에 붙인 @WithMockUser 애노테이션은 Spring Security 로 작성한 프로젝트에서 인증된 정보를 자동으로 생성해서 사용할 수 있도록 해주지.

이 애노테이션을 사용하기 위해서는 build.gradle 에 아래 의존성이 추가되어 있어야해.

testImplementation 'org.springframework.security:spring-security-test'

DiaryService 의 CreateDiary 에 로그인 사용자의 정보를 확인하기 위한 코드를 삽입해봤어.

    public void CreateDiary(String date, String content) {
        System.out.println("Service:date=" + date + ",content=" + content);
        System.out.println(SecurityContextHolder.getContext().getAuthentication().getName());
        diaryMapper.CreateDiary(date, content);
    }

테스트를 실행시키면 다음과 같은 로그가 기록되지.

Rest API 단에서 위 코드를 이용해서 일기의 주인 정보를 기록하도록 코드를 수정해볼께.

우선 DiaryMapper.xml 에서 CreateDiary 에 대한 쿼리문을 수정해주자.

    <insert id="CreateDiary">
        insert into diary (diary_date, diary_content, email) values (DATE(#{date}), #{content}, #{email});
    </insert>

DiaryMapper 인터페이스에서는 추가된 email 을 파라미터로 넘겨줘야지.

@Mapper
public interface DiaryMapper {
    public void CreateDiary(@Param("date") String date, @Param("content") String content, @Param("email") String email);
...

테스트코드를 작성하면서 아래와 같은 오류가 발생했는데, 함수의 파라미터와 xml 파일에 기록된 파라미터가 일치될 수 있도록 @Param 애노테이션을 사용하니 오류가 해결되었어.

org.apache.ibatis.binding.BindingException: Parameter 'date' not found. Available parameters aremoz-extension://e4d2ec3d-f31b-4d0d-966e-6c89c7143ff2/html/options.html

이제 DiaryService 에서 DiaryMapper 인터페이스에 새로 추가된 email 파라미터를 사용하도록 수정해볼께.

    public void CreateDiary(String date, String content) {
        System.out.println("Service:date=" + date + ",content=" + content);
        String email = SecurityContextHolder.getContext().getAuthentication().getName();
        diaryMapper.CreateDiary(date, content, email);
    }

이제 로그인해서 일기를 작성하면 해당 일기 데이터의 email 에는 로그인한 사용자의 email 주소가 기록되게 되지.


그렇다면 UI 에서는 로그인한 사용자정보를 어떻게 가져와서 표시할 수 있을까?

우선 build.gradle 에 아래 의존성을 추가해주자.

implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'

home.html 파일에 spring security 네임스페이스를 등록해줘.

<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

그리고나면 로그인한 사용자정보를 다음과 같이 표시할 수 있어.

<div sec:authentication="principal.username"></div>

아래와 같은 태그로도 가능해.

<p th:text="${#authentication.name}"></p>

로그인 상태에 따라 로그아웃 버튼을 보여주기 위해서는 아래와 같이 작성하면 되지.

                <th:block sec:authorize="isAuthenticated()">
                    <!-- 인증 받음 -->
                    <a href="/logout" class="icons me-2 ms-2">로그아웃</a>
                </th:block>

logout 처리를 위해서는 SecurityConfig 클래스에 logout 처리 URL 을 지정해주면 돼.

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/process_login", "/signup").permitAll()
                        .anyRequest().authenticated()
                )
                .formLogin(form -> form
                        .loginPage("/login")
                        .loginProcessingUrl("/process_login")
                        .usernameParameter("email")

                        .permitAll()
                )
                .logout(logout -> logout
                        .logoutUrl("/logout")
                        .logoutSuccessUrl("/login")
                        .invalidateHttpSession(true)
                )
                .build();
    }

/logout URL 에 대한 핸들러는 LoginController 에 작성을 해줬어.

@Controller
public class LoginController {
   ...
    @GetMapping("/logout")
    public String logout(HttpServletRequest request, HttpServletResponse response) {
        new SecurityContextLogoutHandler().logout(request, response, SecurityContextHolder.getContext().getAuthentication());
        return "redirect:/login";
    }
}


이제 로그인한 사용자가 작성한 일기 데이터만 가져오도록 수정하는 일이 남았는데, GetAllDiaries 의 쿼리문을 수정하고, DiaryMapper 인터페이스의 함수에 파라미터를 추가해주면 될 것 같아.

DiaryMapper.xml
    <select id="GetAllDiaries" resultType="hashmap">
        select
            *
        from diary
        where
            email = #{email}
        order by
            diary_date desc
            , id desc
    </select>
DiaryMapper.java
@Mapper
public interface DiaryMapper {
    ...
    List<Map<String, Object>> GetAllDiaries(@Param("email") String email);
}
DiaryService.java
@Service
public class DiaryService {
    ...
    public List<Map<String, Object>> GetAllDiaries() {
        String email = SecurityContextHolder.getContext().getAuthentication().getName();
        return diaryMapper.GetAllDiaries(email);
    }
}
반응형