Skip to content

Spring security with JWT #

Find similar titles
  • 최초 작성자
    yang4851

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);
    }
}

Suggested Pages #

0.0.1_20231010_1_v71