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);
}
}