Friday, August 24, 2007

ทดลองเรียกใช้ Ofbiz service ผ่าน RMI

วันนี้ทดลองเรียกใช้ service บน Ofbiz ดู
หน้าตาของ client code เป็นดังนี้
String endPoint = "rmi://127.0.0.1:1099/RMIDispatcher";
RemoteDispatcher dispatcher = (RemoteDispatcher) Naming.lookup(endPoint);

Map ret = dispatcher.runSync("createPerson", UtilMisc.toMap("firstName", "polawat", "lastName", "phetra"));
String id = (String) ret.get("personId");


code ดูตรงไปตรงมาดี แต่ก็มีปัญหาจำนวนหนึ่ง

1. ผมสร้างโปรแกรม client ใน Eclipse
เนื่องจาก code มันอ้างถึง class ใน Ofbiz ด้วย
ดังนั้น ผมก็เลย add Ofbiz project เข้ามาเป็น dependency project ใน build path
ปัญหา ก็คือเวลา run ก็จะเกิด exception หน้าตาแบบนี้
Caused by: java.io.InvalidClassException: org.ofbiz.service.rmi.socket.ssl.SSLClientSocketFactory; 
local class incompatible: stream classdesc serialVersionUID = -6771263703492769955,
local class serialVersionUID = 266831034753376983
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:562)

สาเหตุเกิดจาก eclipse class ที่ server ของ ofbiz ใช้ (compile จาก ant)
กับ class ที่ eclipse compile มันเป็นคนละตัวกัน
ทางแก้ไขก็คือ ต้องใช้ class ตัวเดียวกัน โดยการ config build path ของ eclipse project ให้ถูกต้อง
ประเด็นถัดมา ก็คือ
แล้วเราจะ add jar ของ ofbiz ตัวไหน เข้าไปใน build path บ้าง
ตรงนี้ผมใช้วิธีค่อยๆทดลองใส่เข้าไปทีละตัว
พอมัน error ว่า classNotFound ตรงไหน ก็ใส่ตัวนั้นเพ่ิมเข้าไป
ได้ข้อสรุปว่า ต้องใช้ jar และ classpath ดังนี้
  • ofbiz-base.jar
  • ofbiz-service.jar
  • javolution-4.2.8.jar
  • jdbm-1.0.jar
  • log4j.jar
  • (classpath) ofbiz/framework/base/config


2. ปัญหาถัดไป ก็คือเวลา run แล้วมันเกิด exception นี้
Caused by: java.security.cert.CertificateException: No trusted certificate found
at org.ofbiz.base.util.MultiTrustManager.checkServerTrusted(MultiTrustManager.java:70)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:967)

หลังจากปล้ำกับ exception นี้ 3 ชั่วโมง ก็ได้ข้อสรุปดังนี้
เนื่องจาก RMI ของ ofbiz ใช้วิธีขี่บน SSL
เมื่อเปิด ssl connection, server จะส่ง certificate มาให้ client
ปัญหาที่เกิดก็คือ client จะต้องตรวจสอบก่อนว่า
มันควรจะเชื่อใจว่า server ตัวที่มันต่อ ใช้ตัวจริงหรือไม่
โดยมันจะทำการตรวจ certificate authority ที่ sign ให้ SSL server certificate
ว่าเชื่อใจได้หรือไม่ โดยการตรวจว่า CA certificate นั้นมีอยู่ภายใน keystore ของ current runtime หรือไม่
(ปกติ ก็คือ file jre/lib/security/cacerts)

วิธีแก้ไข ก็คือ เราต้องหา public key ของ CA ที่ sign ให้ SSL server cert. ให้เจอ
แล้วก็นำมา import เข้า jre/lib/security/cacerts ของเรา
ปัญหา ก็คือ จะหาจากไหน

วิธีแรกสุด ก็คือ ต้องรู้ก่อนว่า CA certificate มีหน้าตาเป็นอย่างไร
อันนี้ ทำได้โดย กำหนด property นี้ลงไป ตอน run client code ดังนี้
-Djavax.net.debug=ssl
ซึ่ง information ที่ได้มา ก็จะบอกว่า issuer เป็นใคร
  Key:  Sun RSA public key, 2048 bits
