개요

웹 사이트에 로그인하지 않았을 때는 상단 헤더에 로그인 버튼이 보이고 로그인을 하면 로그아웃 버튼으로 바뀌거나 내 정보를 보여준다. 어떻게 구현하는 걸까? JSP부터 Thymeleaf까지 정리해보자.

JSP와 시큐리티

방법 1

컨트롤러에서 유저의 인증정보를 사용하기 위해서는 Principal을 메소드 인자로 받아야한다. 여기에는 getName이라는 메소드가 있다.
getName은 위에서 설정한 Username값을 담고있다.
그럼 이 값을 받아서 MVC컨트롤러의 model에 추가하여 사용할 수 있다.

@GetMapping("/test")
public String test(Principal principal, Model model) {
  String username = principal.getName();
  model.addAttribute("username", username);

  return "test";
}
<h1> Username: ${username}</h1>

유저의 정보를 단순한 String으로 뷰에 넘겨주는 방식으로 시큐리티와 뷰가 연관되어작동하지는 않는다.
만약 추가적인 정보가 더 필요하다면 컨트롤러에서 username을 이용해서 데이터베이스를 조회하고 별도의 객체에 담아서 넘겨주는 식으로 사용할 수도 있다.

방법 2

시큐리티 태그 라이브러리를 불러온다.

<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec"%>

그런 다음 이렇게 사용하면 된다.

<h1>Principal : <sec:authentication property="principal"/></h1>

만약 로그인을 한 유저와 그렇지 않은 유저에게 화면을 다르게 적용하고 싶다면 이렇게 하면 된다.

<sec:authorize access="isAnonymous()">
  비회원
</sec:authorize>

<sec:authorize access="isAuthenticated()">
  회원
</sec:authorize>

이렇게 작성하면 된다.

위의 두 코드를 합치면 이렇게 활용할 수 있다.

<sec:authorize access="isAnonymous()">
  <a href="/login">로그인</a><br>
  <a href="/join">회원가입</a>
</sec:authorize>

<sec:authorize access="isAuthenticated()">
  <a href="/userinfo?email=<sec:authentication property="principal.username"/>"><sec:authentication property="principal.username"/></a>
</sec:authorize>

이 외에도 다양한 조건이 있다.

표현식 의미
hasRole([role]) 현재 principal이 명시된 role을 가진다면 true를 리턴함
hasAnyRole([role1, role2]) 현재 principal이 명시된 role들 중 하나 이상을 가진다면 true를 리턴함
hasAuthority([authority]) 현재 principal이 명시된 권한을 가진다면 true를 리턴함
hasAnyAuthority([authority1, authority2]) 현재 principal이 명시된 권한 중 하나 이상을 가진다면 true를 리턴함
principal 현재 로그인한 유저의 정보를 담고 있는 principal 객체에 접근
authentication SecurityContext로부터 Authentication 객체에 접근
permitAll 모든 사용자에게 허용
denyAll 모든 사용자에게 거부
isAnonymous() 인증 정보가 없는 경우 true를 리턴함
isRememberMe() remember-me 사용 유저라면 true를 리턴
isAuthenticated() 익명 사용자가 아니라면 true를 리턴
isFullyAuthenticated() remember-me와 익명 사용자에 해당하지 않으면 true를 리턴
hasPermission(Object target, Object permission) 유저가 대상에 해당하는 권한이 있다면 true를 리턴
hasPermission(Object targetId, String targetType, Object permission) 유저가 타겟에 해당하는 권한이 있다면 true를 리턴

이들은 Spring Security의 체크 애노테이션을 JSP에서도 사용할 수 있도록 내장된 표현식으로 여기 나온 기능은 컨트롤러에서도 사용할 수 있다.

예를 들면 이렇다.

@PreAuthorize("hasRole('USER')")
public String test() {
// ...
}

자세한 내용은 공식문서에서 확인할 수 있다.
Spring Security Docs

Thymeleaf

스프링부트에서는 JSP대신 Thymeleaf를 기본 템플릿 엔진으로 사용한다. 스프링 부트 프로젝트를 만들 때 의존성으로 Thymeleaf를 추가해서 사용하면 된다.

타임리프에는 일종의 히든 옵션이 존재한다. 바로 thymeleaf-extras-springsecurity6로 타임리프만 추가했을 때는 보이지 않고 타임리프와 시큐리티를 같이 의존성에 추가하면 생기는 의존성이다.

이 의존성은 위에서 살펴본 Spring Security 태그 라이브러리와 같은 기능을 사용할 수 있게 해주는 의. 만약 시큐리티와 뷰를 연계해서 시큐리티 정보를 사용하고싶다면 이 의존성을 사용하면 된다.

의존성을 추가해줬다면 html템플릿에서 <html lang="ko" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml">코드를 추가해준다.

JSP를 작성할 때와 동일한 방법으로 사용할 수 있는데 다소 다른 점은 태그 형식으로 사용하는 것이 아니라 태그 내에 속성을 지정하듯 사용한다는 것이다.
예를 들어 로그인한 유저의 정보를 사용하고싶다면 이렇게 할 수 있다.

<h1 sec:authentication="name"></h1>
<h1 sec:authentication="principal.authorities"></h1>

로그인한 유저와 로그인하지 않은 유저에게 다른 문구를 보여주는 건 이렇게 할 수 있다.

<a href="/login" sec:authorize="!isAuthenticated()">로그인</a><br>
<a href="/join" sec:authorize="!isAuthenticated()">회원가입</a>
<a href="/userinfo" sec:authorize="isAuthenticated()" sec:authentication="name"></a>