본문 바로가기

JAVA/Spring Boot

스프링부트 보안 OAuth2

반응형

스프링 부트 OAuth2

OAuth2는 피보탈, 구글, 아마존, 페이스북, 트위터 등의 회사에서 채택한 공개 표준 명세이다

이들 회사는 크레덴셜(클라이언트ID와 비밀키)에 기반한 액세스 토큰(access token)을 발급하는 식으로 서비스 접근 권한을 부여한다.


리소스 오너Resource Owner(사용자)는 어떤 앱이 자신의 계정에 접근하는 행위를 특정 스코프(읽기 또는 쓰기)로 제한한다.

인증서버 Authorization Server는 사용자 신원을 검증한 뒤 앱 클라이언트에 인증 토큰을 발행하고, 리소스 서버는 오직 이 인증 토큰을 통해서만 접근할 수 있게끔 리소스를 보호한다.

따라서 앱 클라이언트는 반드시 사용자이름, 비밀번호, 토큰으로 인증을 받아야 리소스에 접근할 수 있다.

인증과정은 API로 검증해야한다.


spring init -d=web, thymeleaf, data-jpa, data-rest, security -g=com.apress.spring -a=spring-boot-journal-oauth --package-name=com.apress.spring -name=spring-boot-journal-oauth -x


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.apress.spring</groupId>

<artifactId>spring-boot-journal-oauth</artifactId>

<version>0.0.1-SNAPSHOT</version>

<packaging>jar</packaging>


<name>spring-boot-journal-oauth</name>

<description>spring-boot-journal-oauth</description>


<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>1.5.12.BUILD-SNAPSHOT</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>


<properties>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<java.version>1.8</java.version>

</properties>


<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-jpa</artifactId>

</dependency>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-data-rest</artifactId>

        </dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-security</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-thymeleaf</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

    <groupId>org.springframework.security.oauth</groupId>

    <artifactId>spring-security-oauth2</artifactId>

        </dependency>

        <dependency>

            <groupId>mysql</groupId>

            <artifactId>mysql-connector-java</artifactId>

            <scope>runtime</scope>

        </dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-test</artifactId>

<scope>test</scope>

</dependency>

</dependencies>


<build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

</plugins>

</build>


<repositories>

<repository>

<id>spring-snapshots</id>

<name>Spring Snapshots</name>

<url>https://repo.spring.io/snapshot</url>

<snapshots>

<enabled>true</enabled>

</snapshots>

</repository>

<repository>

<id>spring-milestones</id>

<name>Spring Milestones</name>

<url>https://repo.spring.io/milestone</url>

<snapshots>

<enabled>false</enabled>

</snapshots>

</repository>

</repositories>


<pluginRepositories>

<pluginRepository>

<id>spring-snapshots</id>

<name>Spring Snapshots</name>

<url>https://repo.spring.io/snapshot</url>

<snapshots>

<enabled>true</enabled>

</snapshots>

</pluginRepository>

<pluginRepository>

<id>spring-milestones</id>

<name>Spring Milestones</name>

<url>https://repo.spring.io/milestone</url>

<snapshots>

<enabled>false</enabled>

</snapshots>

</pluginRepository>

</pluginRepositories>



</project>



controller

package com.apress.spring.web;


import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.servlet.ModelAndView;


import com.apress.spring.repository.JournalRepository;


@RestController

public class JournalController {


private static final String VIEW_INDEX = "index";

@Autowired

JournalRepository repo;

@RequestMapping(value="/", method=RequestMethod.GET)

public ModelAndView index(ModelAndView modelAndView) {

modelAndView.setViewName(VIEW_INDEX);

modelAndView.addObject("journal", repo.findAll());

return modelAndView;

}


}



index.html

<!DOCTYPE html>

<html lang="en-US" xmlns="http://www.thymeleaf.org">

<head>

<meta charset="utf-8"></meta>

<meta http-equiv="Content-Type" content="text/html"></meta>

<title>스프링부트 일기</title>

<link rel="stylesheet" type="text/css" media="all" href="css/bootstrap.min.css"></link>

<link rel="stylesheet" type="text/css" media="all" href="css/bootstrap-glyphicons.css"></link>

<link rel="stylesheet" type="text/css" media="all" href="css/styles.css"></link>

</head>