modulus: 17344648192569884299786164434338372557687257978285180720285392205708340271654619651329483132682296140227663296442343040152431948217141461304574537723511105703477881281981923049874873997527674061132974306266530070170417234226257753990168554412489871566243809200783575680539880294393348381403494415570636578448776926614739538191796509060477513894871026620210065065716045117073870737655887248726060382348138811502024743774034713421310777435989153209885185191144967681642453965102277302605991311051345165434979041324933404867053275745104875913849033117376130950416551314398706806603259933232713428792347281547375515443731
public exponent: 65537
Validity: [From: Fri May 04 03:36:54 ICT 2007,
To: Mon May 01 03:36:54 ICT 2017]
Issuer: CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
SerialNumber: [ 463a47e6]

เยี่ยมมาก Unknown ล้วนๆ

ขั้นถัดไป ก็คือมองหา certificate ที่หน้าตาแบบนี้ ใน directory ของ ofbiz
ลองใช้คำสั่ง find . -name '*.pem' -print
ก็ได้ผลลัพท์ดังนี้
@pann[~/dev/ofbiz]$ find . -name '*.pem' -print
./framework/base/cert/certreq.pem
./framework/base/cert/demoCA/cacert.pem
./framework/base/cert/demoCA/newcerts/01.pem
./framework/base/cert/demoCA/newcerts/02.pem
./framework/base/cert/demoCA/private/cakey.pem
./framework/base/cert/newcert.pem

ตัวที่ชื่อดูสื่อสุดก็คือ cacert.pem
ลองใช้คำสั่ง keytool -printcert -file cacert.pem เพื่อดู information
ก็พบว่า ไม่ใช่ file นี้
ลองดูที่เหลืออีก 5 ตัว ก็ไม่ใช่

สุดท้ายลองแกะ code ของ ofbiz ดู
ก็พบเงื่อนงำว่า ofbiz มี keystore ของตัวเอง
โดยตั้งชื่อ file ให้มีนามสกุล .jks
@pann[~/dev/ofbiz]$ find . -name '*.jks' -print
./framework/base/config/ofbizrmi.jks
./framework/base/config/ofbizssl.jks
./framework/service/config/rmitrust.jks

อยากรู้ว่าเป็นตัวไหน ก็ต้อง browse ดู
(จำได้ว่า ถ้าเป็น JDK ของ IBM มันจะมี GUI tool มาให้ด้วย
แต่วันนี้คิดว่าจะฝึกใช้ command-line ก็เลยไม่ได้ลองค้นดู)
@pann[~/dev/ofbiz/framework/service/config]$ keytool -list -v -keystore rmitrust.jks
Enter keystore password:

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: mykey
Creation date: May 4, 2007
Entry type: trustedCertEntry

Owner: CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Issuer: CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Serial number: 463a47e6
Valid from: Fri May 04 03:36:54 ICT 2007 until: Mon May 01 03:36:54 ICT 2017
Certificate fingerprints:
MD5: 86:B1:D1:09:74:36:66:C7:30:18:7B:16:FA:C2:C2:9E
SHA1: 90:42:E8:91:BD:4E:88:59:E4:D7:F3:10:12:8F:57:3E:81:87:96:A2
Signature algorithm name: MD5withRSA
Version: 1


จากการเปรียบเทียบดู ก็พบว่า ofgizrmi.jks กับ rmitrust.jks
เก็บ cert ตัวเดียวกัน และตรงกับที่มันฟ้องใน debug console ของเรา

ขั้นถัดมาก็คือ export cert ออกจาก keystore
โดยใช้คำสั่ง keytool -exportcert -keystore ofbizrmi.jks -file pok.pem -alias rmissl
นำ file ที่ได้ไป import เข้า jre/lib/security/cacerts โดยใช้คำสั่ง
keytool -import -keystore ./cacerts -file pok.pem -alias ofbiz_rmissl

