RATSENO

[SpringBoot]Spring Boot Security + OAuth 2.0 [Authorization code grant] (2) 본문

DEV/SPRING

[SpringBoot]Spring Boot Security + OAuth 2.0 [Authorization code grant] (2)

RATSENO 2020. 9. 14. 17:10

이전 포스팅

ratseno.tistory.com/90

 

[SpringBoot]Spring Boot Security + OAuth 2.0 (1)

OAuth (Open Authorization)는 보호된 데이터를 게시하고 상호 작용하는 간단한 방법입니다. 인터넷에서 토큰 기반 인증 및 권한 부여를위한 개방형 표준입니다. 이를 통해 최종 사용자의 계정 정보는 �

ratseno.tistory.com

에 이어서 진행해 보겠습니다.

두가지 프로젝트 소스로 구성 됩니다.

  • spring boot client application - client_id(ratseno)와 secret_key(secret)를 이미 발급받은것을 전제로 합니다.

  • resouce server - OAuth를 사용하여 authoriztion server를 구성합니다. client application을 식별하기 위한 client_id, secret_key를 이미 가지고 있습니다.

flow는 아래와 같습니다.

  • Resource Owner(사용자)Client Application에게 Resource Server로 부터 데이터를 가져오도록 요청합니다.
  • Resource ServerResource Owner(사용자)에게 사용자 자신을 인증하고 데이터를 공유할수 있는 권한을 요청합니다.
  • 성공적인 인증 후 Resource Server는 Client ApplicationAuthrizaition code를 공유합니다.


Resource Server Application

 

먼저 Resoucer Server입니다. JSON 데이터를 인증 및 반환하는 프로젝트이며, Authorization Server 또한 구성할 것입니다.

maven 프로젝트로 구성됩니다. 프로젝트를 생성 시 spring initializr를 이용하여 import하거나 직접 생성하셔도 됩니다.

저는 한땀 한땀 생성하도록 하겠습니다. IDE는 인텔리제이 커뮤니티를 사용하였습니다.

프로젝트 명은 boot-resouce-server이며 깡통 maven 프로젝트로 생성하였기때문에

이렇게 기본 pom.xml과 프로젝트가 구성됩니다. 이제 필요한 dependency등 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>org.example</groupId>
    <artifactId>boot-resource-server</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</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.security.oauth</groupId>
            <artifactId>spring-security-oauth2</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-security</artifactId>
        </dependency>

    </dependencies>

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

</project>

필요한 의존성들을 추가하였습니다.(oauth2, security)

이어서 패키지 및 main 클래스를 작성합니다. spring initializr를 이용하여 생성하신 분들은 스킵하셔도 됩니다.

package com.ratseno; //패키지 구성(가장 상위)

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

JSON response로서 return될 model 클래스를 생성합니다.

package com.ratseno.model;

public class Employee {

    private String empId;
    private String empName;

    public String getEmpId() {
        return empId;
    }

    public void setEmpId(String empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "empId='" + empId + '\'' +
                ", empName='" + empName + '\'' +
                '}';
    }
}

JSON response를 반환할 end point GET REST Contorller를 생성합니다.

package com.ratseno.controllers;

import com.ratseno.model.Employee;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
public class EmployeeController {

    @GetMapping(value = "/user/getEmployeeList")
    public List<Employee> getEmployeeList(){

        List<Employee> employees = new ArrayList<>();
        Employee employee = new Employee();
        employee.setEmpId("emp1");
        employee.setEmpName("emp1");
        employees.add(employee);
        return employees;
    }
}

마지막은 spring security 관련 설정 파일을 생성합니다. 이 설정파일에서는 어떤URL을 체크하고, 어떤 사용자가 어떤역할로 접근할지를 정의합니다.

package com.ratseno.config;

import org.springframework.context.annotation.Configuration;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**");
        //resources 하위 정적자원들에 대해 보안구성에서 제외한다.
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //HttpSecurity - 특정 http 요청에 대해 웹 기반 보안을 구성할 수 있다.
        //기본적으로 모든 요청에 적용되니만 requestMatcher또는 유사한 방법을 사용하여 제한 할 수 있다.

        //authorizeRequests - RequestMatcher 구현(url 패턴)을 사용하여 HttpServletReqeust를 기반으로 접근을 제한 할 수 있다.
        //antMatchers - 제공된 ant pattern과 일치할때만 호출되도록 HttpSecurity를 호출할 수 있다.
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/user/getEmployeeList").hasAnyRole("ADMIN")
                .anyRequest().authenticated()
                .and().formLogin().permitAll()
                .and().logout().permitAll();

        http.csrf().disable();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationMgr) throws Exception {
        authenticationMgr.inMemoryAuthentication().withUser("admin").password("admin")
                .authorities("ROLE_ADMIN");
    }
}

다음으로 @EnableAuthorizationServer 어노테이션을 이용하여  Authorization Server를 구성합니다.

AuthorizationServcerConfigurer 인터페이스의 메소드를 구현하는 AuthorizationServerConfigurerAdaptar 클래스를

상속받아 정의합니다.

