SAP 雲平臺為開發者提供了一個強大的集成和擴展開發的平臺。開發者可以在 SAP 平臺上開發各種雲原生應用包括 Java、Node.js 以及 UI5 等等。在開發應用時,如何進行用戶權限管理是個必須考慮的問題。
本文主要介紹如何在 SAP 雲平臺 Cloud Foundry 上開發應用以及管理用戶權限。這是 SAP 提供的一種實現用戶權限管理的標準方案。
在 SAP 雲平臺上,應用的整體架構包含 UI5、 Java 以及 App Router。這是前後端分離的架構,Java 應用調用 S/4HANA 系統提供的接口並且對外提供接口,但是前端應用不是直接連接後端,而是通過 App Router 來消費後端的接口。終端用戶也是通過 App Router 來訪問應用。用戶的信息存儲在 Identity Provider 內,SAP 雲平臺提供了默認的 SAP ID Service。用戶權限管理可在 SAP 雲平臺上完成。
App Router 在整個架構中起到非常關鍵的作用,主要有兩個:
應用的單一入口:所有對前後端應用的訪問需要通過 App Router,這意味著真正的應用路徑被隱藏了,App Router 會分發請求到真實的URL。
用戶認證:沒有經過授權的請求將會被重定向到 XSUAA 服務進行認證和授權,授權成功後會返回一個 JWT Token 並且會被緩存在 App Router 中。在用戶請求後端接口時,App Router 會把 Token 轉發給 Java 應用進行權限認證。整個授權過程遵循 OAuth 2.0 流程。
* 關於 App Router 的詳細介紹,推薦大家參閱 SCP Application Router 簡介。
並行擴展應用架構圖
現在我們一共需要開發和部署三個應用:
Java Application
UI5 Application
App Router (Node.js)
App Router 是 SAP 提供的一個 Node.js 的應用。如果不需要增加額外的功能,那麼只需要對它進行簡單的配置,然後部署到雲平臺即可。本文會主要詳細如何開發和部署 Java 和 App Router 應用。我們將在第二篇文章中介紹如何開發 UI5 應用。基於 Cloud Foundry 的 UI5 模板裡包含了App Router 模塊,所以在開發項目時,不需要一個單獨的項目來開發和部署 App Router。
Identity Provider
本文使用的 Identity Provider 是SAP ID Service。當然您也可以自己提供 Identity Provider,但是前提是這個 Identity Provider 與 SAP 雲平臺之間已經建立信任連接。
SAP 雲平臺服務
我們需要用到 XSUAA Service,如果想要調用 S/4HANA On-premise 系統的接口,還需要配置 Cloud Connector、 Destination 和 Connectivity 服務。
接下來讓我們設計一個簡單的應用場景。我們準備為外部供應商開發一個訂單管理系統,公司在 S/4HANA 系統上建立採購訂單以後,外部供應商可以在雲應用中看到相應的銷售訂單。需要定義一個讀訂單列表的權限,無權限就不能看到訂單。因為沒有前端應用,我們可以把需求轉換成技術語言,Java 應用提供一個查詢訂單的接口,擁有讀權限才能調用接口。下面我們開始逐步實現這個需求。
我們會使用本地 IDE 開發 Java 和 Node.js 應用以並部署到 SAP 雲平臺,因此需要提前準備開發環境。在這個例子中,開發者使用 Eclipse 來開發 Java 應用,Visual Studio Code 開發 Node.js 應用:
我們會藉助 CLI 來創建 SAP 雲平臺服務實例以及部署應用。具體的安裝步驟請參考https://docs.cloudfoundry.org/cf-cli/install-go-cli.html。安裝好以後可以用 CLI 連接 SAP 雲平臺 Cloud Foundry 環境。
注意,如果您是在公司內網,可能需要在系統環境變量裡配置代理。
Scope、Role 和 Role Collection
Scope, Role and Role Collection
首先介紹一下 SAP 雲平臺的 Scope,Role 和 Role Collection 之間的對應關係。
灰色部分表示 SAP 雲平臺的開發人員在開發可以定義 Scope、Attribute 以及 Role Template。Scope 和 Attribute 是權限的最小單位,這部分在開發應用的時候需要定義。
藍色部分表示在 SAP 雲平臺上創建好權限以後,管理員可以管理 Role 和 Role Collection。在 SAP 雲平臺上,最小的權限是 Role Template,您不會看到 Role 裡面包含的具體 Scope,所以 Role 和 Scope 以及 Attribute 的對應關係一定要提前理清楚。
綠色部分表示最終分配給用戶和用戶組的權限是 Role Collection (User Group)。
了解基本的概念以後我們開始創建 Scope 和 Role Template。定義應用安全描述符的文件叫做 xs-security.json。它裡面定義了 Scope、Attribute 以及 Role Template。此外。它還包含了 OAuth2 的相關配置,比如 Access Token 和 Refresh Token 的有效時間,允許使用哪些 Identity Provider。這個文件默認放在 SAP UI5 項目的根目錄下 (第二篇文章會解釋為什麼放在 UI5 項目裡)。在本文中,由於暫時沒有涉及到 UI5 應用開發,所以我們把它放在 App Router 的根目錄下,該文件存放的路徑是C:\purchaseorderlist_approuter\xs-security.json。
這裡我們定義了 xsappname, 這個變量代表開發人員定義的 OAuth Client ID,需要確保它在整個 Subaccount 裡是唯一的。我們還分別定義了一個 Scope 和 Role Template,用於表示讀取訂單的權限。
{
"xsappname" : "purchase-order-list",
"tenant-mode" : "dedicated",
"scopes": [
{
"name": "$XSAPPNAME.read",
"description": "Read purchase order list"
}
],
"role-templates": [
{
"name": "Viewer",
"description": "Role to get purchase order list",
"scope-references": [
"$XSAPPNAME.read"
]
}
]
}
詳細的配置介紹請參考官方文檔:
https://help.sap.com/viewer/4505d0bdaf4948449b7f7379d24d0f0d/2.0.03/en-US/6d3ed64092f748cbac691abc5fe52985.html
Create and Configure XSUAA Sevice
定義好用戶權限以後,我們要在 SAP 雲平臺上創建 XSUAA 服務實例。這個服務實例會被同時綁定在 App Router 和 Java 應用上。有兩種方式可以創建XSUAA服務實例。第一種方式是通過雲平臺圖形化界面,第二種方式是通過 Cloud Foundry 的命令行(CLI)。本文採取第二種方式。
cf api <API Endpoint of Cloud Foundry>
查詢 Cloud Foundry 所屬區域的 API Endpoint:cf login
Email> 輸入郵箱
Password> 輸入密碼
選擇 Subaccount 和 Space
創建 XSUAA 服務實例
cd <path to your xs-security.json file>
cf create-service xsuaa application <XSUAA_INSTANCE_NAME> -c xs-security.json
請通過 cf services 來查看服務實例是否創建成功。創建好了以後可以登錄 SAP 雲平臺對應的 Subaccount 並檢查自定義的 Role 是否創建完成。
Develop Java App & OAuth 2.0
例子中的 Java 應用可提供獲取訂單的接口。它會被保護,只允許具備相應權限的用戶訪問。本文中使用的 Java 項目是 SAP Cloud SDK 提供的基於 Spring 模板。使用 Cloud SDK 的原因有三點。首先,Java 應用要用 Cloud SDK 來從 S/4HAHA 中獲取訂單信息的數據。其次,向 SAP 雲平臺上部署應用需要定義一些參數,比如應用名稱,訪問路徑。在 Cloud SDK 的 項目中會自帶一個部署文件 (manifest.yml),這樣會省去很多麻煩;最後,SAP Cloud SDK 還提供了獲取用戶權限的接口。根據已知的權限,可以靈活地對前端應用的顯示進行控制。
Step 1: 創建 Java 項目並導入 Eclipse,然後打開 Workspace,在命令行輸入 Maven 命令生成模板項目。
mvn archetype:generate -DarchetypeGroupId=com.sap.cloud.sdk.archetypes -DarchetypeArtifactId=scp-cf-spring -DarchetypeVersion=LATEST
提供 GroupId 和 ArtifactId,然後按多次回車生成最新版本的 SAP Cloud SDK 項目。
打開 Eclipse,選擇 File -> Import -> Existing Maven Projects,找到項目路徑,點擊完成。
本文中的 Eclipse 項目是按層級結構顯示,如果是扁平結構,可以看到四個項目。項目整體結構是一個父項目包含三個子項目,這是一種 Maven 多模塊項目結構,每個子模塊都是一個 Maven 項目。暫時可以不用考慮其他模塊。
application裡包含業務代碼
integration-tests 裡包含集成測試的測試用例
unit-tests 模塊裡包含單元測試的測試用例
manifest.yml,部署到 SAP 雲平臺需要用的配置文件
打開項目後您會發現,這是一個經典的 SpringBoot 項目。只不過項目結構發生了改變,項目裡添加了一些由 Cloud SDK 提供的集成 SAP 雲平臺服務,安全以及調用 OData API 的工具的依賴。
Step 2: 調用 S/4HANA On-premise 系統的 OData Service 並且提供 RESTful 接口
我們在包 com.sap.purchaseorderlist.controllers 路徑下新建一個 Java 文件 MyPurchaseOrderController.java。這個 Contoller 提供獲取 Purchase Order 的 Restful 接口。在方法體內,我們通過 SAP Cloud SDK 提供的 PurchaseOrderService 去 S/4HANA 系統裡獲取採購訂單,直接返回獲取的結果。注意,調用 S/4HANA OP 系統的接口需要額外的配置和雲平臺服務,包括 Cloud Connector、Destination、 Connectivity 等。所以建議大家從資料庫讀取數據或者創建一個列表存儲數據,然後返回。
@RestController
@RequestMapping("/purchaseorderlist")
public class MyPurchaseOrderController {
@Inject private
PurchaseOrderService poService;
GetMapping
public List<PurchaseOrder> getAllPurchaseOrder() throws Exception {
// Get Purchase Order list from S/4HANA On-premise system
List<PurchaseOrder> purOrder = poService.getAllPurchaseOrder().top(10) .
execute(DestinationUtil.getHttpDestination("S4X_HTTP"));
return purOrder; }}
Step 3: 用 Spring Security 保護 RESTful 接口
用來保護Java應用接口的框架是Spring Security。SAP 對Spring Security做了增強,提供了XSUAA Spring Security library,結合XSUAA服務實現整個OAuth2的授權流程。在整個授權流程中,Java應用作為資源伺服器XSUAA作為授權伺服器。在應用中,我們還需要創建一個文件來進行OAuth相關的配置。
<dependency>
<groupId>com.sap.cloud.security.xsuaa</groupId>
<artifactId>xsuaa-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
還有兩個測試相關的依賴,也可以一起加入pom.xml中。
<dependency>
<groupId>com.sap.cloud.security.xsuaa</groupId>
<artifactId>spring-xsuaa-test</artifactId>
<version>2.0.0</version><scope>test</scope>
</dependency>
<dependency>
<groupId>com.sap.cloud.security.xsuaa</groupId>
<artifactId>spring-xsuaa-mock</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
創建安全配置文件
在Java項目的默認包路徑下,有一個SecurityConfig.java的文件。本文默認包路徑是com.sap.purchaseorderlist。這是 Cloud SDK 提供的 OAuth 配置的模板。作者不建議使用這份配置文件,首先文件裡用到的類和接口已經被廢棄。其次它用到的一些依賴還沒有放到Maven中央倉庫。這些依賴需要手動下載,然後上傳到本地倉庫。在默認包路徑下,創建一個安全配置文件SecurityConfigXSUAA.java。然後把下面的代碼複製進去。
在配置文件裡,我們定義了一條規則,用HTTP Get方式請求接口「/purchaseorderlist/**」,都要檢查權限$XSAPPNAME.read, 這個scope是($XSAPPNAME.read)在xs-security.json文件中定義的。Java應用在收到轉發的token時,會根據綁定的XSUAA服務實例中的xsappname參數名來過濾用戶權限,同時去掉這個前綴。所以在判斷權限的時候只需要使用「read」。
@Profile("cloud")
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true,
jsr250Enabled = true)
public class SecurityConfigXSUAA extends WebSecurityConfigurerAdapter {
private final XsuaaServiceConfiguration xsuaaServiceConfiguration;
@Autowired
public SecurityConfigXSUAA(XsuaaServiceConfiguration xsuaaServiceConfiguration) {
this.xsuaaServiceConfiguration = xsuaaServiceConfiguration;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.authorizeRequests()
.antMatchers(GET, "/purchaseorderlist/**").hasAuthority("read") //checks scope $XSAPPNAME.read
.anyRequest().denyAll() // denies anything not configured above
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(getJwtAuthoritiesConverter());
}
Converter<Jwt, AbstractAuthenticationToken> getJwtAuthoritiesConverter() {
TokenAuthenticationConverter converter = new
TokenAuthenticationConverter(xsuaaServiceConfiguration);
converter.setLocalScopeAsAuthorities(true);
return converter;
}
}
Step 4: 部署到 SAP 雲平臺
Integration test case會運行失敗,因為現在測試接口需要提供Token,沒有提供token會拋出http 401的錯誤。沒關係,暫時忽略它,因為application模塊已經打包完成。
---
applications:
- name: PurchaseOrderList_service
memory: 1024M
timeout: 300
random-route: true
path: application/target/PurchaseOrderList-application.jar
buildpacks:
- sap_java_buildpack
env:
TARGET_RUNTIME: main
SPRING_PROFILES_ACTIVE: 'cloud'
JBP_CONFIG_SAPJVM_MEMORY_SIZES: 'metaspace:128m..'
services:
- my-application-logs
- purchaseorder-list-xsuaa
- my-destination
- my-connectivity
跟創建 XUSAA 服務的做法一樣,首先用 CLI 連接 Cloud Foundry 環境,進入工作的 Subaccount 和 Space。
運行這個命令的時候,默認會在當前目錄下尋找manifest.yml文件進行部署,如果找不到會報錯。部署完成以後應用就在運行狀態。
Step 5: 測試接口
應用部署到SAP雲平臺以後,我們可以看到程序訪問的入口。本實驗中,Purchase Order List應用的訪問入口是
https://purchaseorderlistservice-talkative-mandrill.cfapps.eu10.hana.ondemand.com/。
訂單接口的完整路徑是
https://purchaseorderlistservice-talkative-mandrill.cfapps.eu10.hana.ondemand.com/purchaseorderlist。如果直接用Postman請求接口或直接在瀏覽器中打開,會得到http 401 unauthorized的錯誤。這是正確的,因為現在接口已被保護,提供正確的 access token 才能訪問。
Develop and Confiture App Router
App Router 是 SAP開發的Node.js的應用。在開發和配置 App Router 之前,本地首先得安裝 Node.js 的環境。本文不涉及 App Router 的擴展開發,只是進行簡單配置,但是我們需要了解配置的含義。
在本文中App Router是作為一個獨立的應用來開發和部署的。如果使用SAP UI5來開發前端應用,模板裡會包含 App Router 模塊,它和前端的代碼在同一個項目裡,模板採用multi-target application的方式管理所有模塊。部署的時候,多個模塊會一起部署,也可以單獨部署某一個模塊。這樣開發一個前後端應用,不會有三個獨立的項目。
這一步,我們要把PurchaseOrderList應用「註冊」到App Router:
{
"name":
"approuter",
"dependencies": {
"@sap/approuter": "*"
},
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
}
}
介紹完這些概念,接下來介紹如何進行URL轉換。在本文中,通過App Router訪問的後端接口 URL,也就是 Source URL, 為 <App Router URL>/backend/purchaseorderlist,它經過 routes 規則轉換,最終的 Target URL 為 <purchase-order-list URL>/purchaseorderlist。這樣就達到 URL 轉換,轉發請求的目的了。
{
"welcomeFile": "index.html",
"routes": [
{
"source": "^/backend/(.*)$",
"target": "$1",
"destination": "purchase-order-list",
"authenticationType": "xsuaa"
}
]
}
---
applications:
- name: purchaseorderlist_approuter
random-route: true
path: approuter
memory: 128M
buildpacks:
- nodejs_buildpack
env:
destinations: '[
{
"name":"purchase-order-list",
"url" :"https://purchaseorderlistservice-talkative-mandrill.cfapps.eu10.hana.ondemand.com/",
"forwardAuthToken": true
}
]'
services:
- purchaseorder-list-xsuaa
部署完成後用 cf apps 命令查看應用的運行狀態。到目前為止,我們已經部署了兩個應用。本文中App Router的訪問路徑是
https://purchaseorderlistapprouter-humble-lynx.cfapps.eu10.hana.ondemand.com。
User Permission Allocation
所有的開發工作已經完成。這時候我們要通過 App Router 來訪問後端接口。本文中這個路徑是 https://purchaseorderlistapprouter-humble-lynx.cfapps.eu10.hana.ondemand.com/backend/purchaseorderlist;真實的接口路徑是 https://purchaseorderlistservice-talkative-mandrill.cfapps.eu10.hana.ondemand.com/purchaseorderlist。
用瀏覽器第一次訪問 App Router 的路徑時,會跳出來一個登陸界面。這說明 App Router 正常工作了,當它發現這個請求沒有被 Session 記錄時,會把請求重定向到 XSUAA 服務,XSUAA 服務發現請求沒有被認證,則會再重定向到 IdP,IdP 最終返回了這個登錄界面。
當用戶提供了正確的用戶名和密碼登陸以後,XSUAA 服務會籤發一個 Token 返回給 App Router,App Router會把 Token 緩存下來,同時創建一個 Session。下次訪問的時候如果有 session 和 token,就不會要求用戶重新登陸。同時,當用戶請求後端接口時,App Router 會把 Token 轉發給相應的應用。當後端應用拿到 Access Token 就根據綁定 XSUAA 服務實例的信息去解析 Token,從而判斷當前用戶是否具有權限。
這個流程雖然比較複雜,但是需要開發者做的事情比較簡單。用戶認證,Token 獲取,轉發和解析都是自動完成的,並不需要額外的開發工作。
這裡我們用的是 SAP ID Service,我們輸入 SAP 雲平臺的帳號和密碼。登陸完成後,會看到 HTTP ERROR 403 「Access Denied」 。不同於沒有提供 Access Token,這個錯誤說明用戶沒有足夠的權限去訪問這個接口。
下面我們登陸 SAP 雲平臺來給用戶分配權限。如果您使用的是試用帳號,默認有相應的權限。如果是公司的帳號,首先要確認登陸帳號是否具有授權權限。
Step 1:登陸 SAP 雲平臺,進入 Subaccount,打開 Security -> Roll Collections
Step 2:點擊 Role Collection,創建一個新的 Role Collection - PurchaseOrderViewer
Step 3:往 Role Collection 裡添加 Viewer Role
Step 4:選擇 Trust Configuration,打開 SAP ID Service
Step 5:分配 Role Collection 給用戶
檢查一下,確認用戶此時已經有 PurchaseOrderViewer 這個 Role Collection。
這時,我們在瀏覽器中再一次訪問這個接口。注意,如果用的是同一個瀏覽器,需要清理一下緩存,且退出重新登陸才能生效。這時候會重新跳出登陸頁面,輸入用戶名密碼。進入:
https://purchaseorderlistapprouter-humble-lynx.cfapps.eu10.hana.ondemand.com/backend/purchaseorderlist 便可以看到用戶有權限看到訂單數據了。
基於 SAP 雲平臺的應用授權管理一共有兩篇文章,本文是系列文章的第一篇,只介紹了 App Router 和 Java 應用,一個完整的應用還應該包含前端應用。下一篇文章會跟大家一起探討如何結合 SAP UI5,App Router 以及 Java 應用來一起實現用戶權限管理。敬請期待。
註:本文所涉用例是基於 SAP 雲平臺個人免費試用帳號實現的,數據中心在歐洲,而阿里雲環境由於合規等原因,在配置上會稍有變化。對於阿里雲環境和別的數據中心在使用中的差別,我們計劃近期發文章來專門介紹,大家到時候可以關注一下。
Jerry Zhang 擁有五年 Java 開發經驗。目前就職於博世,主要工作是研究基於 SAP 雲平臺的並行擴展開發,內容包括 SAP 雲平臺服務,SAP UI5,SAP Cloud SDK 以及 SAP Cloud Application Programming Model,同時提供軟體架構參考方案,制定開發流程和規範。
如果您或者您身邊的技術大牛也想要分享與 SAP 雲平臺有關的開發內容和實用技巧,歡迎您積極投稿或者推薦。我們會進行適當篩選,並隨後與作者溝通聯繫。
投稿郵箱:gavin.du@sap.com,jiahui.zou01@sap.com 和 kate.shen02@sap.com。
往期回顧: