1. @AuthenticationPrincipal 어노테이션이란 무엇인가?
스프링 시큐리티를 사용하여 사용자 인증을 처리하고 있는 경우,
@AuthenticationPrincipal 어노테이션을 통해 현재 인증된(로그인된) 사용자의 정보를 편리하게 가져와 사용할 수 있습니다.
@AuthenticationPrincipal은 스프링 프레임워크에서 사용되는 어노테이션 중 하나로, 사용자 인증 정보를 주입받기 위해 사용합니다.
주로 웹 애플리케이션에서 인증된(로그인한) 사용자의 정보를 Controller이나 Service 클래스에서 접근하고 활용하는 데 사용됩니다.
@AuthenticationPrincipal을 통해 매번 인증된 사용자의 정보를 DB에 접근해서 데이터를 가져오는 것을 막을 수 있습니다. 한 번 인증된 사용자 정보를 세션에 담아 놓고 세션이 유지되는 동안 User객체를 DB에 접근하지 않고 사용자의 정보를 사용할 수 있습니다.
Spring Security에서는 SecurityContextHolder 내부에 SecurityContext가 있고 이 컨텍스트 안에 Authentication 객체를 저장하고 있는데, 우리는 인증되어 저장된 Authentication 객체를 참조하여 사용하겠습니다.
Authentication 객체는 UserDetailsService를 구현한 클래스 'PrincipalUserDetailsService' 의 loadUserByUsername 메서드에서 반환해 준 값을 사용합니다.
반환되는 값은 UserDetail을 구현한 클래스 'PrincipalUserDetail'를 override한 여러 메서드와 User 엔티티를 통해서 인증된 사용자의 정보에 접근할 수 있습니다.
PrincipalUserDetailsService 클래스
@Service
@RequiredArgsConstructor
public class PrincipalUserDetailsService implements UserDetailService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
User principal = userRepository.findByUsername(username).orElseThrow(()->{
return new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다. : " +username);
});
return new PrincipalDetail(principal);
}
}
PrincipalDetail 클래스
@Data
public class PrincipalDetail implements UserDetails{
private User user; // Composition
public PrincipalDetail(User user) {
this.user = user;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
// 계정이 만료되지 않았는지 리턴한다. (true: 만료안됨)
@Override
public boolean isAccountNonExpired() {
return true;
}
// 계정이 잠겨있지 않았는지 리턴한다. (true: 잠기지 않음)
@Override
public boolean isAccountNonLocked() {
return true;
}
// 비밀번호가 만료되지 않았는지 리턴한다. (true: 만료안됨)
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// 계정이 활성화(사용가능)인지 리턴한다. (true: 활성화)
@Override
public boolean isEnabled() {
return true;
}
// 계정이 갖고있는 권한 목록을 리턴한다. (권한이 여러개 있을 수 있어서 루프를 돌아야 하는데 우리는 한개만)
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collectors = new ArrayList<>();
collectors.add(()->{ return "ROLE_"+user.getRole();});
return collectors;
}
}
UserController 에서 @AuthenticationPrincipal 적용하여 인증된 사용자 정보 받아오기
@Controller
public class UserController {
@GetMapping("/user/updateForm")
public String update(Model model, @AuthenticationPrincipal principalDetail principal){
model.addAttribute("principal", principal);
return "user/updateForm";
}
}
사용자 정보를 사용하는 HTML 파일
<div class="container">
<form class="m-4">
<input type="hidden" id="id" th:value="${principal.user.id}">
<div class="form-group">
<label for="username">Username</label>
<input th:value="${principal.user.username}" class="form-control" id="username" placeholder="Enter Username" type="text" readonly>
</div>
<div class="form-group">
<label for="password">Password</label>
<input th:readonly="${principal.user.oauth!=null ? 'readonly' : false}"
class="form-control" id="password" placeholder="Enter password" type="password">
</div>
<div class="form-group">
<label for="email">Email</label>
<input th:readonly="${principal.user.oauth!=null ? 'readonly' : false}"
th:value="${principal.user.email}" class="form-control" id="email" placeholder="Enter email" type="email">
</div>
</form>
<button id="btn-update" class="btn btn-primary mx-4 mb-2">회원 수정완료</button>
</div>
Controller에서 Model에 인증된 사용자의 정보를 저장하여 html 파일에서 Thymeleaf 문법을 통해 사용자 정보에 접근할 수 있습니다.
principal은 UserDetailService에서 생성한 UserDetail 객체입니다.
우리는 UserDetail 객체를 구현한 PrincipalUserDetail 클래스를 반환받아 사용자의 정보에 접근할 수 있습니다.
Override 한 함수 또는 private User user; 변수에 사용자의 정보가 저장되어 있으므로 principal.user를 통해 인증된 사용자의 User 엔티티로 접근하여 원하는 속성값에 접근할 수 있습니다.
2. Thymleaf 문법 sec:authorize 사용하기
sec:authorize는 Spring Security가 적용된 상태에서 인증된 사용자인지 아닌지를 구분하는 데에 있어 사용할 수 있습니다.
sec:authorize를 사용하기 위해서 thymeleaf view에 xml name spaec를 위 2가지를 모두 작성해야 합니다.
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
sec:authoirze 적용한 부분
로그인한 사용자에게는 '로그아웃' 버튼이 보이도록 / 로그인하지 않은 사용자에게는 '로그인' 버튼이 보이도록 하는 코드입니다.
<form class="form-inline my-2 my-lg-0" form-method="post" th:action="@{/logout}">
<button sec:authorize="isAuthenticated()" class="btn btn-outline-danger my-2 my-sm-0 btn-sm" type="submit">Logout</button>
<button sec:authorize="isAnonymous()" th:href="@{/login}" class="btn btn-outline-info my-2 my-sm-0 btn-sm" type="submit">Login</button>
</form>
- isAuthenticated( ) : 로그인한 사용자에게만 보인다.
- isAnonymous( ) : 로그인하지 않은 사용자에게만 보인다.
hasRole('ROLE_ADMIN') | 해당 권한이 있을 경우 |
hasAnyRole('ROLE_ADMIN,'ROLE_USER') | 포함된 권한 중 하나라도 있을 경우 |
isAuthenticated() | 권한에 관계없이 로그인 인증을 받은 경우 |
isFullyAuthenticated() | 권한에 관계없이 인증에 성공했고, 자동 로그인이 비활성인 경우 |
isAnonymous() | 권한이 없는 익명의 사용자일 경우 |
isRememberMe() | 자동 로그인을 사용하는 경우 |
permitAll | 모든 경우 출력함 |
denyAll | 모든 경우 출력하지 않음 |