Skip to content

Spring security with JWT #

Find similar titles

1회 업데이트 됨.

Edit
  • 최초 작성자
    yang4851
  • 최근 업데이트
    jhshin

Structured data

Category
Programming

Spring framework 기반의 웹 어플리케이션을 REST-ful 하게 구현하기 위해 스프링의 보안 프레임워크의 표준 보안 필터의 사용자 인증, 인가 방식을 세션이 아닌 JWT(Json Web Token)를 이용하기 위해 서버 프로그램의 XML 설정과 필터 클래스 구현에 대해 예제를 통해 알아보도록 하겠다. (스프링 보안 프레임워크 참고 : Spring security framework)

스프링 보안 설정 #

MAVEN 프로젝트 의존성 추가 #

스프링 보안 설정을 위한 스프링 기본 버전은 4.2.4.RELEASE을 기본으로 하고 MAVEN 기반 자바 프로젝트 설정을 위해 pom.xml 파일 설정을 다음과 같이 의존성을 추가

<!-- for JWT -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>4.2.4.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>4.2.4.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-crypto</artifactId>
    <version>4.2.4.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.9.RELEASE</version>
</dependency>

<!-- jackson (for json Handling) -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.4</version>
</dependency>

Spring security 보안 설정 변경 #

스프링 보안 설정을 JWT 방식으로 토큰을 사용하기 위해 spring-security 프레임워크 설정을 다음과 같이 수정한다. 이클립스에서 MAVEN 으로 프로젝트를 생성한 경우 /src/main/resources/sping 폴더에 context-security.xml 파일을 추가해 다음과 같이 설정한다.

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/security
        http://www.springframework.org/schema/security/spring-security-4.2.xsd">

    <context:component-scan base-package="com.insilicogen.example" use-default-filters="false" >
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service" />
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository" />
    </context:component-scan>

    <global-method-security secured-annotations="enabled" pre-post-annotations="enabled" />

    <!-- index.html과 resources 폴더의 하위 자원들은 사용자 인증/인가 필터를 적용하지 않도록 설정 --> 
    <http pattern="/" security="none"/>
    <http pattern="/index.html" security="none"/>
    <http pattern="/resources/**" security="none"/>

    <!-- 사용자 인증 필터 설정(/api/login 으로 요청에 대한 처리 설정) --> 
    <http pattern="/api/login" auto-config="true" use-expressions="true">
        <csrf disabled="true"/>
        <intercept-url pattern="/**" access="permitAll" />

        <form-login login-processing-url="/api/login"
            username-parameter="username"
            password-parameter="password"
            always-use-default-target="false"/>

        <!-- 사용자 로그인 요청을 받으면 뒤의 설정하게 되는 사용자 로그인 필터(loginFilter)를 사용하도록 설정 --> 
        <http-basic entry-point-ref="authenticationEntryPoint" />
        <custom-filter before="FORM_LOGIN_FILTER" ref="loginFilter" />
    </http>

    <!-- 그 외 다른 요청에 대한 사용자 인가 필터 설정 --> 
    <http pattern="/**" auto-config="true" use-expressions="true">
        <csrf disabled="true"/>
        <intercept-url pattern="/api/**" access="isAuthenticated()" method="GET" />
        <intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" />

        <access-denied-handler ref="accessDenied"/>

        <http-basic entry-point-ref="authenticationEntryPoint" />
        <custom-filter before="BASIC_AUTH_FILTER" ref="authenticationFilter" />
</http>

    <!-- 사용자 권한과 인가 실패처리 객체 선언 --> 
    <beans:bean id="accessDenied" class="com.insilicogen.example.security.StatelessAccessDeniedHandler" />

    <!-- 서버 암호화 객체 선언 -->
    <beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" />

    <!-- 사용자 정보 관리 서비스 객체 선언 --> 
    <beans:bean id="userDetailsService" class="com.insilicogen.example.service.UserDetailsService" />

    <!-- 사용자 로그인 처리 객체 선언 -->
    <beans:bean id="loginFilter" class="com.insilicogen.example.security.StatelessLoginFilter">
        <beans:constructor-arg name="urlMapping" type="java.lang.String" value="/api/login" />
        <beans:constructor-arg name="authManager" ref="authManager" />
    </beans:bean>

    <!-- 사용자 인가 엔트리 객체 선언 -->
    <beans:bean id="authenticationEntryPoint" class="com.insilicogen.example.security.StatelessAuthenticationEntryPoint" />

    <!-- 사용자 인가 필터 객체 선언 -->
    <beans:bean id="authenticationFilter" class="com.insilicogen.example.security.StatelessAuthenticationFilter" />

    <beans:bean id="authenticationProvider" class="com.insilicogen.example.security.StatelessAuthenticationProvider" />

    <authentication-manager id="authManager">
        <authentication-provider ref="authenticationProvider" />
    </authentication-manager>

