📍 Spring Security PrincipalDetails
- 스프링 시큐리티는 /login 주소 요청이 오면 해당 요청을 낚아채서 로그인을 진행시킬 수 있다.
=> WebSecurityConfigurerAdapter를 상속받아 configure을 override 한후 다음과 같이 작성한다.
@EnableWebSecurity // 해당 파일로 시큐리티를 활성화
@Configuration // IoC
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// super 삭제 - 기존 시큐리티가 가지고 있는 기능이 다 비활성화됨
// super.configure(http);
http.authorizeRequests()
.antMatchers("/","/user/**","/image/**","/subscribe/**","/comment/**","/api/**")
.authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/auth/signin") // GET
.loginProcessingUrl("/auth/signin") // POST -> 스프링 시큐리티가 로그인 프로세스 진행
.defaultSuccessUrl("/");
}
}
- antMatchers("주소").authenticated() : 해당 주소들을 로그인 하지 않은 상태로 접근 불가능
- anyRequest().permitAll() : 나머지 주소들을 접근 가능
- formLogin() : 스프링 시큐리티 인증 실행
- loginPage("/auth/signin") : "/auth/signin" 따로 만든 로그인 페이지로 가는 주소
- loginProcessingUrl("/auth/signin") : POST 방식으로 접근하면 로그인 프로세스가 진행
- defaultSuccessUrl("/") : 로그인 완료 후 이동되는 주소
"/auth/signin" 주소로 POST 방식의 데이터를 보내면 로그인 프로세스가 진행되므로
따로 컨트롤러를 만들 필요가 없다.
<form class="login__input" action="/auth/signin" method="post">
<input type="text" name="username" placeholder="유저네임" required="required" />
<input type="password" name="password" placeholder="비밀번호" required="required" />
<button>로그인</button>
</form>
📍UserDetails, UserDetailsService
단순히 스프링 시큐리티에서 로그인을 구현하면 username과 password 두 개만 받아오게된다.
따라서, UserDetails과 UserDetailsService를 상속받은 각각의 class를 생성하여 커스텀 해준다.
UserDetailsService 란?
Spring Security에서 유저의 정보를 가져오는 인터페이스이다.
userRepository에서 username으로 찾은 엔티티를 PrincipalDetails 엔티티로 만들어서 리턴해준다.
@RequiredArgsConstructor
@Service
public class PrincipalDetailsService implements UserDetailsService {
private final UserRepository userRepository;
// 1. 패스워드는 알아서 체킹하니까 신경쓸 필요 없다.
// 2. 리턴이 잘되면 자동으로 세션을 만든다.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userEntity = userRepository.findByUsername(username);
if(userEntity == null) {
return null;
} else {
return new PrincipalDetails(userEntity);
}
}
}
UserDetails 란?
Spring Security에서 사용자의 정보를 담는 인터페이스이다.
User를 받는 생성자를 만들고 Override된 메소드들을 알맞게 작성해준다.
@Data
public class PrincipalDetails implements UserDetails {
private User user;
public PrincipalDetails(User user) {
this.user = user;
}
// 권한 : 한개가 아닐 수 있음. (3개 이상의 권한)
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collection;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
📍 @AuthenticationPrincipal
- 로그인이 성공하면 컨트롤러에서 @AuthenticationPrincipal를 이용하여 세션에 저장된 로그인 정보를 사용할 수 있다.
- 사용예시 (프로필 정보 출력)
@GetMapping("/user/{pageUserId}")
public String profile(@PathVariable int pageUserId, Model model, @AuthenticationPrincipal PrincipalDetails principalDetails) {
UserProfileDto dto = userService.회원프로필(pageUserId, principalDetails.getUser().getId());
model.addAttribute("dto", dto);
if(pageUserId == principalDetails.getUser().getId()) {
model.addAttribute("check",true);
} else {
model.addAttribute("check", false);
}
return "user/profile";
}
@AuthenticationPrincipal PrincipalDetails principalDetails에서
아까 User를 받은 생성자가 있었으니 로그인 정보는 principalDetails.getUser()로 받을 수 있다.
📍 Mustache에서 문제 발생
header 부분에 로그인한 사용자 프로필페이지로 이동하는 버튼이 있어, 로그인한 사용자
즉, 세션 정보가 모든 페이지마다 필요하다.
그렇다면 위에 @AuthenticationPrincipal PrincipalDetails principalDetails를 모든 컨트롤러에서
사용해야 하는건가?
JSP는 태그 라이브러리를 사용하면 JSP에서 받아올 수 있지만 mustache는 비슷한 기능이 없다.
<sec:authorize access="isAuthenticated()">
<sec:authentication property="principal" var="principal"/>
</sec:authorize>
> 임시 작업
로그인시 HttpSession을 이용하여 추가로 userId 세션을 생성한다.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userEntity = userRepository.findByUsername(username);
if(userEntity == null) {
return null;
} else {
session.setAttribute("userId", userEntity.getId());
return new PrincipalDetails(userEntity);
}
}
그리고 mustache에서 세션 정보를 받아올 수 있도록
application.properties에 아래줄을 추가한다.
spring.mustache.expose-session-attributes=true
그러면 로그인시 어디서든 userId를 mustache에서 사용할 수 있다.
<header class="header">
<div class="container">
<a href="/" class="logo">
<img src="/images/logo.jpg" alt="">
</a>
<nav class="navi">
<ul class="navi-list">
<li class="navi-item"><a href="/">
<i class="fas fa-home"></i>
</a></li>
<li class="navi-item"><a href="/image/popular">
<i class="far fa-compass"></i>
</a></li>
<li class="navi-item"><a href="/user/{{userId}}">
<i class="far fa-user"></i>
</a></li>
</ul>
</nav>
</div>
</header>