기본 콘텐츠로 건너뛰기

[Spring Security] SecurityContext로 강제 로그인 처리하기 (SSO·백오피스 연동 팁)

[Spring Security] SecurityContext로 강제로 로그인 처리하기 (SSO·백오피스 연동)

[Spring Security] SecurityContext로 강제로 로그인 처리하기 (SSO·백오피스 연동)

내부 시스템이나 SSO 연동, 또는 이미 별도 인증 과정을 거친 뒤 Spring Security 세션만 강제로 만들어야 하는 상황이 종종 있습니다.
이 글에서는 SecurityContextUsernamePasswordAuthenticationToken을 사용해 프로그램적으로 로그인(강제 로그인) 처리하는 방법을 예제와 함께 정리합니다.


1. 왜 강제 로그인(프로그램 로그인)이 필요할까?

대표적인 사용 시나리오는 다음과 같습니다.

  • 사내 SSO 서버에서 이미 인증을 완료했고, 결과로 내려온 ID로만 Spring Security 컨텍스트를 세팅하고 싶을 때
  • 별도 로그인 페이지 없이, 백오피스·관리자 화면에서 바로 내부 계정으로 진입시켜야 할 때
  • 테스트·운영 도구 등에서 특정 계정으로 자동 로그인이 필요할 때

⚠️ 단, 이런 강제 로그인은 보안적으로 매우 주의해야 합니다.
외부에서 이 로직에 접근할 수 없도록 IP 제한, 별도 권한 체크, 토큰 검증 등을 반드시 거쳐야 합니다.

2. 기존 강제 로그인 코드 이해하기

질문에서 주신 예제 코드를 조금 다듬어서 먼저 살펴보겠습니다.

2-1. 예제 코드


SecurityContext securityContext = SecurityContextHolder.getContext();
UsernamePasswordAuthenticationToken result = null;
List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();
String login_id = "!!";

try {

    CustomUserDetailsVO userInfo = loginMapper.getSSOUser(new CustomUserDetailsVO(login_id));
    
    if (userInfo == null) {
        throw new BadCredentialsException("등록된 아이디 없음");
    } else {

        // 기본 ROLE_USER 부여
        roles.add(new SimpleGrantedAuthority("ROLE_USER"));

        // 인증 토큰 생성 (principal: 로그인 ID, credentials: 생략용 더미 값)
        result = new UsernamePasswordAuthenticationToken(login_id, "A", roles);

        // UserDetails 같은 추가 정보 세팅
        result.setDetails(userInfo);

        // HttpSession에 사용자 이름 저장 (예: 화면에서 사용)
        request.getSession().setAttribute("username", userInfo.getRecher_nm());
    }

} catch (NullPointerException e) {
    e.printStackTrace();
    throw new BadCredentialsException(e.getMessage());
}

// SecurityContext에 인증 정보 저장
securityContext.setAuthentication(result);

// 세션에 SPRING_SECURITY_CONTEXT 키로 저장하면 이후 요청에서도 로그인 상태 유지
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
    

2-2. 코드 흐름 설명

  • loginMapper.getSSOUser(...) : SSO 혹은 DB에서 사용자 정보 조회
  • userInfo == null : 등록되지 않은 계정이면 BadCredentialsException 발생
  • roles.add(new SimpleGrantedAuthority("ROLE_USER")) : 로그인 사용자에게 ROLE_USER 권한 부여
  • new UsernamePasswordAuthenticationToken(login_id, "A", roles) : 인증된 사용자 토큰 생성
  • securityContext.setAuthentication(result) : 현재 스레드의 SecurityContext에 인증 정보 저장
  • session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext) : HttpSession에 저장하여 다음 요청에서도 로그인 상태 유지

3. 권장되는 강제 로그인 전체 예제

실무에서는 보통 Controller, Filter, 또는 Service 레벨에서 강제 로그인을 처리합니다.
아래는 HttpServletRequest를 이용해 Spring Security 세션을 세팅하는 예제입니다.

3-1. 강제 로그인 유틸 메서드 예제


import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;

public class SecurityLoginUtil {