<body>

    <div class="container">

    <header class="page-header">

        <h1>스프링 부트 일기</h1>

    </header>s

  <ul class="timeline">

        <div th:each="entry, status :${journal}">

        <li th:attr="class=${status.odd}?'timeline-inverted':''">

            <div class="tl-circ"></div>

            <div class="timeline-panel">

                <div class="tl-heading">

                    <h4><span th:text="${entry.title}">제목</span></h4>

                    <p><small class="text-muted"><i class="glyphicon glyphicon-time"></i>

                    <span th:text="${entry.createdAsShort}">에 작성.</span></small></p>

                </div>

                <div class="tl-body">

                    <p><span th:text="${entry.summary}">요약</span></p>

                </div>

            </div>

       </li>    

        </div>

    </ul>  

    </div>

</body>

</html>


config

package com.apress.spring.config;


import org.springframework.context.annotation.Configuration;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

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;


@Configuration

@EnableAuthorizationServer

@EnableResourceServer

public class ResourceOAuthSecurityConfiguration extends ResourceServerConfigurerAdapter{


@Override

public void configure(HttpSecurity http) throws Exception {

http.authorizeRequests()

.antMatchers("/").permitAll()

.antMatchers("/api/**").authenticated();

}

}



@EnableAuthorizationServer

OAuth2 인증 서버를 구성한다.

인증 끝점(/oauth/authorize)은 개발자가 직접 보안을 걸러야 하며, 토큰끝점(/oauth/token)은 사용자 크레덴셜(여기서 DB를 조회한 username, password)에 따라 HTTP 기본 인증방식으로 보안이 자동 적용된다.



@EnableResourceServer

리소스 서버를 구성

알맞는 OAuth2 토큰을 보낸 요청만 통과시키는 스프링 시큐리티 필터를 켠다.


ResourceServerConfigurerAdapter

ResourceOAuthSecurityConfiguration 클래스가 상속한 ResourceServerConfigurerAdapter는 ResourceServerConfigurer 인터페이스를 구현한 단순 창구 역할을 하는 클래스다.

configurer(ResourceServerSecurityConfigurer), configure(HttpSecurity) 두 메서드를 재정의해서 사용한다.

configure(HttpSecurity)를 재정의해서 /api 리소스에 적용했다.


configure(HttpSecurity)

ResourceServerConfigurerAdapter클래스의 메서드를 재정의 한것으로 HttpSecurity 인스턴스를 받아 authorizedRequests() 메서드 빌드를 호출한다.

HttpSecurity는 연결형 API를 제공하므로 요청을 설정하고 보안을 적용하기 간편하다.


/api에 OAuth2 보안이 걸려있다.

인증을 받아야 한다.


OAuth2를 인증하려면 클라이언트 ID와 비밀키가 필요한데, 이 두값은 앱을 시동할때 마다 달라진다.


예)

security.oauth2.client.client-id = 9671f3a9-cb54-415c-9581-a63f8e5c08ac

security.oauth2.client.client-secret = 98cea43d-6fab-4f50-8879-d512fa1ace2a


curl -i localhost:8080/oauth/token -d "grant_type=password&scope=read&username=springboot&password=isawesome" -u 9671f3a9-cb54-415c-9581-a63f8e5c08ac:98cea43d-6fab-4f50-8879-d512fa1ace2a



클라이언트의 크레덴셜은 -u <클라이언트ID>:<비밀키> 형식으로 보낸다

콜론으로 분리한다.

클라이언트 ID, 비밀키는 로그에 나온 값을 쓰면된다.

-d 옵션은 grant_type=password, scope=read(read, write 둘중 하나로 , POST, PUT, DELETE 메서드로 호출할 리소스는 write로 지정한다.) username=springboot, pasword=isawesome


발급받은 액세스 토큰으로 호출한다.

curl -i H "Authorization: bearer 토큰키" localhost:8080/api


로그에 나왔던 클라이언트 ID와 비밀키가 추가로 필요하다.

두 키값을 DB에 담아두었다가 액세스 토큰을 받을 때 꺼내서 크레덴셜과 함께 요청하면 액세스 토큰을 발급받는 일련의 체계가 마련되어야 한다.


실제 사용자가 cURL 명령어로 OAuth2 보안이 적용된 앱에 접근하는 경우가 거의 없다.

보통은 웹 인터페이스를 통해 접속한다.

https://spring.io/guides/tutorials/spring-boot-oauth2/

앵귤러JS를 클라이언트로 하여 스프링 부트에서 OAuth2 인증을 처리하는 개발가이드가 있다.



확인










반응형

'JAVA > Spring Boot' 카테고리의 다른 글

REST API  (0) 2020.08.21
스프링 부트 기본설정  (0) 2020.08.20
스프링 부트 보안 (예제)  (0) 2018.04.07
스프링부트 웹 개발 (샘플)  (0) 2018.04.06
Spring Data JPA  (0) 2018.04.02