GraphQL 백엔드 - 인증 및 인증

28865 단어 graphqlspringboot

GraphQL 백엔드 - 인증 및 인증


배경.


모든 사용자가 서로 상호작용하는 시스템에서 권한 수여와 신분 검증은 모든 사용자가 무엇을 할 수 있는지 제어하는 관건적인 요소이다.인증 및 권한 부여에 대한 자세한 내용을 보려면 RBAC을 시도하십시오.용례를 보여주기 위해서 나는 아래에서 몇 가지 역할과 자원을 소개했다.이것은 전자상거래 사이트다.
역할
코멘트
방문객
세션이 없으므로 시스템에서 구매자/판매자로 등록할 수 있습니다.
바이어
구매자는 주문서를 내리고 주소 정보를 업데이트할 수 있습니다
파는 사람
일단 구매자가 주문을 하면 판매자는 제품을 추가하고 주문 상태를 갱신할 수 있다
간단하게 보기 위해서 자원은 몇 개의GraphQL 돌연변이일 뿐이고 사용자는 시스템에서 하드코딩이다.github을 통해 예시 프로젝트 소스 코드를 얻을 수 있습니다.
graphql 모드에서 신분 검증으로 전환합시다.
  • Background

  • 🧑‍💻 Code it
  • Ground preparation

  • Components to know
  • Filters
  • RequestContextHolder
  • RequestManager
  • Aspect [AOP]
  • RequestManager
  • Request filter
  • Custom annotations
  • Aspect

  • 🚀 Run it
  • Why do we need method level control?
  • Future scope
  • 📖 Resources
  • 🧑‍💻 부호화


    지상 준비


    각 역할에 대한 소량의 조회를 포함하는 모델을 먼저 정의합니다.모든 변이를 호출할 때 문자열만 되돌려줍니다.Catch는 모든 사용자가 자원에 접근할 수 있도록 특정한 신분 검증 영패를 필요로 하는 것을 말한다.
        type Mutation {
            registerUser: String
            placeOrder: String
            addProduct: String
        }
    
    리소스의 해당 DGS 구성 요소를 공개하면 다음과 같습니다.
    
    @DgsComponent
    class DummyResource {
    
        @DgsMutation
        fun registerUser(): String = "dummy-member-id"
    
        @DgsMutation
        fun placeOrder(): String = "dummy-order-id"
    
        @DgsMutation
        fun addProduct(): String = "dummy-product-id"
    
    }
    
    
    전형적인 시스템에서 그들 중 하나는 모두 입력해야 한다. 왜냐하면 우리는 캐릭터와 모든 돌연변이가 다른 캐릭터의 영향을 받지 않도록 어떻게 보호하는지에 관심을 가지기 때문에 간결하게 보기 위해 모든 다른 부분은 생략되었다.우리는 시스템이 신분 검증에 실패할 때 오류를 던지기를 바란다.다음은 DummyUserService클래스입니다. 이 클래스에서 시스템은 신분 검증 영패를 정한 상황에서 사용자 역할을 표시합니다.
    
    @Service
    class DummyUserService {
    
        val sessions = mapOf(
            "token-buyer" to Roles.BUYER,
            "token-seller" to Roles.SELLER
        )
    
        fun identifyRole(token: String?): Roles {
            return sessions[token] ?: Roles.VISITOR
        }
    }
    
    enum class Roles {
        VISITOR,
        BUYER,
        SELLER
    }
    
    
    이상은 역할과 자원의 총체적인 설정이다.인증에 연결하겠습니다.

    알아야 할 구성 요소


    다음 구성 요소를 사용하여 인증을 설정할 것입니다.그것들은 graphql 특유의 것이 아니라springboot &aspectj에서 탄생했다.따라서 RestController에서도 다음 방법을 사용할 수 있습니다.실시를 시작하기 전에 모든 제품에 안감이 하나씩 있다.그것들이 어떻게 상호작용하는지 아래 그림과 같다.

    주황색 구성 요소는 우리가 만들 것입니다.녹색은springboot에서 제공합니다.파란색 포장은aspectj가 만들어 줍니다.

    필터


    필터는 요청 차단기입니다.그들은 모든 전송된 servlet 요청이 해상도에 도착하기 전에 처리할 수 있습니다.우리는 OncePerRequestFilter을 사용하여 캐릭터 관련 정보를 사용하여 전송 요청을 증가시킬 것입니다.

    RequestContextHolder

    RequestContextHolder은 servlet이 요청한 세션 관련 정보(사용자 id, 영패, 역할)를 저장할 수 있습니다.그러나 우리는 회화 상하문을 구축하는 논리를 가지고 있다.언제든지 시스템에서 이 기능에 액세스하여 세션을 식별할 수 있습니다.

    요청 관리자


    이것은 우리가 만든 포장 RequestContextHolder이며 다른 구성 요소가 역할을 발휘할 수 있도록 좋은 인터페이스를 제공합니다.세션 상하문을 구축하는 업무 논리가 여기서 완성됩니다.

    방면[AOP]


    방면 프로그래밍은 일종의 프로그래밍 모델로 그 중 어떠한 구성 요소도 공공 기능을 제공하기 위해 확장될 수 있다. 그러면 모든 컨트롤러(변이)는 중복된 업무 논리(신분 검증)를 가지지 않아도 된다.이것은 목표 클래스를 둘러싸고 에이전트 클래스를 만드는 것을 통해 이루어진 것이다.자세한 내용은 wiki으로 이동하십시오.
    이것은 모든 구성 요소와 신분 검증에서의 역할을 포함한다.우리들은 더욱 명확하게 코드를 작성할 것이다.나는 서로의 의존 관계를 알 수 있도록 아래에서 위로 구축할 것이다.

    요청 관리자


    Requst 관리자는 수신 요청에서 헤더 파일을 읽고 이를 RequestContextHolder에 저장합니다.그것은 지면 준비 부분에 만들어진 UserService을 사용한다.그것은 또한 후기의 세션 데이터 검색을 간소화하는 보조 방법을 제공했다.코드는 마땅히 스스로 해석해야 하며, 내연 주석을 띠고 있어야 한다.
    
    @Component
    class DummyRequestManager {
    
        @Autowired
        private lateinit var userService: DummyUserService
    
        // Save the session info per request. Retrieve it throughout the request
        fun saveSession(request: HttpServletRequest) {
            // Retrieve auth token from request - if any
            val token: String? = request.getHeader(HEADER_TOKEN)
    
            // Identify role
            val role = userService.identifyRole(token)
    
            // attribute the request with session. This will be available throughout the session
            request.setAttribute(
                KEY_SESSION, DummySession(
                    role = role
                )
            )
        }
    
        // A non-null guaranteed session. Everyone has a role here.
        fun getSession(): DummySession {
            val session = RequestContextHolder
                .getRequestAttributes()!!.getAttribute(KEY_SESSION, RequestAttributes.SCOPE_REQUEST)
            return session as DummySession
        }
    
        /**
         * Convenience method to retrieve role
         */
        fun getUserRole(): Roles = getSession().role
    
        companion object {
            private const val KEY_SESSION = "userSession"
            private const val HEADER_TOKEN = "x-auth-token"
        }
    }
    
    data class DummySession( val role: Roles)
    
    

    요청 필터


    요청 필터는 모든 요청의 차단기입니다.로그 기록과 세션 생성은 이 구성 요소의 흔한 예입니다.DummyRequestFilter을 만들고 요청 관리자를 호출해서 세션을 만듭니다.
    
    /**
     * Interceptor that executed once per http request
     */
    @Component
    class DummyRequestFilter : OncePerRequestFilter() {
    
        @Autowired
        private lateinit var requestManager: DummyRequestManager
    
        override fun doFilterInternal(
            request: HttpServletRequest,
            response: HttpServletResponse,
            filterChain: FilterChain
        ) {
    
            // Feed the request to request manager for session preparation
            requestManager.saveSession(request)
    
            // Resume with request
            filterChain.doFilter(request, response)
        }
    }
    
    

    주석 사용자화


    상기 두 가지 부분이 있어서 우리는 모든 요청을 위해 사용자 역할을 확정했다.다음은 모든 돌연변이의 접근 권한을 검색하는 것입니다.각 역할에 대한 주석을 작성합니다.
    
    @Retention(AnnotationRetention.RUNTIME)
    @Target(AnnotationTarget.FUNCTION)
    annotation class BuyerOnly
    
    @Retention(AnnotationRetention.RUNTIME)
    @Target(AnnotationTarget.FUNCTION)
    annotation class SellerOnly
    
    @Retention(AnnotationRetention.RUNTIME)
    @Target(AnnotationTarget.FUNCTION)
    annotation class VisitorOnly
    
    
    각 사용자에게 영역을 표시합니다.
    
    File: "DummyResource.kt"
    
    @DgsComponent
    class DummyResource {
    
        @VisitorOnly
        @DgsMutation
        fun registerUser(): String = "dummy-member-id"
    
        @BuyerOnly
        @DgsMutation
        fun placeOrder(): String = "dummy-order-id"
    
        @SellerOnly
        @DgsMutation
        fun addProduct(): String = "dummy-product-id"
    
    }
    

    방면


    우리는 모든 캐릭터에 세션과 자원을 표시했다.나머지는 주석과 캐릭터를 읽고 요청을 인증하는 것이다.이를 위해, 우리는 pointcut 전후에 Aspect를 실행할 수 있습니다.여기서 착안점은 우리의 변이 방법을 가리킨다. 우리가 변이하기 전에 실행하는 안전망 논리를 건의라고 한다.그것들은 함께 방면이라고 불린다.그래서 우리의 마지막 문장인'방면'은 바로 여기에 있다.
    
    @Component
    @Aspect
    class DummyAspect {
    
        @Autowired
        private lateinit var requestManager: DummyRequestManager
    
        @Before("@annotation(SellerOnly)")
        fun restrictSellerOnly(joinPoint: JoinPoint) {
            val role = requestManager.getUserRole()
            if (role != Roles.SELLER) {
                throw GraphQLException("This operation is specific to Seller accounts")
            }
        }
    
        @Before("@annotation(BuyerOnly)")
        fun restrictBuyerOnly(joinPoint: JoinPoint) {
            val role = requestManager.getUserRole()
            if (role != Roles.BUYER) {
                throw GraphQLException("This operation is specific to Buyer accounts")
            }
        }
    
        @Before("@annotation(VisitorOnly)")
        fun restrictVisitorOnly(joinPoint: JoinPoint) {
            val role = requestManager.getUserRole()
            if (role != Roles.BUYER) {
                throw GraphQLException("Please logout from existing session")
            }
        }
    }
    
    
    이렇게

    🚀 그것을 운행하다


    curl 또는graphiQL 놀이터에서 돌연변이[http://localhost:8080/graphiql]를 실행해 보십시오.
    // Seller adds product succussfully
    
    curl 'http://localhost:8080/graphql' \
      -H 'x-auth-token: token-seller' \
      -H 'Content-Type: application/json' \
      --data-raw '{"query":"mutation { addProduct }","variables":null}' \
      --compressed
    
    {"data":{"addProduct":"dummy-product-id"}}
    
    
    // Buyer cannot add product
    
      curl 'http://localhost:8080/graphql' \
      -H 'x-auth-token: token-buyer' \
      -H 'Content-Type: application/json' \
      --data-raw '{"query":"mutation {\n  \n  addProduct\n  \n}","variables":null}' \
      --compressed
    {
      "errors": [
        {
          "message": "This operation is specific to Seller accounts",
          "locations": [],
          "extensions": {
            "errorType": "UNKNOWN"
          }
        }
      ],
      "data": {
        "addProduct": null
      }
    }
    
    

    왜 우리는 방법급 통제가 필요합니까?


    graphql을 사용하면 다음과 같은 조회를 할 수 있습니다.
    
    mutation { 
        addProduct 
        placeOrder 
    }
    
    ### header
    "x-auth-token":"token-seller"
    
    
    graphQL http 요청에서 여러 작업을 쌓을 수 있습니다.이 경우 한 작업만 실패하고 다른 작업은 성공적으로 수행됩니다.멋있죠.😎?.
    {
      "errors": [
        {
          "message": "This operation is specific to Buyer accounts",
          "locations": [],
          "extensions": {
            "errorType": "UNKNOWN"
          }
        }
      ],
      "data": {
        "addProduct": "dummy-product-id",
        "placeOrder": null
      }
    }
    

    미래 범위


    이 비계가 있으면 우리는 입도 제어로 자원을 구축할 수 있다.모든 방법에 간단한 주석을 추가하면 해상기의 업무 논리를 수정하지 않고 끼워 넣는 대상을 제한할 수 있다.
    이 예에서 우리는 Role에 초점을 맞췄다.예를 들어 주소 같은 사용자에 대한 자원을 만들어야 한다고 가정하십시오.입력에서 사용자 id를 전달할 필요가 없습니다.그것은 영패와 교차 검증을 하고 세션에서 사용할 수 있다.사용자의 클라이언트/로케일 등 완전한 상하문은 완벽한 응답을 맞춤형으로 설정할 수 있습니다.
    현실 시스템에서 사용자 서비스는 테이블 조회나 연방 서비스 제공자가 될 것이다.성능을 향상시키기 위해서는 세션 정보를 캐시하고 원격 호출을 피하여 성능을 향상시켜야 한다.

    📖 리소스

  • Github repo
  • Aspect oriented programming
  • Role Based Access Control
  • 좋은 웹페이지 즐겨찾기