    public static void forceLogin(HttpServletRequest request,
                                 CustomUserDetailsVO userInfo) {

        // 1) 권한 목록 구성
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));

        // 필요 시 DB에서 ROLE_ADMIN, ROLE_MANAGER 등 동적으로 가져와 추가 가능
        // authorities.add(new SimpleGrantedAuthority(userInfo.getRole()));

        // 2) principal로 UserDetails 또는 loginId 사용
        UserDetails principal = userInfo; // CustomUserDetailsVO가 UserDetails 구현했다고 가정

        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(principal, null, authorities);

        authentication.setDetails(userInfo);

        // 3) SecurityContext 생성 및 설정
        SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
        securityContext.setAuthentication(authentication);

        // 4) HttpSession에 SPRING_SECURITY_CONTEXT 저장
        HttpSession session = request.getSession(true);
        session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);

        // 필요 시 별도 세션 값 저장 (예: 화면에 표시용 이름)
        session.setAttribute("username", userInfo.getRecher_nm());
    }
}
    

3-2. Controller에서 호출하는 예제

SSO 콜백 혹은 내부 인증 후 콜백 URL에서 다음과 같이 사용할 수 있습니다.


@GetMapping("/sso/callback")
public String ssoCallback(@RequestParam("token") String token,
                          HttpServletRequest request) {

    // 1) 토큰 검증 및 사용자 정보 조회 (예시)
    String loginId = ssoService.verifyAndGetLoginId(token);
    CustomUserDetailsVO userInfo = loginMapper.getSSOUser(new CustomUserDetailsVO(loginId));

    if (userInfo == null) {
        throw new BadCredentialsException("등록된 아이디 없음");
    }

    // 2) 강제 로그인 처리
    SecurityLoginUtil.forceLogin(request, userInfo);

    // 3) 로그인 후 이동할 URL로 리다이렉트
    return "redirect:/main";
}
    

4. 강제 로그인 구현 시 주의할 점

  • 1) 반드시 인증된 요청에서만 사용 외부에서 직접 이 URL을 호출해 아무나 강제 로그인할 수 없도록 IP 제한, 토큰 검증, 추가 서명 체크 등이 필요합니다.
  • 2) 계정 도용·권한 상승 취약점 주의 단순히 login_id만 넘겨받고 검증 없이 SecurityContext를 세팅하면, 공격자가 다른 ID로 로그인 상태를 만들 수 있습니다.
  • 3) 세션 고정(Session Fixation) 공격 방어 강제 로그인 직전에 기존 세션을 무효화하고 새 세션을 생성하는 것이 안전합니다.
  • 4) ROLE 관리 예제에서는 ROLE_USER만 넣었지만, 실제로는 DB에서 권한 목록을 조회해 넣는 게 좋습니다.

5. 정리: SecurityContext를 이용한 Spring Security 강제 로그인

  • loginMapper.getSSOUser() 등을 통해 사용자 정보를 조회한다.
  • UsernamePasswordAuthenticationToken을 생성해 권한(ROLE)과 principal을 세팅한다.
  • SecurityContextsetAuthentication()으로 인증 정보를 넣는다.
  • 마지막으로 HttpSessionSPRING_SECURITY_CONTEXT를 저장하면 이후 요청에서도 Spring Security가 로그인된 사용자로 인식한다.
  • 단, 이런 강제 로그인 로직은 항상 보안 검증을 충분히 한 뒤 내부용으로만 사용해야 한다.

위 예제를 참고해서, SSO 연동이나 내부 시스템간 연동 시 안전하게 Spring Security 로그인 세션을 직접 생성하는 패턴으로 활용해보세요.

© 2025 칼퇴하는 개발자. Spring Security 강제 로그인 패턴 정리.

댓글

이 블로그의 인기 게시물

Java Servlet Request Parameter 완전 정복 — GET/POST 모든 파라미터 확인 & 디버깅 예제 (Request Parameter 전체보기)

