pengenalan
Dalam ulasan ini saya ingin membincangkan topik seperti keselamatan aplikasi web. Java mempunyai beberapa teknologi yang menyediakan keselamatan:-
" Senibina Keselamatan Platform Java SE ", butiran lanjut mengenainya boleh dibaca dalam Panduan daripada Oracle: " Senibina Keselamatan Platform JavaTM SE ". Seni bina ini menerangkan cara kami perlu melindungi aplikasi Java kami dalam persekitaran masa jalan Java SE. Tetapi ini bukan topik perbualan kita hari ini.
-
" Seni Bina Kriptografi Java " ialah Sambungan Java yang menerangkan penyulitan data. Anda boleh membaca lebih lanjut mengenai sambungan ini di JavaRush dalam ulasan " Java Cryptography Architecture: First acquaintance " atau dalam Guide from Oracle: " Java Cryptography Architecture (JCA) Reference Guide ".
JAAS
JAAS ialah sambungan kepada Java SE dan diterangkan dalam Panduan Rujukan Java Authentication and Authorization Service (JAAS) . Seperti yang dicadangkan oleh nama teknologi, JAAS menerangkan cara pengesahan dan kebenaran harus dilakukan:-
" Pengesahan ": Diterjemah daripada bahasa Yunani, "authentikos" bermaksud "sebenar, tulen". Maksudnya, pengesahan adalah ujian ketulenan. Bahawa sesiapa yang disahkan adalah benar-benar yang mereka katakan.
-
" Kebenaran ": diterjemahkan daripada bahasa Inggeris bermaksud "kebenaran". Iaitu, kebenaran ialah kawalan akses yang dilakukan selepas pengesahan berjaya.
- lesen memandunya sebagai representasi seseorang sebagai pengguna jalan raya
- pasportnya, sebagai representasi seseorang sebagai warganegara negaranya
- pasport asingnya, sebagai representasi seseorang sebagai peserta dalam hubungan antarabangsa
- kad perpustakaannya di perpustakaan, sebagai representasi seseorang sebagai pembaca yang melekat pada perpustakaan
aplikasi sesawang
Jadi kami memerlukan aplikasi web. Sistem binaan projek automatik Gradle akan membantu kami menciptanya. Terima kasih kepada penggunaan Gradle, kami boleh, dengan melaksanakan arahan kecil, memasang projek Java dalam format yang kami perlukan, secara automatik mencipta struktur direktori yang diperlukan, dan banyak lagi. Anda boleh membaca lebih lanjut tentang Gradle dalam gambaran ringkas: " Pengenalan Ringkas kepada Gradle " atau dalam dokumentasi rasmi " Gradle Bermula ". Kita perlu memulakan projek (Initialization), dan untuk tujuan ini Gradle mempunyai pemalam khas: " Gradle Init Plugin " (Init ialah singkatan untuk Initialization, mudah diingati). Untuk menggunakan pemalam ini, jalankan arahan pada baris arahan:gradle init --type java-application
Selepas berjaya disiapkan, kami akan mempunyai projek Java. Sekarang mari buka skrip binaan projek kami untuk diedit. Skrip binaan ialah fail yang dipanggil build.gradle
, yang menerangkan nuansa binaan aplikasi. Oleh itu namanya, bina skrip. Kita boleh mengatakan bahawa ini adalah skrip binaan projek. Gradle ialah alat serba boleh, keupayaan asasnya diperluaskan dengan pemalam. Oleh itu, pertama sekali, mari kita perhatikan blok "plugin":
plugins {
id 'java'
id 'application'
}
Secara lalai, Gradle, mengikut apa yang kami nyatakan " --type java-application
", telah menyediakan set beberapa pemalam teras, iaitu pemalam yang disertakan dalam pengedaran Gradle itu sendiri. Jika anda pergi ke bahagian "Dokumen" (iaitu dokumentasi) di tapak web gradle.org , kemudian di sebelah kiri dalam senarai topik dalam bahagian "Rujukan" kita melihat bahagian " Pemalam Teras ", i.e. bahagian dengan penerangan tentang pemalam yang sangat asas ini. Mari kita pilih betul-betul pemalam yang kita perlukan, dan bukannya pemalam yang Gradle hasilkan untuk kita. Menurut dokumentasi, " Gradle Java Plugin " menyediakan operasi asas dengan kod Java, seperti menyusun kod sumber. Selain itu, menurut dokumentasi, " Pemalam aplikasi Gradle " memberikan kami alat untuk bekerja dengan "aplikasi JVM boleh laku", i.e. dengan aplikasi java yang boleh dilancarkan sebagai aplikasi kendiri (contohnya, aplikasi konsol atau aplikasi dengan UI sendiri). Ternyata kita tidak memerlukan pemalam "aplikasi", kerana... kami tidak memerlukan aplikasi kendiri, kami memerlukan aplikasi web. Mari padamkannya. Serta tetapan "mainClassName", yang hanya diketahui oleh pemalam ini. Selanjutnya, dalam bahagian " Pembungkusan dan pengedaran " yang sama di mana pautan ke dokumentasi Pemalam Aplikasi disediakan, terdapat pautan ke Pemalam Perang Gradle. Gradle War Plugin , seperti yang dinyatakan dalam dokumentasi, menyediakan sokongan untuk mencipta aplikasi web Java dalam format perang. Dalam format WAR bermakna bahawa bukannya arkib JAR, arkib WAR akan dibuat. Nampaknya ini yang kita perlukan. Juga, seperti yang dinyatakan dalam dokumentasi, "Pemalam Perang memanjangkan pemalam Java". Iaitu, kita boleh menggantikan plugin java dengan plugin perang. Oleh itu, blok pemalam kami akhirnya akan kelihatan seperti ini:
plugins {
id 'war'
}
Juga dalam dokumentasi untuk "Gradle War Plugin" dikatakan bahawa plugin menggunakan tambahan "Project Layout". Susun atur diterjemahkan daripada bahasa Inggeris sebagai lokasi. Iaitu, pemalam perang secara lalai menjangkakan kewujudan lokasi tertentu fail yang akan digunakan untuk tugasnya. Ia akan menggunakan direktori berikut untuk menyimpan fail aplikasi web: src/main/webapp
Kelakuan pemalam diterangkan seperti berikut:
web.xml
- ini ialah "Deskriptor Deployment" atau "Descriptor Deskriptor". Ini ialah fail yang menerangkan cara mengkonfigurasi aplikasi web kami untuk berfungsi. Fail ini menentukan permintaan yang akan dikendalikan oleh aplikasi kami, tetapan keselamatan dan banyak lagi. Pada terasnya, ia agak serupa dengan fail manifes daripada fail JAR (lihat " Bekerja dengan Fail Manifes: Asas "). Fail Manifest memberitahu cara untuk bekerja dengan Aplikasi Java (iaitu arkib JAR), dan web.xml memberitahu cara untuk bekerja dengan Aplikasi Web Java (iaitu arkib WAR). Konsep "Deskriptor Deployment" tidak timbul dengan sendirinya, tetapi diterangkan dalam dokumen " Spesifikasi API Servlet"". Mana-mana aplikasi web Java bergantung pada "Servlet API" ini. Adalah penting untuk memahami bahawa ini ialah API - iaitu, ia adalah perihalan beberapa kontrak interaksi. Aplikasi web bukan aplikasi bebas. Ia dijalankan pada pelayan web , yang menyediakan komunikasi rangkaian dengan pengguna. Iaitu, pelayan web adalah sejenis "bekas" untuk aplikasi web. Ini adalah logik, kerana kami ingin menulis logik aplikasi web, iaitu halaman apa yang pengguna akan lihat dan bagaimana mereka harus bertindak balas terhadap tindakan pengguna. Dan kami tidak mahu menulis kod untuk bagaimana mesej akan dihantar kepada pengguna, cara bait maklumat akan dipindahkan dan perkara peringkat rendah dan sangat menuntut kualiti yang lain. Selain itu, ternyata aplikasi web semuanya berbeza, tetapi pemindahan data adalah sama. Iaitu, sejuta pengaturcara perlu menulis kod untuk tujuan yang sama berulang kali. Jadi pelayan web bertanggungjawab untuk beberapa interaksi pengguna dan pertukaran data, dan aplikasi web dan pembangun bertanggungjawab untuk menjana data tersebut. Dan untuk menyambung dua bahagian ini, i.e. pelayan web dan aplikasi web, anda memerlukan kontrak untuk interaksi mereka, i.e. apakah peraturan yang akan mereka ikuti untuk melakukan ini? Untuk menerangkan kontrak, bagaimana interaksi antara aplikasi web dan pelayan web sepatutnya kelihatan, API Servlet telah dicipta. Menariknya, walaupun anda menggunakan rangka kerja seperti Spring, masih terdapat Servlet API yang berjalan di bawah hud. Iaitu, anda menggunakan Spring dan Spring berfungsi dengan Servlet API untuk anda. Ternyata projek aplikasi web kami mesti bergantung pada API Servlet. Dalam kes ini, Servlet API akan menjadi kebergantungan. Seperti yang kita ketahui, Gradle juga membenarkan anda untuk menerangkan kebergantungan projek dengan cara deklaratif. Pemalam menerangkan cara kebergantungan boleh diurus. Sebagai contoh, Java Gradle Plugin memperkenalkan kaedah pengurusan pergantungan "testImplementation", yang mengatakan bahawa pergantungan sedemikian hanya diperlukan untuk ujian. Tetapi Gradle War Plugin menambah kaedah pengurusan pergantungan "providedCompile", yang mengatakan bahawa pergantungan sedemikian tidak akan disertakan dalam arkib WAR aplikasi web kami. Mengapa kami tidak memasukkan Servlet API dalam arkib WAR kami? Kerana API Servlet akan diberikan kepada aplikasi web kami oleh pelayan web itu sendiri. Jika pelayan web menyediakan API Servlet, maka pelayan itu dipanggil bekas servlet. Oleh itu, adalah menjadi tanggungjawab pelayan web untuk memberikan kami Servlet API, dan menjadi tanggungjawab kami untuk menyediakan ServletAPI hanya pada masa kod disusun. sebab tu providedCompile
. Oleh itu, blok dependencies akan kelihatan seperti ini:
dependencies {
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
testImplementation 'junit:junit:4.12'
}
Jadi, mari kita kembali ke fail web.xml. Secara lalai, Gradle tidak mencipta sebarang Deskriptor Deployment, jadi kami perlu melakukannya sendiri. Mari kita cipta direktori src/main/webapp/WEB-INF
, dan di dalamnya kita akan mencipta fail XML yang dipanggil web.xml
. Sekarang mari kita buka "Java Servlet Specification" itu sendiri dan bab " BAB 14 Deployment Descriptor ". Seperti yang dinyatakan dalam "14.3 Deskriptor Deskriptor", dokumen XML Deskriptor Deployment diterangkan oleh skema http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd . Skema XML menghuraikan elemen yang boleh terdiri daripada dokumen dan dalam susunan yang sepatutnya ia muncul. Mana yang wajib dan mana yang tidak. Secara umum, ia menerangkan struktur dokumen dan membolehkan anda menyemak sama ada dokumen XML disusun dengan betul. Sekarang mari kita gunakan contoh dari bab " 14.5 Contoh ", tetapi skema mesti ditentukan untuk versi 3.1, i.e.
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd
Yang kosong kami web.xml
akan kelihatan seperti ini:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>JAAS Example</display-name>
</web-app>
Sekarang mari kita terangkan servlet yang akan kita lindungi menggunakan JAAS. Sebelum ini, Gradle menjana kelas Apl untuk kami. Mari jadikan servlet. Seperti yang dinyatakan dalam spesifikasi dalam " BAB 2 Antara Muka Servlet ", bahawa " Untuk kebanyakan tujuan, Pembangun akan melanjutkan HttpServlet untuk melaksanakan servlet mereka ", iaitu, untuk menjadikan kelas sebagai servlet, anda perlu mewarisi kelas ini daripada HttpServlet
:
public class App extends HttpServlet {
public String getGreeting() {
return "Secret!";
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().print(getGreeting());
}
}
Seperti yang kami katakan, Servlet API ialah kontrak antara pelayan dan aplikasi web kami. Kontrak ini membolehkan kami menerangkan bahawa apabila pengguna menghubungi pelayan, pelayan akan menjana permintaan daripada pengguna dalam bentuk objek HttpServletRequest
dan menghantarnya kepada servlet. Ia juga akan menyediakan servlet dengan objek HttpServletResponse
supaya servlet boleh menulis respons kepadanya untuk pengguna. Setelah servlet selesai dijalankan, pelayan akan dapat memberikan respons berdasarkannya kepada pengguna HttpServletResponse
. Iaitu, servlet tidak berkomunikasi secara langsung dengan pengguna, tetapi hanya dengan pelayan. Untuk membolehkan pelayan mengetahui bahawa kami mempunyai servlet dan untuk permintaan apa yang perlu digunakan, kami perlu memberitahu pelayan tentang perkara ini dalam deskriptor penggunaan:
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>jaas.App</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/secret</url-pattern>
</servlet-mapping>
Dalam kes ini, semua permintaan /secret
tidak akan dialamatkan kepada satu servlet kami mengikut nama app
, yang sepadan dengan kelas jaas.App
. Seperti yang kami katakan sebelum ini, aplikasi web hanya boleh digunakan pada pelayan web. Pelayan web boleh dipasang secara berasingan (berdiri sendiri). Tetapi untuk tujuan semakan ini, pilihan alternatif adalah sesuai - berjalan pada pelayan terbenam. Ini bermakna pelayan akan dibuat dan dilancarkan secara pemrograman (pemalam akan melakukan ini untuk kami), dan pada masa yang sama aplikasi web kami akan digunakan padanya. Sistem binaan Gradle membolehkan anda menggunakan pemalam " Gradle Gretty Plugin " untuk tujuan ini:
plugins {
id 'war'
id 'org.gretty' version '2.2.0'
}
Selain itu, pemalam Gretty mempunyai dokumentasi yang baik . Mari kita mulakan dengan fakta bahawa pemalam Gretty membolehkan anda bertukar antara pelayan web yang berbeza. Ini diterangkan dengan lebih terperinci dalam dokumentasi: " Bertukar antara bekas servlet ". Mari bertukar kepada Tomcat, kerana... ia adalah salah satu yang paling popular digunakan, dan juga mempunyai dokumentasi yang baik dan banyak contoh dan masalah yang dianalisis:
gretty {
// Переключаемся с дефолтного Jetty на Tomcat
servletContainer = 'tomcat8'
// Укажем Context Path, он же Context Root
contextPath = '/jaas'
}
Kini kami boleh menjalankan "gradle appRun" dan kemudian aplikasi web kami akan tersedia di http://localhost:8080/jaas/secret
Pengesahan
Tetapan pengesahan selalunya terdiri daripada dua bahagian: tetapan pada bahagian pelayan dan tetapan pada sisi aplikasi web yang berjalan pada pelayan ini. Tetapan keselamatan aplikasi web tidak boleh tidak berinteraksi dengan tetapan keselamatan pelayan web, jika tidak ada sebab lain daripada itu aplikasi web tidak boleh tidak berinteraksi dengan pelayan web. Tidak sia-sia kami bertukar kepada Tomcat, kerana... Tomcat mempunyai seni bina yang diterangkan dengan baik (lihat " Seni Bina Apache Tomcat 8 "). Daripada penerangan seni bina ini jelas bahawa Tomcat, sebagai pelayan web, mewakili aplikasi web sebagai konteks tertentu, yang dipanggil " Konteks Tomcat ". Konteks ini membenarkan setiap aplikasi web mempunyai tetapan sendiri, diasingkan daripada aplikasi web lain. Selain itu, aplikasi web boleh mempengaruhi tetapan konteks ini. Fleksibel dan mudah. Untuk pemahaman yang lebih mendalam, kami mengesyorkan membaca artikel " Memahami Bekas Konteks Tomcat " dan bahagian dokumentasi Tomcat " Bekas Konteks ". Seperti yang dinyatakan di atas, aplikasi web kami boleh mempengaruhi Konteks Tomcat aplikasi kami menggunakan/META-INF/context.xml
. Dan salah satu tetapan yang sangat penting yang boleh kita pengaruhi ialah Alam Keselamatan. Alam Keselamatan ialah sejenis "kawasan keselamatan". Kawasan yang menetapkan tetapan keselamatan khusus. Sehubungan itu, apabila menggunakan Alam Keselamatan, kami menggunakan tetapan keselamatan yang ditakrifkan untuk Alam ini. Alam Keselamatan diuruskan oleh bekas, i.e. pelayan web, bukan aplikasi web kami. Kami hanya boleh memberitahu pelayan skop keselamatan yang perlu diperluaskan kepada aplikasi kami. Dokumentasi Tomcat dalam bahagian " The Realm Component " menerangkan Alam sebagai koleksi data tentang pengguna dan peranan mereka untuk melaksanakan pengesahan. Tomcat menyediakan satu set pelaksanaan Alam Keselamatan yang berbeza, salah satunya ialah " Alam Jaas ". Setelah memahami sedikit istilah, mari kita terangkan Konteks Tomcat dalam fail /META-INF/context.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Realm className="org.apache.catalina.realm.JAASRealm"
appName="JaasLogin"
userClassNames="jaas.login.UserPrincipal"
roleClassNames="jaas.login.RolePrincipal"
configFile="jaas.config" />
</Context>
appName
- nama aplikasi. Tomcat akan cuba memadankan nama ini dengan nama yang dinyatakan dalam configFile
. configFile
- ini ialah "fail konfigurasi log masuk". Contoh ini boleh dilihat dalam dokumentasi JAAS: " Lampiran B: Contoh Konfigurasi Log Masuk ". Di samping itu, adalah penting bahawa fail ini akan dicari dahulu dalam sumber. Oleh itu, aplikasi web kami boleh menyediakan fail ini sendiri. Atribut userClassNames
dan roleClassNames
mengandungi petunjuk kelas yang mewakili prinsipal pengguna. JAAS memisahkan konsep "pengguna" dan "peranan" sebagai dua konsep berbeza java.security.Principal
. Mari kita huraikan kelas di atas. Mari kita buat pelaksanaan paling mudah untuk prinsipal pengguna:
public class UserPrincipal implements Principal {
private String name;
public UserPrincipal(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
}
Kami akan mengulangi pelaksanaan yang sama untuk RolePrincipal
. Seperti yang anda boleh lihat daripada antara muka, perkara utama bagi Pengetua ialah menyimpan dan mengembalikan beberapa nama (atau ID) yang mewakili Pengetua. Kini, kami mempunyai Alam Keselamatan, kami mempunyai kelas utama. Ia kekal untuk mengisi fail daripada configFile
atribut " ", aka login configuration file
. Penerangannya boleh didapati dalam dokumentasi Tomcat: " The Realm Component ".
\src\main\resources\jaas.config
Mari kita tetapkan kandungan fail ini:
JaasLogin {
jaas.login.JaasLoginModule required debug=true;
};
Perlu diingat bahawa context.xml
nama yang sama digunakan di sini dan di dalam. Ini memetakan Alam Keselamatan kepada LoginModule. Jadi, Tomcat Context memberitahu kami kelas mana yang mewakili pengetua, serta LoginModule yang hendak digunakan. Apa yang perlu kita lakukan ialah melaksanakan LoginModule ini. LoginModule mungkin salah satu perkara yang paling menarik dalam JAAS. Dokumentasi rasmi akan membantu kami dalam membangunkan LoginModule: " Java Authentication and Authorization Service (JAAS): LoginModule Developer's Guide ". Mari kita laksanakan modul log masuk. Mari buat kelas yang melaksanakan antara muka LoginModule
:
public class JaasLoginModule implements LoginModule {
}
Mula-mula kita menerangkan kaedah permulaan LoginModule
:
private CallbackHandler handler;
private Subject subject;
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, <String, ?> sharedState, Map<String, ?> options) {
handler = callbackHandler;
this.subject = subject;
}
Kaedah ini akan menyimpan Subject
, yang akan kami sahkan dan isi dengan maklumat tentang pengetua. Kami juga akan menyimpan untuk kegunaan masa hadapan CallbackHandler
, yang diberikan kepada kami. Dengan bantuan, CallbackHandler
kami boleh meminta pelbagai maklumat tentang subjek pengesahan sedikit kemudian. Anda boleh membaca lebih lanjut mengenainya CallbackHandler
dalam bahagian dokumentasi yang sepadan: " Panduan Rujukan JAAS: CallbackHandler ". Seterusnya, kaedah login
untuk pengesahan dilaksanakan Subject
. Ini ialah fasa pertama pengesahan:
@Override
public boolean login() throws LoginException {
// Добавляем колбэки
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("login");
callbacks[1] = new PasswordCallback("password", true);
// При помощи колбэков получаем через CallbackHandler логин и пароль
try {
handler.handle(callbacks);
String name = ((NameCallback) callbacks[0]).getName();
String password = String.valueOf(((PasswordCallback) callbacks[1]).getPassword());
// Далее выполняем валидацию.
// Тут просто для примера проверяем определённые значения
if (name != null && name.equals("user123") && password != null && password.equals("pass123")) {
// Сохраняем информацию, которая будет использована в методе commit
// Не "пачкаем" Subject, т.к. не факт, что commit выполнится
// Для примера проставим группы вручную, "хардcodeно".
login = name;
userGroups = new ArrayList<String>();
userGroups.add("admin");
return true;
} else {
throw new LoginException("Authentication failed");
}
} catch (IOException | UnsupportedCallbackException e) {
throw new LoginException(e.getMessage());
}
}
Adalah penting bahawa login
kita tidak harus mengubah Subject
. Perubahan sedemikian seharusnya hanya berlaku dalam kaedah pengesahan commit
. Seterusnya, kita mesti menerangkan kaedah untuk mengesahkan pengesahan yang berjaya:
@Override
public boolean commit() throws LoginException {
userPrincipal = new UserPrincipal(login);
subject.getPrincipals().add(userPrincipal);
if (userGroups != null && userGroups.size() > 0) {
for (String groupName : userGroups) {
rolePrincipal = new RolePrincipal(groupName);
subject.getPrincipals().add(rolePrincipal);
}
}
return true;
}
Ia mungkin kelihatan pelik untuk memisahkan kaedah login
dan commit
. Tetapi intinya ialah modul log masuk boleh digabungkan. Dan untuk pengesahan yang berjaya, beberapa modul log masuk mungkin perlu berfungsi dengan jayanya. Dan hanya jika semua modul yang diperlukan telah berfungsi, kemudian simpan perubahan. Ini adalah fasa kedua pengesahan. Mari kita selesaikan dengan abort
dan kaedah logout
:
@Override
public boolean abort() throws LoginException {
return false;
}
@Override
public boolean logout() throws LoginException {
subject.getPrincipals().remove(userPrincipal);
subject.getPrincipals().remove(rolePrincipal);
return true;
}
Kaedah abort
dipanggil apabila fasa pertama pengesahan gagal. Kaedah logout
dipanggil apabila sistem log keluar. Setelah melaksanakan kami Login Module
dan mengkonfigurasinya Security Realm
, Sekarang kami perlu menunjukkan web.xml
fakta bahawa kami ingin menggunakan yang khusus Login Config
:
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>JaasLogin</realm-name>
</login-config>
Kami menyatakan nama Alam Keselamatan kami dan menentukan Kaedah Pengesahan - BASIC. Ini adalah salah satu jenis pengesahan yang diterangkan dalam Servlet API dalam bahagian " 13.6 Authentication ". Kekal n
GO TO FULL VERSION