[spring boot + security oauth] 세팅 + 테스트

개요

먼저 OAuth의 개념에 관한 자료는 http://helloworld.naver.com/helloworld/textyle/24942http://earlybird.kr/1584 를 참고하길 바란다. 이 글에서는 개념보다는 Provider쪽 세팅 예제와 테스트를 해보겠다. 테스트는 client_credentials, password, authorization_code 인증 방식을 사용하겠다.

참고 : 예제에서 curl 을 사용하기 때문에 윈도우용 curl을 설치 하시거나 cygwin 혹은 git bash 에서 curl을 사용하셔도 된다.

예제

먼저 STS 플러그인에 있는 Spring Starter Project로 프로젝트를 생성하자.

Web과 Security를 체크하고 생성한 다음, spring-security-oauth2 라이브러리를 추가한다. 그러면 pom.xml은 다음과 같다.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.tistory.jekalmin</groupId>
    <artifactId>boot_oauth</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>boot_oauth</name>
    <description></description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.1.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.0.3.RELEASE</version>
        </dependency>
    </dependencies>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>com.tistory.jekalmin.Application</start-class>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

다음 Application.java를 아래와 같이 수정한다.


Application.java

package com.tistory.jekalmin;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Configuration
@ComponentScan
@EnableAutoConfiguration
@RestController
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @RequestMapping("/")
    public String home() {
        return "Hello World";
    }

    @Configuration
    @EnableResourceServer
    protected static class ResourceServer extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http
                // Just for laughs, apply OAuth protection to only 2 resources
                .requestMatchers().antMatchers("/","/admin/beans").and()
                .authorizeRequests()
                .anyRequest().access("#oauth2.hasScope('read')");
            // @formatter:on
        }

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.resourceId("sparklr");
        }

    }

    @Configuration
    @EnableAuthorizationServer
    protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {

        @Autowired
        private AuthenticationManager authenticationManager;

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager);
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // @formatter:off
            clients.inMemory()
                .withClient("my-trusted-client")
                    .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
                    .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
                    .scopes("read", "write", "trust")
                    .resourceIds("sparklr")
                    .accessTokenValiditySeconds(60)
            .and()
                .withClient("my-client-with-registered-redirect")
                    .authorizedGrantTypes("authorization_code")
                    .authorities("ROLE_CLIENT")
                    .scopes("read", "trust")
                    .resourceIds("sparklr")
                    .redirectUris("http://localhost:8080")
            .and()
                .withClient("my-client-with-secret")
                    .authorizedGrantTypes("client_credentials", "password")
                    .authorities("ROLE_CLIENT")
                    .scopes("read")
                    .resourceIds("sparklr")
                    .secret("secret");
        // @formatter:on
        }

    }

    @Configuration
    @EnableWebMvcSecurity
    protected static class SecurityConfig extends WebSecurityConfigurerAdapter{

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication().withUser("min").password("min").roles("USER");
        }

        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean()
                throws Exception {
            return super.authenticationManagerBean();
        }

    }

}

이러면 서버쪽 세팅은 끝이다. 이제 AccessToken을 받아오자.



인증 방법

  • client_credentials

    my-client-with-secret 라는 클라이언트에 client_credentials 인증방법으로 인증해보자.

      curl -u my-client-with-secret:secret http://localhost:8080/oauth/token -d "grant_type=client_credentials"
    

    결과는 다음과 같았다.

      {"access_token":"4444455c-a8a5-4988-ae61-c4b15080e433","token_type":"bearer","expires_in":43200,"scope":"read"}
    



  • password

    이번엔 my-trusted-client 클라이언트에 password 방법으로 인증해보자.

      curl -u my-trusted-client: http://localhost:8080/oauth/token -d "grant_type=password&username=min&password=min"
    

    결과는 다음과 같다.

      {"access_token":"c09bfac0-274b-4dc6-a15c-ec46d2680d44","token_type":"bearer","refresh_token":"9b5c996c-eede-4111-b3d1-aaff28e5c063","expires_in":49,"scope":"read write trust"}
    



  • authorization_code

    my-client-with-registered-redirect 클라이언트에 authorization_code 방법으로 인증하자.
    이전과는 다르게 AccessToken을 발급 받기 위해서는 권한 승인해서 코드를 가져오는 과정이 필요하다. http://localhost:8080/oauth/authorize?client_id=my-client-with-registered-redirect&response_type=code 로 가서 먼저 등록된 min / min 계정으로 로그인 하면 다음과 같은 창이 뜬다.

    둘 다 허용하고 승인하면 아래 화면으로 이동한다.

    보시는 바와 같이 http://localhost:8080/?code=h6cdAM 로 리다이렉트 된다. 이제 code를 이용해서 AccessToken을 발급 받아보자.

      curl -u my-client-with-registered-redirect: http://localhost:8080/oauth/token -d "grant_type=authorization_code&code=h6cdAM"
    

    결과는 다음과 같았다.

      {"access_token":"1ce32b00-05fa-40f2-a1f5-f6ed2719df4e","token_type":"bearer","expires_in":43199,"scope":"trust read"}
    

어떤 방법이든 인증에 성공했으면, AccessToken을 받급 받았을 것이다. 토큰을 가지고 테스트 하기에 앞서 Application.java에서 설정했던 부분을 다시 살펴보자.

@RequestMapping("/")
public String home() {
    return "Hello World";
}

접근에 성공하면 Hello World가 출력되야 한다.
이제 http://localhost:8080 에 접근해보자. 먼저 토큰을 사용하지 않고 접근해서 보호된 자원임을 확인하자.

C:\Users\Min>curl http://localhost:8080
{"error":"unauthorized","error_description":"An Authentication object was not found in the SecurityContext"}

접근이 안된다. 이제 위에서 받았던 AccessToken을 사용해서 접근해보자.

C:\Users\Min>curl -H "authorization: bearer 1ce32b00-05fa-40f2-a1f5-f6ed2719df4e" http://localhost:8080
Hello World

AccessToken을 가지고 요청할 때에만 접근이 되는 것을 확인할 수 있다.

결론

인증 방법에 따라 파라미터로 넘겨야 하는 필수 항목들이 있는데, 최소한의 파라미터로 테스트를 했다. 필수 파라미터들은 http://tutorials.jenkov.com/oauth2/authorization-code-request-response.html 에서 확인할 수 있다.

spring boot를 사용해서 spring security oauth 설정해서 간단해 보이지만, xml 설정을 해보면 TokenEndPoint, TokenStore, Filter 등 설정해야 할 부분이 많다. 그렇게 설정을 해도 결과가 좋지 않았다. 나중에 xml 기반 세팅도 완료 되는데로 포스팅 하겠다.

ps : 예제는 https://github.com/dsyer/sparklr-boot 소스에서 security 세팅을 추가했다.

참고