gi_dor

로그아웃 - SpringSecurity, MySQL , MyBatis 본문

Back_End/SpringBoot

로그아웃 - SpringSecurity, MySQL , MyBatis

기돌 2024. 4. 17. 13:12
728x90

오늘은 지난번 시간에 이어서 스프링시큐리티로 로그아웃을 해보겠습니다.

로그인

@Service
@RequiredArgsConstructor
public class UserService implements UserDetailsService {

    private final UserMapper userMapper;
    private final PasswordEncoder passwordEncoder;
    /**
        * 주어진 사용자 아이디를 기준으로 사용자의 데이터를 가져와 UserDetails 객체로 반환합니다.
        * @param id 사용자 아이디
        * @return UserDetails 객체
        * @throws UsernameNotFoundException 주어진 아이디에 해당하는 사용자를 찾을 수 없는 경우 발생합니다.
    */
    @Override
    public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {

        // 사용자 아이디를 기준으로 데이터베이스에서 사용자 정보를 가져옵니다. 이 정보는 user 객체에 저장
        User user = userMapper.selectUserById(id);

        // 데이터베이스에서 가져온 사용자 정보가 없다면(null이면) 예외를 발생시킵니다.
        if(user == null) {
            throw new UsernameNotFoundException("Id 찾을수 없습니다 : " +id);
        }

        // UserDetailsImpl 클래스의 객체를 생성합니다. 이 객체는 사용자의 인증 및 권한 정보를 제공하기위해 사용한다
        UserDetailsImpl userDetails = new UserDetailsImpl();

        // 객체에서 가져온 사용자 아이디와 비밀번호를 userDetails 객체에 설정
        userDetails.setId(user.getId());
        userDetails.setPassword(user.getPassword());

        userDetails.setAuthorities(List.of(new SimpleGrantedAuthority("ROLE_USER")));

        System.out.println("로그인한 아이디 : " +user.getId());
        return userDetails;
    }
}

loadUserByUsernam 메서드는 주어진 사용자 아이디를 기준으로
selectByUserId( ) 를 사용해 사용자 정보를 데이터베이스에서 가져와야 합니다.

가져온 사용자 정보가 없으면 UsernameNotFoundException을 발생시킵니다

사용자 정보가 있으면 UserDetailsImpl 객체를 생성하여 해당 사용자의 아이디와 비밀번호를 설정합니다.
그리고 해당 사용자의 권한을 설정합니다.

마지막으로 로그인한 사용자의 아이디를 출력하고 UserDetails 객체를 반환합니다

사용자의 아이디를 기반으로 사용자를 인증하고 사용자 정보를 제공하는 메서드입니다.
스프링 시큐리티에서 사용되며, 사용자가 로그인할 때 호출되어 해당 사용자의 정보를 가져와 인증에 사용됩니다.


SecurityConfig

 // 폼기반 로그인을 활성화
// 사용자가 로그인 한다면 /user/login 경로로 이동하게되는데 로그인 성공시 ("/") 로 리다이렉트
http.formLogin(formLogin -> formLogin.loginPage("/user/login")
                            .usernameParameter("id")
                            .defaultSuccessUrl("/"));


// 로그아웃
// HTTP 세션을 무효화해서 로그인 상태를 제거한다
http.logout(logout -> logout.logoutUrl("/user/logout")
                            .logoutSuccessUrl("/")
                            .invalidateHttpSession(true));

각자 컨트롤러 매핑에 맞게 연결

<li class="nav-item">
    <a id="loginButton" class="nav-link" href="/user/login">로그인</a>
</li>
<li class="nav-item">
    <a id="logoutButton" class="nav-link"  href="/user/logout123">로그아웃</a>
</li>

💡 로그아웃 (1)

@Controller
@RequiredArgsConstructor
@RequestMapping("/user")
public class UserController {
    // 로그아웃
    @GetMapping("/logout123")
    public String logout() {
        Authentication user = SecurityContextHolder.getContext().getAuthentication();
        if (user != null) {
            // 로그아웃 처리
            SecurityContextHolder.getContext().setAuthentication(null);
        }
        return "redirect:/";
    }
}

원래는 Principal 을 사용해 사용자의 인증에 대해 정보를 가져오려고 했습니다

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
SecurityContextHolder를 사용하여 현재 사용자의 인증 객체를 가져옵니다.

