로그인/로그아웃 화면 연동
로그인시 화면 VS 로그아웃시 화면
📍 로그인 시 화면에는 우측 상단 메뉴에 '로그아웃 | 장바구니 | 주문목록' 이 나옴
+ 관리자로 로그인 시에는 '상품등록 | 상품관리' 메뉴가 추가로 나오도록 함!
+ 로그인 후에는 회원가입 창이 접속 안되도록 설정
📍 로그아웃 시 화면에는 우측 상단 메뉴에 '로그인 | 회원가입' 메뉴만 나오도록 함.
💡 thymeleaf-extras-security6는 Thymeleaf와 Spring Security를 통합하여, Thymeleaf 템플릿에서 Spring Security의 기능을 손쉽게 사용할 수 있게 해주는 라이브러리. 이 라이브러리는 Spring Security 6 버전과 함께 사용할 수 있도록 설계되었으며, Thymeleaf 템플릿에서 보안 관련 기능을 보다 쉽게 구현할 수 있게 도와줌.
⚡️ header.html 에 네비게이션 바가 있고 메뉴 목록이 들어 있으므로 이 코드 안에 설정함!
- Spring Security 사용 위해 네임스페이스 정해줘야 함. 'xmlns:sec="http://www.thymeleaf.org/extras/spring-security'
<div id="nav_item">
<div>
<ul class="navbar-nav justify-content-end me-3">
<li class="nav-item"
sec:authorize="hasAnyAuthority('ROLE_ADMIN')">
<a class="nav-link text-muted" href="/admin/item/new"
style="font-size: small;">✓ 상품등록</a>
</li>
<li class="nav-item"
sec:authorize="hasAnyAuthority('ROLE_ADMIN')">
<a class="nav-link text-muted" href="/admin/items"
style="font-size: small;">✓ 상품관리</a>
</li>
</ul>
</div>
<div>
<ul class="navbar-nav justify-content-end me-3">
<li class="nav-item" sec:authorize="isAnonymous()">
<a class="nav-link text-dark" href="/members/new">
<i class="bi bi-person-fill text-dark me-1"></i>Sign Up</a>
</li>
<li class="nav-item" sec:authorize="isAnonymous()">
<a class="nav-link text-dark" href="/members/login">
<i class="bi bi-box-arrow-in-right text-dark me-2"></i>Login</a>
</li>
<li class="nav-item" sec:authorize="isAuthenticated()">
<a class="nav-link text-dark" href="/members/logout">
<i class="bi bi-box-arrow-in-right text-dark me-2"></i>Logout</a>
</li>
<li class="nav-item" sec:authorize="isAuthenticated()">
<a class="nav-link text-dark" href="/cart">
<i class="bi bi-bag-heart-fill text-dark me-1"></i>Cart</a>
</li>
<li class="nav-item" sec:authorize="isAuthenticated()">
<a class="nav-link text-dark" href="/orders">
<i class="bi bi-clipboard2-heart-fill me-1"></i>Order</a>
</li>
</ul>
</div>
</div>
sec:authentication 속성
Thymeleaf의 Spring Security 통합을 위해 제공되며, sec 네임스페이스를 사용하여 인증된 사용자에 대한 정보를 가져오는 데 도움을 준다.
• sec:authentication="principal.username": 현재 인증된 사용자의 사용자 이름을 템플릿에서 가져옴
• sec:authentication="principal.authorities": 현재 인증된 사용자의 권한 목록을 템플릿에서 가져옴
sec:authorize 속성
Thymeleaf 템플릿에서 Spring Security와 통합하여 콘텐츠의 표시 여부를 조건부로 제어하기 위해 사용되는 속성. 이 속성을 사용하면 특정 조건에 따라 HTML 요소를 표시하거나 숨길 수 있다.
- isAuthenticated(): 사용자가 인증된 상태인 경우에만 콘텐츠를 표시
- isAnonymous(): 사용자가 익명(비인증) 상태인 경우에만 콘텐츠를 표시
- hasRole('ROLE_NAME'): 사용자가 특정 역할을 가지고 있는 경우에만 콘텐츠를 표시
- hasAnyRole('ROLE1', 'ROLE2'): 사용자가 특정 역할 중 하나라도 가지고 있는 경우에만 콘텐츠를 표시
- hasAuthority('AUTHORITY'): 사용자가 특정 권한을 가지고 있는 경우에만 콘텐츠를 표시
- hasAnyAuthority('AUTHORITY1', 'AUTHORITY2'): 사용자가 특정 권한 중 하나라도 가지고 있는 경우에만 콘텐츠를 표시
+ 로그인 시 회원가입 페이지 접속을 막아두고 싶어서 controller에 추가 설정 함
@GetMapping("/new")
public String memberForm(Model model, Principal principal) {
if (principal != null) {
// 로그인된 사용자가 접근 시 다른 페이지로 리디렉션
return "redirect:/";
}
model.addAttribute("memberFormDTO", new MemberFormDTO());
return "member/memberForm";
}
Principal 객체 ?
스프링 시큐리티에서 현재 인증된 사용자를 나타내는 객체. 로그인을 해야만 생성되는 객체이기 때문에 로그인하지 않은 상태에서 사용하면 null값이 들어가서 오류가 발생.
페이지 권한 설정
1. ADMIN (관리자) 계정만 접근할 수 있는 상품 등록 페이지를 생성
- item 패키지 아래 itemForm.html 생성
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http:www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layouts/layout1}">
<head>
<meta charset="UTF-8">
</head>
<body>
<div layout:fragment="content">
<h1> 상품 등록 페이지 입니다.</h1>
</div>
</body>
</html>
2. ItemController 생성
@Controller
public class ItemController {
@GetMapping("/admin/item/new")
public String itemForm(){
return "/item/itemForm";
}
}
3. CustomAuthenticationEntryPoint 클래스 생성
- ajax 경우 http request header에 XMLHttpRequest 라는 값이 세팅되어 요청이 오는데, 인증되지 않은 사용자가 ajax로 리소스를 요청할 경우 "Unauthorized" 에러를 발생시키고 나머지 경우 로그인 페이지로 리다이렉트 시켜준다.
⚡️ AuthenticationEntryPoint 란?
인증이 필요한 리소스에 접근할 때 인증되지 않은 사용자를 처리하는 방법을 정의하는 인터페이스. 일반적으로 AuthenticationEntryPoint는 사용자에게 로그인 페이지로 리다이렉트하거나, JSON 응답을 반환하는 등의 작업을 수행
@Log4j2
public class CustomAuthenticationEntryPoint
implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException)
throws IOException, ServletException {
if ("XMLHttpRequest".equals(request.getHeader("x-requested-with"))) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
} else {
response.sendRedirect("/members/login");
}
}
}
http.authorizeHttpRequests(configurer ->
configurer
.requestMatchers("/h2-console/**").permitAll() // H2 콘솔에 대한 접근 허용
.requestMatchers("/", "/members/**", "/item/**", "/images/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/css/**", "/js/**", "/img/**").permitAll()
.anyRequest().authenticated() // 나머지 요청에 대해 인증 필요
);
// Custom AuthenticationEntryPoint 설정
http.exceptionHandling(exceptionHandling ->
exceptionHandling
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
);
- 모든 사용자가 인증 없이 접근 가능한 곳과 특정 계정만 접근할 수 있는 곳 설정함.
- 인증되지 않은 사용자가 리소스에 접근했을 때 수행되는 핸들러 설정함.
4. 테스트 코드 작성
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(locations = "classpath:application-test.properties")
class ItemControllerTest {
@Autowired
MockMvc mockMvc;
@Test
@DisplayName("상품 등록 페이지 권한 테스트")
@WithMockUser(username = "admin", roles = "ADMIN")
public void itemFormTest() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/admin/item/new"))
.andDo(print())
.andExpect(status().isOk());
}
@Test
@DisplayName("상품 등록 페이지 일반회원 접근 테스트")
@WithMockUser(username = "user", roles = "USER")
public void itemFormNotAdminTest() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/admin/item/new"))
.andDo(print())
.andExpect(status().isForbidden());
}
}
'Spring & Spring Boot' 카테고리의 다른 글
Spring Boot 쇼핑몰 프로젝트 | 상품 등록 (0) | 2024.08.04 |
---|---|
Spring Boot 쇼핑몰 프로젝트 | 엔티티 연관 관계 매핑, 엔티티 공통 속성 공통화 (0) | 2024.08.02 |
Spring Boot 쇼핑몰 프로젝트 | 로그인 기능 구현 (0) | 2024.08.01 |
Spring Boot 쇼핑몰 프로젝트 | 회원가입 구현 (0) | 2024.07.31 |
JpaRepository method, Query method (0) | 2024.07.31 |