</beans:beans>

사용자 인가 필터 서비스 클래스 추가 #

보안필터 적용 예외 설정이 되지 않은 요청에 대한 사용자 인증 필터를 위한 클래스를 security-context.xml 파일에서 설정한 정보와 동일하게 생성하고 다음과 필터 메소드를 오버라이드 해 JWT 토큰 내용을 읽어 요청 리소스에 대한 권한을 확인하고 권한이 없는 경우는 필터에서 권한 오류를 반환하도록 함.

class StatelessAuthenticationFilter extends GenericFilterBean {

    @Autowired
    private TokenAuthenticationService tokenAuthenticationService;

    protected StatelessAuthenticationFilter() {
        super();
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
        throws IOException, ServletException {
        Authentication authentication = tokenAuthenticationService.getAuthentication((HttpServletRequest) req);

        if(logger.isDebugEnabled()) {
            logger.debug("Request URL=" + ((HttpServletRequest)req).getRequestURI());
            logger.debug("Authentication : " + new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(authentication));
        }       
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(req, res);
    }
}

JWT 개념 #

JWT (Json Web Token) #

  • 사용자의 인증(authentication) 또는 인가(authorization) 정보를 서버와 클라이언트 간에 안전하게 주고받기 위해서 사용하는 토큰.
  • JSON 형식의 데이터를 검증이 가능한 형태로 전송.

JWT 구성 요소 (Header, Payload, Signature) #

  • Header: JWT의 버전과 서명을 생성하는 데 사용되는 토큰 유형 및 해싱 알고리즘에 대한 메타데이터가 포함되어 있음.
  • Payload: 토큰에 포함할 사용자 정보 및 추가 데이터에 대한 설명인 claims가 포함되어 있음.
  • Signature: Header와 Payload를 암호화한 값으로 토큰의 위조를 방지하여 토큰의 무결성을 보장.

JWT 동작 과정 #

  1. 클라이언트에서 ID/PW를 통해 로그인 요청.
  2. 서버에서 DB에 해당 ID/PW를 가진 사용자 확인 후 Access Token과 Refresh Token 발급.
  3. 클라이언트는 Access Token을 받아 API 요청 시 Authorization 헤더에 값으로 넣어 서버에 전송.
  4. 서버는 Access Token을 검증하여 요청에 대한 권한을 부여.

JWT 특징 #

  • HTTP 헤더 또는 URL 파라미터에 포함하여 전송될 수 있어 편리.
  • 클라이언트에서 서버로 인증 정보를 전송할 필요가 없음.
  • 서버에서 클라이언트의 인증 정보를 쉽게 검증할 수 있음.

JWT 용도 #

  • 인증(Authentication): 클라이언트의 인증 정보를 검증하여 사용자를 식별하는 데 사용.
  • 권한 부여(Authorization): 클라이언트가 특정 리소스에 접근할 수 있는 권한을 부여하는 데 사용.
  • 세션 유지(Session Management): 클라이언트의 세션을 유지하는 데 사용.

JWT 장점 #

  • 경량화: JWT는 JSON 형태로 저장되기 때문에 비교적 가벼움.
  • 간편성: JWT는 별도의 인증 서버 없이 클라이언트와 서버 간 직접 데이터를 주고받을 수 있음.
  • 확장성: JWT는 사용자 정보뿐만 아니라, 다양한 정보를 담을 수 있음.

JWT 단점 #

  • 보안 취약성: JWT는 암호화되어 있지만, 탈취되면 사용자 정보가 노출될 수 있음.
  • 만료 시간 설정: JWT는 만료 시간을 설정해야 하며, 만료 시간이 지난 후에는 사용할 수 없음.

Suggested Pages #

0.0.1_20240214_1_v81