인증여부 확인하기 : getAuthentication() 메서드는 반환하는 객체가 null이 아니라면, 사용자가 이미 인증되어 있음을 나타냅니다. 따라서 이를 통해 현재 사용자의 인증 상태를 확인할 수 있습니다

인증된 사용자의 정보 획득: getPrincipal() 메서드는 반환된 인증 객체를 통해 사용자의 정보를 얻을 수 있습니다.
예를 들어, 사용자의 아이디나 사용자 정보 객체를 가져와서 화면에 표시할수 있습니다

SecurityContextHolder.getContext() 현재 사용자의 인증 정보를 확인하거나 조작

Authentication 객체에는 사용자의 실제 인증 정보를 가지고있다.
예를 들어, 사용자의 아이디, 패스워드, 그리고 사용자가 가진 권한 등의 정보를 포함할 수 있습니다.

auth라는 변수를 통해 해당 인증 객체에 접근하고, 사용자의 인증 정보를 확인하거나 조작할 수 있습니다.
주로 로그인, 로그아웃, 인증 여부 확인 등의 작업에 사용

이 코드는 직접 로그아웃을 처리하기 위한 방식입니다

스프링 시큐리티에서는 기본적으로 /logout 엔드포인트를 제공하고 있으며, 스프링 시큐리티의 설정에 따라
자동으로 사용자를 로그아웃시킬 수 있습니다.

엔드포인트 - 웹 애플리케이션에서 클라이언트가 요청을 보낼 수 있는 URL 경로를 가리킨다

❓❓ 로그아웃 (2) - 실패

@Controller
@RequiredArgsConstructor
@RequestMapping("/user")
public class UserController {
    // 로그아웃
    @GetMapping("/logout123")
    public String logout(Principal principal) {
        Authentication user = SecurityContextHolder.getContext().getAuthentication();
        if (user != null) {
            // 로그아웃 처리
             SecurityContextHolder.clearContext(); // 인증 객체를 모두 제거
        }
        return "redirect:/";
    }
}
SecurityContextHolder.clearContext(); // 인증 객체를 모두 제거

SecurityContextHolder.clearContext() 메서드를 호출하면 보안 컨텍스트에 저장된 현재 사용자의 인증 정보가 모두 제거되므로 사용자는 다시 인증되지 않은 상태가 된다고 합니다

마이페이지로 이동해 로그인을 한 페이지 입니다

로그아웃을 클릭해 메인페이지 " / " 로 이동한 화면입니다

SecurityContextHolder.clearContext(); 를 사용해서 로그아웃이 되는 것은 확인했지만

다시 마이페이지를 클릭해보니 로그인이 되어 있는 상태가 발생했습니다 .
정확히 무슨 문제인지는 아직 확인하지 못했습니다

로그아웃 (3) - LogoutFilter

  • 로그아웃 요청 감지하기 , logoutUrl 메서드에 지정한 url 로 온 요청하는지 확인
  • 맞다면 SecurityContext 에서 인증 객체를 꺼냄
  • 그것을 SecurityContextLogoutHandler 에 전달
  • 인증정보를 제거 한다 세션만료 , 인증객체 초기화 , 쿠키삭제 , SecurityContext (사용자 인증 정보) 삭제를 수행
  • 로그아웃이 다 끝나면 SimpleUrlLogoutSuccessHandler 호출하여 로그인 페이지 이동

 

위에있던 SpringConfig 기억하시나요 ?

// 로그아웃
// HTTP 세션을 무효화해서 로그인 상태를 제거한다
http.logout(logout -> logout
    .logoutUrl("/user/logout") // 로그아웃 요청 감지하기, logoutUrl 메서드에 지정한 URL로 온 요청하는지 확인
    .logoutSuccessUrl("/")     // 로그아웃 성공 후에 지정된 URL로 리다이렉트
    .invalidateHttpSession(true)); // 세션 만료하여 인증 객체 제거

해당 설정으로 인해 컨트롤러의 메서드를 만들지 않고 쉽게 로그아웃기능을 만들수 있습니다

SpringConfig 에 정의한 logoutUrl 과 navbar에 url 을 맞추겠습니다

