HttpSession 객체가 생성되고 제거될 때 발생되는 이벤트인 HttpSessionEvent를 처리하는 리스너
HttpSession에는 다음과 같이 현재 접속한 클라이언트에 대한 정보를 담을 수 있다.
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,SecurityContextHolder.getContext());
따라서 다음과 같이 UserController의 login 부분에서 로그인 성공 시 세션을 생성하고 클라이언트에 대한 정보를 담는다.
@Api(tags = {"1.User"})
@RequiredArgsConstructor
@RestController
public class UserController {
@PostMapping("/login/form")
public JwtTokenDto login(@RequestBody UserLoginDto user, HttpSession session) {
User member = userRepository.findByUsername(user.getUsername())
.orElseThrow(() -> new IllegalStateException());
if (!passwordEncoder.matches(user.getPassword(), member.getPassword())) {
throw new IllegalArgumentException("잘못된 비밀번호입니다.");
}
// session
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
Authentication authenticate = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authenticate);
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
log.info("session made " + session.getId());
log.info(String.valueOf(session.isNew()));
// ...
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
UserDetails userDetails = (UserDetails) principal;
log.info("welcome" + ((UserDetails) principal).getUsername());
return jwtTokenDto;
}
}
User 객체는 다음과 같다. UserDetails 객체를 상속해 이루어져있다.
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 100, nullable = false, unique = true)
private String username;
@Column(length = 100, nullable = false)
private String password;
@ElementCollection(fetch = FetchType.EAGER)
@Builder.Default
private List<String> roles = new ArrayList<>();
@Column(length = 1000, nullable = false)
private String profileImg;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.roles.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
SecurityContext에 들어갈 수 있는 객체는 Authentication 타입 뿐이다.
Authentication 타입에 저장할 수 있는 객체는 UserDetails와 OAuth2User 타입이 있다.
회원 가입 프로세스를 직접 구현한 경우 UserDetails 타입 객체를 사용하고, 소셜 로그인 구현시에는 OAuth2User 타입 객체를 사용한다.
두개 다 사용하려면 UserDetails,OAuth2User를 implements 해서 PrincipalDetails를 만들어 사용하면 된다.
여기서는 소셜 로그인은 구현하지 않았기에, UserDetails 만 implements한 User 객체를 사용해 구현하였다.
현재 로그인한 사용자 정보(username)을 얻기위해서는 다음과 같다.
@PostMapping("/user/resource")
public String loginusername() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
UserDetails userDetails = (UserDetails) principal;
return ((UserDetails) principal).getUsername();
}
다시 본론으로 돌아와서, HttpSessionListener를 이용해 사용자 정보를 불러오는 과정은 다음과 같다.
@GetMapping("/onusers")
public OnUsersRes getCount() {
// return SessionUserCounter.getCount();
List<String> sessions = CustomHttpSessionListener.getSessions();
List<String>profiles = new ArrayList<>();
for (String session : sessions) {
profiles.add(userService.loadProfileImgByUsername(session));
}
return new OnUsersRes(profiles.subList(0,3), profiles.size()-3);
}
@Slf4j
@Component
public class CustomHttpSessionListener implements HttpSessionListener {
private static final Map<String, HttpSession> sessions = new ConcurrentHashMap<>();
@Override
public void sessionCreated(HttpSessionEvent event) {
HttpSession session = event.getSession();
sessions.put(session.getId(), session);
log.info("sessions - SESSION CREATED : {}", session.getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
HttpSession session = event.getSession();
sessions.remove(session.getId());
}
public static List<String> getSessions() {
Collection<HttpSession> values = sessions.values();
List<String> names = new ArrayList<>();
for (HttpSession value : values) {
SecurityContext springSecurityContextKey = (SecurityContext) value.getAttribute(SPRING_SECURITY_CONTEXT_KEY);
names.add(springSecurityContextKey.getAuthentication().getName());
}
return names;
}
public static List<String> getUsers() {
return null;
}
}
sessionCreated
세션이 생겼을 때 세션을 저장하는 map에 저장하고 sessionDestroyed
세션이 파기되면 map에서 remove 한다.
이를 통해 얻은 map을 getSessions()
를 이용해 조회하여 username list를 뽑는다.
뽑은 username list로 userRepository.findProfileImgByUsername()
를 이용해 profileImg URL을 가져온다.