Authorization Server는 authorization end point(예를 들어 /oauth/authorize)에 대한 보안을 진행하지 않습니다.

configure 메서드에 spring security authentication manager를 삽입합니다.in memory client service를 사용하여 서버에 접근할 수 있는 client를 설정합니다.

  • client_id : ratseno
  • secret_key : secret
package com.ratseno.config;

import org.springframework.context.annotation.Configuration;
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;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("ratseno")      //client_id
                .secret("secret")                   //secret_key
                .authorizedGrantTypes("authorization_code")
                .scopes("read").authorities("CLIENT");
    }
}

Client Application

 

Client Application은 JSON data를 얻기위해 앞에서 만든 Resource Server에 요청을 할것 입니다.

앞에서 설명했듯이 Client Application은 이미 Resource Server에 등록되어 있으며,

client_id : ratseno, secret_key : secret으로 가정하였습니다.

OAuth spec에 의해 기본 uri인 /authorize로 인증을 요청해야합니다. 이 예제에서는 기본 uri로 진행하겠습니다.(변경 가능)

기본 인증 uri와 함께 parameter도 전송해야합니다.

  • response_type - (필수값) 값은 "code" 로 설정해야 합니다.

  • client_id - (필수값) 등록하여 얻은 클라이언트 식별자 입니다. 여기서는 "ratseno" 입니다.

  • redirect_uri - (선택)  성공적으로 권한이 부여된 후, Resource Owner가 redirect_uri로 리디렉션 됩니다. 

  • scope - (선택) access request의 범위입니다. Read 또는 Write가 될수 있으며. 여기서는 Read를 사용합니다.

위의 parameter들은 "application/x-www-form-urlencoded" 으로 요청해야 합니다.

 

프로젝트 명은 boot-client-application, maven 프로젝트로 위의 Resource server와 동일하게 생성합니다.

pom.xml을 작성합니다. 크게 spring과 관련된 의존성은 거의 없습니다. 

간단한 화면 구성을 위한 의존성들입니다.(JSP)

<?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>org.example</groupId>
    <artifactId>boot-client-application</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</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-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>

    </dependencies>

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


</project>

기본 패키지와 메인 클래스를 생성합니다.

package com.ratseno;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

"/getEmployees" uri를 가지며 JSP페이지를 리턴하는 컨트롤러를 생성합니다.

package com.ratseno.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class EmployeeController {

    @GetMapping(value = "/getEmployees")
    public ModelAndView getEmployeeInfo(){
        return new ModelAndView("getEmployees");
    }
}

spring boot에서 view를 JSP로 사용하기 위해서는 resources폴더 하위의 application.properties

파일을 이용하여 설정이 필요합니다.

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

server.port=8090

저와 같이 기본 maven 프로젝트로 생성하였다면 해당 파일을 생성하여 작성하시면 됩니다.

jsp파일을 생성하기 위해 webapp - WEB-INF - jsp 폴더를 순서대로 생성하고 getEmployees.jsp 파일을 생성합니다.

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Get Employees</title>
</head>
<body>
    <h3 style="color: red;">Get Employee Info</h3>
    <div id="getEmployees">
        <form:form action="http://localhost:8080/oauth/authorize"
            method="post" modelAttribute="emp">
            <p>
                <label>Enter Employee Id</label>
                <div>
                    response_type : <input type="text" name="response_type" value="code" />
                </div>
                <div>
                    client_id : <input type="text" name="client_id" value="ratseno" /><br/>
                </div>
                <div>
                    redirect_uri : <input type="text" name="redirect_uri" value="http://localhost:8090/showEmployees" />
                </div>
                <div>
                    scope : <input type="text" name="scope" value="read" /><br/>
                </div>
                <div>
                <input type="SUBMIT" value="Get Employee info" />
                </div>
        </form:form>
    </div>
</body>
</html>

이제 boot-client-applicationboot-resource-server를 전부 동작시키고.

http://localhost:8090/getEmployees 경로로 브라우저를 이용하여 접근합니다.

Get Employee info버튼을 클릭하면, Resource Server(포트 8080)의 기본 spring security login form 화면이 뜨게됩니다.

admin, admin을 입력하여 로그인합니다.

다음으로는 jsp페이지에서 요청한 기본 인증 화면(/oauth/authorize)을 볼 수 있습니다.

Approve를 선택 후 Authorize 버튼을 클릭하여 Client Application이 Resource를 접근할 수 있도록 권한을 부여해 줍니다.

 

지금은 Client Application에 showEmployees를 받아줄 수 있는 uri가 존재하지 않기때문에 에러페이지가 뜨지만,

자세히 살펴보면,

Resource  Server에 요청할때 같이 보낸 redirect_uriAuthorization code가 성공적으로 전달된 것을 확인할 수 있습니다.

 

다음 포스팅에서는 공유된 Authorization code 을 이용하여 Access Token을 얻고 

Access Token을 이용하여 JSON데이터를 가져오는 방법을 살펴보겠습니다.

 

출처: www.javainuse.com/spring/spring-boot-oauth-introduction

 

Comments