Note: จริงเราควรใช้ keystore ของเราเอง แทนที่จะใช้ global keystore
โดยกำหนดใน system property ดังนี้
-Djavax.net.ssl.trustStore=mySrvKeystore 
-Djavax.net.ssl.trustStorePassword=123456


Note: default password ของ keystore ของ ofbiz และ java คือ "changeit"

จบ ปิดคดี

Related link from Roti

6 comments:

Sand said...

ขอบคุณมากครับพี่ ตกลงแก้ไขปัญหาได้หรือเปล่าครับ

polawat phetra said...

แก้ได้แล้ว
ขั้นถัดไปคือลอง pack เป็น plugin

Nattanicha said...

ลองทำตาม tutorial นี้อยู่ค่ะ http://www.opensourcesupport.it/products/ofbiz/sample_p.html แต่มีปัญหากับ beanshell ค่ะ run คำสั่ง source("bshcontainer.bsh"); ไม่ผ่าน เหตุมาจากอะไรคะ แล้วมีวิธีไหนบ้างที่ทำ RMI (bshcontainer.bsh มัน provide บางสิ่งบางอย่างสำหรับ entity ถูกไหมคะ)ได้อีก ลองอ่านของพี่ป๊อกแล้วไม่ค่อยเข้าใจค่ะเพราะมัน advance เกินระดับ newbie อย่างเก๋ไปแล้วค่ะ

polawat phetra said...

load bshcontainer.bsh ไม่ผ่าน
นี่เกิดจากหลายสาเหตุนะ
เช่น
1. หา file ไม่เจอ
2. ตัว server ไม่ได้เปิด port ไว้
3. syntax ที่เขียนไว้ใน file ผิด

nat ลอง post error message มาให้พี่ดูหน่อยสิ
ลงใน http://groups.google.com/group/orangegears ก็ได้ จะได้ถามตอบได้ง่ายขึ้น

ส่วนตัว bshcontainer.bsh จริงๆแล้วไม่ได้ทำอะไรเลย
เนื้อหาภายใน ก็คือ import package ต่างๆ
แล้วก็ load
1. delegator ซึ่งจะใช้คุยกับ database
2. dispatcher ซึ่งจะใช้คุยกับ service

วัตถุประสงค์ของขั้นนี้ใน tutorial ก็คือใช้ทดสอบว่า service ที่ define ขึ้นใน tutorial นั้น
เรียกใช้ได้จริงๆหรือเปล่า

ส่วนขั้น RMI จะเป็น step ถัดไป
ซึ่งถ้าทำตาม tutorial เขาก็จะไปติดปัญหาว่า
จะหา class ServerSocketFactory ตัวที่เขาใช้ไม่เจอ
(ไม่รู้มันไปเอามาจากไหนเหมือนกัน)
ซึ่งก็เป็นสาเหตุที่พี่เขียน post นี้ เพราะพี่ทำตามเขาแล้วไม่ผ่าน

ปล. หนูนัทถามได้ตามสบายนะ ไม่ต้องเกรงใจ
พี่ชอบตอบคนที่ขยันหาความรู้อยู่แล้ว

Nattanicha said...

ขอบคุณมากๆ ค่ะ แก้ได้แล้ว เป็นปัญหาเ้ส้นผมบังภูเขา คือไม่ได้ telnet ค่ะ ใช้เป็น java bsh.Console ตาม beanshell.org อธิบายการแก้ปัญหาไว้ที่นี่นะคะ

http://www.ofbizguru.com/?q=node/24

ขอบคุณพี่ป๊อกมากๆ ค่ะ

step ถัดไป เป็น RMI ค่ะ จะพยายามค่ะ

Nattanicha said...

พี่ป๊อกเคยเจอปัญหานี้ไหมคะ

http://www.ofbizguru.com/?q=node/29

เป็นเพราะอะไร และแก้ยังงัยค่ะ ตอนนี้ทำอะไรกับ entity ไม่ได้เลยอ่ะ