로그인/로그아웃 화면 연동


로그인시 화면 VS 로그아웃시 화면

 

  📍 로그인 시 화면에는 우측 상단 메뉴에 '로그아웃 | 장바구니 | 주문목록' 이 나옴

       + 관리자로 로그인 시에는 '상품등록 | 상품관리' 메뉴가 추가로 나오도록 함!

       + 로그인 후에는 회원가입 창이 접속 안되도록 설정

 

  📍 로그아웃 시 화면에는 우측 상단 메뉴에 '로그인 | 회원가입' 메뉴만 나오도록 함.

 

💡  thymeleaf-extras-security6는 Thymeleaf와 Spring Security를 통합하여, Thymeleaf 템플릿에서 Spring Security의 기능을 손쉽게 사용할 수 있게 해주는 라이브러리. 이 라이브러리는 Spring Security 6 버전과 함께 사용할 수 있도록 설계되었으며, Thymeleaf 템플릿에서 보안 관련 기능을 보다 쉽게 구현할 수 있게 도와줌.

 

 

⚡️ header.html 에 네비게이션 바가 있고 메뉴 목록이 들어 있으므로 이 코드 안에 설정함!

<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 요소를 표시하거나 숨길 수 있다.

 

  1. isAuthenticated(): 사용자가 인증된 상태인 경우에만 콘텐츠를 표시
  2. isAnonymous(): 사용자가 익명(비인증) 상태인 경우에만 콘텐츠를 표시
  3. hasRole('ROLE_NAME'): 사용자가 특정 역할을 가지고 있는 경우에만 콘텐츠를 표시
  4. hasAnyRole('ROLE1', 'ROLE2'): 사용자가 특정 역할 중 하나라도 가지고 있는 경우에만 콘텐츠를 표시
  5. hasAuthority('AUTHORITY'): 사용자가 특정 권한을 가지고 있는 경우에만 콘텐츠를 표시
  6. 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());
    }
}

 

 

 

+ Recent posts