<li class="nav-item">
    <a id="loginButton" class="nav-link" href="/user/login">로그인</a>
</li>
<li class="nav-item">
    <a id="logoutButton" class="nav-link"  href="/user/logout">로그아웃</a>
</li>

로그인한 상태와 , 로그아웃한 상태 구분을 위해 타임리프 에서 사용하는 시큐리티 표현식을 사용하겠습니다

<li class="nav-item"  sec:authorize="isAnonymous()">
    <a id="loginButton" class="nav-link"  href="/user/login">로그인</a>
</li>
<li class="nav-item"  sec:authorize="isAuthenticated()">
    <a id="logoutButton" class="nav-link"  href="/user/logout">로그아웃</a>
</li>
<li class="nav-item" sec:authorize="isAnonymous()">
    <a class="nav-link" href="/user/register">회원가입</a>
</li>

 

sec:authorize

Thymeleaf에서 사용되는 시큐리티 표현식입니다.
사용자의 인증 상태를 확인하고 해당 상태에 따라 렌더링되는 HTML 요소를 제어할 수 있습니다.

sec:authorize="isAnonymous()": 현재 사용자가 인증되지 않은 상태(익명 상태)인 경우에만 해당 요소가 보입니다
로그인하지 않은 사용자에게만 보이는 요소입니다.

sec:authorize="isAuthenticated()"** 현재 사용자가 인증된 상태인 경우에만 해당 요소가 보입니다
로그인한 사용자에게만 보이는 요소입니다.

sec:authorize 로 인해 사용자의 인증 상태에 따라 다른 HTML 요소인 로그인, 로그아웃 등의 기능을 구현할 수 있습니다


SecurityContext

public interface SecurityContext extends Serializable {

    /**
     * Obtains the currently authenticated principal, or an authentication request token.
     * @return the <code>Authentication</code> or <code>null</code> if no authentication
     * information is available
     */
    Authentication getAuthentication();

    /**
     * Changes the currently authenticated principal, or removes the authentication
     * information.
     * @param authentication the new <code>Authentication</code> token, or
     * <code>null</code> if no further authentication information should be stored
     */
    void setAuthentication(Authentication authentication);

}

SecurityContextLogoutHandler

public class SecurityContextLogoutHandler implements LogoutHandler {

    protected final Log logger = LogFactory.getLog(this.getClass());

    private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
        .getContextHolderStrategy();

    private boolean invalidateHttpSession = true;

    private boolean clearAuthentication = true;

    private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();

SimpleUrlLogoutSuccessHandler

public class SimpleUrlLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler
       implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
          throws IOException, ServletException {
       super.handle(request, response, authentication);
    }

}

SimpleUrlLogoutSuccessHandler 클래스는 스프링 시큐리티에서 로그아웃 성공 후에 실행될 동작을 구현하는 클래스

  • AbstractAuthenticationTargetUrlRequestHandler 클래스는 요청을 특정 URL로 리다이렉트하는 기능을 제공
  • LogoutSuccessHandler 인터페이스는 로그아웃이 성공했을 때 호출되는 메서드를 정의합니다.
  • onLogoutSuccess 메서드는 로그아웃이 성공했을 때 호출되며, 요청과 응답 객체를 받아서 로그아웃 후 동작을 처리일반적으로는 요청을 특정 URL로 리다이렉트하는 역할을 합니다.

▶ SimpleUrlLogoutSuccessHandler 클래스를 사용하면
로그아웃이 성공한 후에 특정 URL로 이동하는 동작을 손쉽게 구현할 수 있습니다.

 

 

참고 사이트

https://velog.io/@dailylifecoding/spring-security-logout-feature

 

[Spring Security] Logout 처리

로그아웃 처리

velog.io

 

https://velog.io/@shdrnrhd113/spring-Security-3.-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%A1%9C%EA%B7%B8%EC%95%84%EC%9B%83

 

spring Security 3. - 로그인, 로그아웃

log-in 기능 추가지난번에 설정했던 접근 권한에 and() 로 기능을 추가해주면 된다.loginPage/user/login 로 오는 요청은 로그인 이라는 뜻여기서 말하는 요청 url 은 POST 를 뜻한다.defaultSuccessUrl로그인이

velog.io

 

728x90