Java Servlet Request Parameter 완전 정복 — GET/POST 모든 파라미터 확인 & 디버깅 예제 Java Servlet Request Parameter 완전 정복 웹 애플리케이션에서 클라이언트로부터 전달되는 Request Parameter 를 확인하는 것은 필수입니다. 이 글에서는 Java Servlet 과 JSP 에서 GET/POST 요청 파라미터를 전체 출력하고 디버깅하는 방법을 다양한 예제와 함께 소개합니다. 1. 기본 예제: getParameterNames() 사용 Enumeration<String> params = request.getParameterNames(); System.out.println("----------------------------"); while (params.hasMoreElements()){ String name = params.nextElement(); System.out.println(name + " : " + request.getParameter(name)); } System.out.println("----------------------------"); 위 코드는 요청에 포함된 모든 파라미터 이름과 값을 출력하는 기본 방법입니다. 2. HTML Form과 연동 예제 <form action="CheckParamsServlet" method="post"> 이름: <input type="text" name="username"><br> 이메일: <input type="email" name="email"><b...

PostgreSQL 달력(일별,월별)

SQL 팁: GENERATE_SERIES로 일별, 월별 날짜 목록 만들기 SQL 팁: GENERATE_SERIES 로 일별, 월별 날짜 목록 만들기 데이터베이스에서 통계 리포트를 작성하거나 비어있는 날짜 데이터를 채워야 할 때, 특정 기간의 날짜 목록이 필요할 수 있습니다. PostgreSQL과 같은 데이터베이스에서는 GENERATE_SERIES 함수를 사용하여 이 작업을 매우 간단하게 처리할 수 있습니다. 1. 🗓️ 일별 날짜 목록 생성하기 2020년 1월 1일부터 12월 31일까지의 모든 날짜를 '1 day' 간격으로 생성하는 쿼리입니다. WITH date_series AS ( SELECT DATE(GENERATE_SERIES( TO_DATE('2020-01-01', 'YYYY-MM-DD'), TO_DATE('2020-12-31', 'YYYY-MM-DD'), '1 day' )) AS DATE ) SELECT DATE FROM date_series 이 쿼리는 WITH 절(CTE)을 사용하여 date_series 라는 임시 테이블을 만들고, GENERATE_SERIES 함수로 날짜를 채웁니다. 결과 (일별 출력) 2. 📅 월별 날짜 목록 생성하기 동일한 원리로, 간격을 '1 MONTH' 로 변경하면 월별 목록을 생성할 수 있습니다. TO...

CSS로 레이어 팝업 화면 가운데 정렬하는 방법 (top·left·transform 완전 정리)

레이어 팝업 센터 정렬, 이 코드만 알면 끝 (CSS 예제 포함) 이벤트 배너나 공지사항을 띄울 때 레이어 팝업(center 정렬) 을 깔끔하게 잡는 게 생각보다 어렵습니다. 화면 크기가 변해도 가운데에 고정되고, 모바일에서도 자연스럽게 보이게 하려면 position , top , left , transform 을 정확하게 이해해야 합니다. 이 글에서는 아래 내용을 예제로 정리합니다. 레이어 팝업(center 정렬)의 기본 개념 자주 사용하는 position: absolute / fixed 정렬 방식 질문에서 주신 스타일 top: 3.25%; left: 50%; transform: translateX(-50%) 의 의미 실무에서 바로 쓰는 반응형 레이어 팝업 HTML/CSS 예제 1. 레이어 팝업(center 정렬)이란? 레이어 팝업(레이어 팝업창) 은 새 창을 띄우는 것이 아니라, 현재 페이지 위에 div 레이어를 띄워서 공지사항, 광고, 이벤트 등을 보여주는 방식을 말합니다. 검색엔진(SEO) 입장에서도 같은 페이지 안에 HTML이 존재 하기 때문에 팝업 안의 텍스트도 정상적으로 인덱싱될 수 있습니다. 즉, “레이어 팝업 센터 정렬”, “레이어 팝업 만드는 방법”과 같이 관련 키워드를 적절히 넣어주면 검색 노출에 도움이 됩니다. 2. 질문에서 주신 레이어 팝업 스타일 분석 질문에서 주신 스타일은 다음과 같습니다. <div class="layer-popup" style="width:1210px; z-index:9001; position:absolute; top:3.25%; left:50%; transform:translateX(-50%);"> 레이어 팝업 내용 <...