Spring Security-현재 로그인 중인 사용자 프로필 나타내기

정 승 연·2023년 3월 30일
0

목록 보기
7/9

HttpSessionListener를 이용해 현재 로그인 중인 사용자 프로필 나타내기.

HttpSessionListener

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을 가져온다.

0개의 댓글