UserDetailService Interface 를 상속 받는 CustomUserDetailService클래스를 만든다.
UserDetails(인터페이스)를 반환하는 loadUserByUsername
함수를 오버라이드 한다.
UserDetails 인터페이스를 직접 구현해서 커스텀하게 만들 수 있지만 Spring에서 UserDetails를 구현한 클래스 User를 제공한다
UserDetails를 구현한 User클래스를 상속 받는 AccountContext를 만든다
loadUserByUsername
함수에서 DB에서 유저 불러와서 가져온다음, AccountContext에 유저정보와 권한 정보를 넣어준다.
List<GrantedAuthority> roles = new ArrayList<>();
roles.add(new SimpleGrantedAuthority(account.getRole()));
@Service("userDetailsService")
public class CustomUserDetailService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = userRepository.findByUsername(username);
if (account == null) {
throw new UsernameNotFoundException("UsernameNotFoundException");
}
List<GrantedAuthority> roles = new ArrayList<>();
roles.add(new SimpleGrantedAuthority(account.getRole()));
AccountContext accountContext = new AccountContext(account, roles);
return accountContext;
}
}
AuthenticationProvider
인터페이스를 상속 받는다
authenticate
와 supports
메소드를 오버라이드 한다.
supports
메소드로 UsernamePasswordAuthenticationToken
이랑 파라미터로 전달 받은 Authentication 클래스와 같은 클래스 종류인지 판별하고
authenticate
에서 UserDetailService를 주입 받고 AccountContext객체를 가져온다음 검수 하고
UsernamePasswordAuthenticationToken
객체를 만들어 줘서 반환 시킨다.
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
AccountContext accountContext = (AccountContext) userDetailsService.loadUserByUsername(username);
if (!passwordEncoder.matches(password, accountContext.getAccount().getPassword())) {
throw new BadCredentialsException("BadCredentialsException");
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(accountContext.getAccount(), null, accountContext.getAuthorities());
return authenticationToken;
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
CustomUserDetailService
에서는 DB에 있는 정보를 가져와서 있는지 없는지 여부만 체크해 줬고 AccountContext를 반환 하였고, CustomAuthenticationProvider
에서는 CustomUserDetailService
를 활용해서 UsernamePasswordAuthentication
에서 주는 Authentication
객체와 DB에서 조회하였던 AccountContext
를 비교하여 로그인 처리를 해준다.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login_proc")
.defaultSuccessUrl("/")
.permitAll();
<li class="nav-item" sec:authorize="isAnonymous()">
<a class="nav-link text-light" th:href="@{/login}">로그인</a>
</li>
<li class="nav-item" sec:authorize="isAuthenticated()">
<a class="nav-link text-light" th:href="@{/logout}">로그아웃</a>
</li>
// LoginController.class
@GetMapping("/logout")
public String logout(HttpServletRequest request, HttpServletResponse response) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
new SecurityContextLogoutHandler().logout(request, response, authentication);
}
return "redirect:/login";
}
SecurityContextLogoutHandler
는 파라미터가 3개 필요하고, 로그인 되었던 정보는 SecurityContextHolder.getContext().getAuthentication()
에서 가져온다AuthenticationFilter
가WebAuthenticationDetails
를 활용하여 함, 그리고 WebAuthenticationDetails
는 AuthenticationDetailsSource
가 생성한다.AuthenticationFilter
가 Authentication
을 생성하고 기본적으로 id와 password가 저장된다. Authentication
는 내부적으로 details
라는 속성을 가지고 있고 Object 타입이다.(타입과 상관없이 어느 타입이든 저장 가능)WebAuthenticationDetails
객체를 만들어서 저장WebAuthenticationDetails
는 request라는 파라미터를 받아서 parameter값을 저장하고 details라는 속성에 저장된다(remoteAddress와 SessionId는 Security가 처리함)AuthenticationDetailsSource
가 만든다 WebAuthenticationDetails
를