Menyelesaikan masalah log keluar dengan betul dan elegan

Banyak aplikasi Web tidak mengandungi maklumat sulit dan peribadi seperti nombor akaun bank atau data kad kredit. Tetapi ada yang mengandungi data sensitif yang memerlukan semacam skema perlindungan kata laluan. Sebagai contoh, di sebuah kilang di mana pekerja mesti menggunakan aplikasi Web untuk memasukkan maklumat lembar waktu, mengakses kursus latihan mereka, dan mengkaji kadar jam mereka, dan lain-lain, menggunakan SSL (Secure Socket Layer) akan berlebihan (halaman SSL tidak di-cache; perbincangan mengenai SSL adalah di luar skop artikel ini) Tetapi tentunya aplikasi ini memerlukan semacam perlindungan kata laluan. Jika tidak, pekerja (dalam kes ini, pengguna aplikasi) akan menemui maklumat sensitif dan sulit mengenai semua pekerja kilang.

Contoh yang serupa dengan situasi di atas termasuk komputer yang dilengkapi Internet di perpustakaan awam, hospital, dan kafe Internet. Dalam persekitaran seperti ini di mana pengguna berkongsi beberapa komputer biasa, melindungi data peribadi pengguna adalah penting. Pada masa yang sama, aplikasi yang dirancang dengan baik dan dilaksanakan dengan baik tidak memerlukan apa-apa tentang pengguna dan memerlukan latihan yang paling sedikit.

Mari lihat bagaimana aplikasi Web yang sempurna akan bertindak dalam dunia yang sempurna: Seorang pengguna mengarahkan penyemak imbasnya ke URL. Aplikasi Web memaparkan halaman masuk yang meminta pengguna memasukkan kelayakan yang sah. Dia menaip userid dan kata laluan. Dengan andaian kelayakan yang diberikan adalah betul, setelah proses pengesahan, aplikasi Web membolehkan pengguna mengakses kawasan yang dibenarkan secara bebas. Apabila tiba masanya untuk berhenti, pengguna menekan butang Logout halaman. Aplikasi Web memaparkan halaman yang meminta pengguna mengesahkan bahawa dia memang mahu keluar. Setelah dia menekan butang OK, sesi berakhir, dan aplikasi Web menyajikan halaman masuk yang lain. Pengguna kini boleh menjauh dari komputer tanpa perlu risau pengguna lain mengakses data peribadinya. Pengguna lain duduk di komputer yang sama. Dia menekan butang Kembali;aplikasi Web tidak boleh menunjukkan mana-mana halaman dari sesi pengguna terakhir. Sebenarnya, aplikasi Web mesti sentiasa menjaga halaman log masuk sehingga pengguna kedua memberikan kelayakan yang sah — barulah dia dapat mengunjungi kawasan yang dibenarkan.

Melalui contoh program, artikel ini menunjukkan kepada anda bagaimana untuk mencapai tingkah laku tersebut dalam aplikasi Web.

Sampel JSP

Untuk menggambarkan penyelesaian dengan cekap, artikel ini dimulakan dengan menunjukkan masalah yang dihadapi dalam aplikasi Web, logoutSampleJSP1 . Aplikasi sampel ini mewakili pelbagai aplikasi Web yang tidak mengendalikan proses log keluar dengan betul. logoutSampleJSP1 terdiri daripada halaman berikut JSP (JavaServer Pages): login.jsp, home.jsp, secure1.jsp, secure2.jsp, logout.jsp, loginAction.jsp, dan logoutAction.jsp. Halaman JSP home.jsp, secure1.jsp, secure2.jsp, dan logout.jspdilindungi daripada pengguna yang tidak disahkan, iaitu, ia mengandungi maklumat yang selamat dan tidak akan muncul pada pelayar sama ada sebelum pengguna log masuk atau selepas pengguna log keluar. Halaman tersebut login.jspmengandungi borang di mana pengguna memasukkan nama pengguna dan kata laluan mereka. Halamanlogout.jspmengandungi borang yang meminta pengguna mengesahkan bahawa mereka memang mahu log keluar. Halaman JSP loginAction.jspdan logoutAction.jspbertindak sebagai pengawal dan mengandungi kod yang masing-masing melakukan tindakan log masuk dan log keluar.

Contoh aplikasi Web kedua, logoutSampleJSP2 menunjukkan cara untuk mengatasi masalah logoutSampleJSP1. Walau bagaimanapun, logoutSampleJSP2 tetap bermasalah. Masalah logout masih dapat terserlah dalam keadaan khas.

Aplikasi Web sampel ketiga, logoutSampleJSP3 bertambah baik semasa logoutSampleJSP2 dan mewakili penyelesaian yang boleh diterima untuk masalah logout.

Contoh akhir log keluar aplikasi WebSampleStruts menunjukkan bagaimana Jakarta Struts dapat menyelesaikan masalah logout dengan elegan.

Catatan: Sampel yang menyertai artikel ini telah ditulis dan diuji untuk penyemak imbas Microsoft Internet Explorer (IE), Netscape Navigator, Mozilla, FireFox, dan Avant terkini.

Tindakan log masuk

Artikel cemerlang Brian Pontarelli "J2EE Security: Container Versus Custom" membincangkan pendekatan pengesahan J2EE yang berbeza. Ternyata, pendekatan pengesahan dasar dan bentuk HTTP tidak menyediakan mekanisme untuk menangani log keluar. Oleh itu, penyelesaiannya adalah dengan menggunakan pelaksanaan keamanan khusus, kerana memberikan fleksibilitas yang paling.

Amalan umum dalam pendekatan pengesahan kustom adalah dengan mengambil bukti kelayakan pengguna dari penyerahan borang dan memeriksa alam keselamatan backend seperti LDAP (protokol akses direktori ringan) atau RDBMS (sistem pengurusan pangkalan data hubungan). Sekiranya kelayakan yang diberikan sah, tindakan log masuk akan menyimpan beberapa objek dalam HttpSessionobjek tersebut. Kehadiran objek ini HttpSessionmenunjukkan bahawa pengguna telah log masuk ke aplikasi Web. Demi kejelasan, semua aplikasi sampel yang disertakan hanya menyimpan rentetan nama pengguna di HttpSessionuntuk menunjukkan bahawa pengguna log masuk. Penyenaraian 1 menunjukkan coretan kod yang terdapat di dalam halaman loginAction.jspuntuk menggambarkan tindakan log masuk:

Penyenaraian 1

// ... // menginisialisasi objek RequestDispatcher; tetapkan ke halaman utama secara lalai RequestDispatcher rd = request.getRequestDispatcher ("home.jsp"); // Siapkan sambungan dan pernyataan rs = stmt.executeQuery ("pilih kata laluan dari USER di mana userName = '" + userName + "'"); if (rs.next ()) {// Pertanyaan hanya mengembalikan 1 rekod dalam set hasil; hanya 1 kata laluan bagi setiap nama pengguna yang juga merupakan kunci utama jika (rs.getString ("kata laluan"). sama dengan (kata laluan)) {// Sekiranya kata laluan sah sesi.setAttribute ("Pengguna", nama pengguna); // Menyimpan rentetan nama pengguna dalam objek sesi} yang lain {// Kata Laluan tidak sepadan, iaitu permintaan kata laluan pengguna yang tidak sah.setAttribute ("Ralat", "Kata laluan tidak sah."); rd = request.getRequestDispatcher ("login.jsp"); }} // Tidak ada catatan dalam set hasil, iaitu,nama pengguna tidak sah lain {request.setAttribute ("Ralat", "Nama pengguna tidak sah."); rd = request.getRequestDispatcher ("login.jsp"); }} // Sebagai pengawal, akhirnya loginAction.jsp meneruskan ke "login.jsp" atau "home.jsp" rd.forward (permintaan, respons); // ...

Dalam aplikasi Web contoh ini dan selebihnya, bidang keselamatan dianggap sebagai RDBMS. Walau bagaimanapun, konsep artikel ini telus dan berlaku untuk semua bidang keselamatan.

Tindakan log keluar

Tindakan logout hanya melibatkan membuang rentetan nama pengguna dan memanggil invalidate()kaedah pada HttpSessionobjek pengguna . Penyenaraian 2 menunjukkan coretan kod yang terdapat di halaman logoutAction.jspuntuk menggambarkan tindakan log keluar:

Penyenaraian 2

// ... session.removeAttribute ("Pengguna"); sesi.invalidate (); // ...

Cegah akses tanpa kebenaran ke halaman JSP yang dilindungi

Untuk merakam, setelah pengesahan kelayakan yang berjaya diambil dari penyerahan borang, tindakan masuk hanya meletakkan rentetan nama pengguna di HttpSessionobjek. Tindakan log keluar melakukan sebaliknya. Ia mengeluarkan rentetan nama pengguna dari HttpSessiondan memanggil invalidate()kaedah pada HttpSessionobjek. Untuk kedua-dua tindakan log masuk dan logout sama sekali bermakna, semua halaman JSP yang dilindungi mesti memeriksa rentetan nama pengguna yang terkandung di dalamnya HttpSessionuntuk menentukan sama ada pengguna sedang log masuk. Sekiranya HttpSessionmengandungi rentetan nama pengguna - petunjuk bahawa pengguna telah log masuk— aplikasi Web akan menghantar kepada penyemak imbas kandungan dinamik di halaman JSP yang lain. Jika tidak, halaman JSP akan mengemukakan belakang aliran kawalan ke halaman log masuk, login.jsp. Halaman JSP home.jsp, secure1.jsp,secure2.jsp, dan logout.jspsemuanya mengandungi coretan kod yang ditunjukkan dalam Penyenaraian 3:

Penyenaraian 3

// ... String userName = (String) session.getAttribute ("Pengguna"); if (null == userName) {request.setAttribute ("Ralat", "Sesi telah berakhir. Sila log masuk."); RequestDispatcher rd = request.getRequestDispatcher ("login.jsp"); rd.forward (permintaan, tindak balas); } // ... // Benarkan isi dinamik dalam JSP ini disajikan ke penyemak imbas // ...

Coretan kod ini mengambil rentetan nama pengguna dari HttpSession. Sekiranya rentetan nama pengguna yang diambil adalah nol , aplikasi Web terganggu dengan meneruskan aliran kawalan kembali ke halaman log masuk dengan mesej ralat "Sesi telah berakhir. Sila log masuk.". Jika tidak, aplikasi Web membenarkan aliran normal melalui seluruh halaman JSP yang dilindungi, sehingga membolehkan kandungan dinamik halaman JSP dilayan.

Menjalankan log keluarSampelJSP1

Menjalankan logoutSampleJSP1 menghasilkan tingkah laku berikut:

  • The application behaves correctly by preventing the dynamic content of the protected JSP pages home.jsp, secure1.jsp, secure2.jsp, and logout.jsp from being served if the user has not logged in. In other words, assuming the user has not logged in but points the browser to those JSP pages' URLs, the Web application forwards the control flow to the login page with the error message "Session has ended. Please log in.".
  • Likewise, the application behaves correctly by preventing the dynamic content of the protected JSP pages home.jsp, secure1.jsp, secure2.jsp, and logout.jsp from being served after the user has already logged out. In other words, after the user has already logged out, if he points the browser to the URLs of those JSP pages, the Web application will forward the control flow to the login page with the error message "Session has ended. Please log in.".
  • The application does not behave correctly if, after the user has already logged out, he clicks on the Back button to navigate back to the previous pages. The protected JSP pages reappear on the browser even after the session has ended (with the user logging out). However, continual selection of any link on these pages brings the user to the login page with the error message "Session has ended. Please log in.".

Prevent the browsers from caching

The root of the problem is the Back button that exists on most modern browsers. When the Back button is clicked, the browser by default does not request a page from the Web server. Instead, the browser simply reloads the page from its cache. This problem is not limited to Java-based (JSP/servlets/Struts) Web applications; it is also common across all technologies and affects PHP-based (Hypertext Preprocessor), ASP-based, (Active Server Pages), and .Net Web applications.

After the user clicks on the Back button, no round trip back to the Web servers (generally speaking) or the application servers (in Java's case) takes place. The interaction occurs among the user, the browser, and the cache. So even with the presence of Listing 3's code in the protected JSP pages such as home.jsp, secure1.jsp, secure2.jsp, and logout.jsp, this code never gets the chance to execute when the Back button is clicked.

Depending on whom you ask, the caches that sit between the application servers and the browsers can either be a good thing or a bad thing. These caches do in fact offer a few advantages, but that's mostly for static HTML pages or pages that are graphic- or image-intensive. Web applications, on the other hand are more data-oriented. As data in a Web application is likely to change frequently, it is more important to display fresh data than save some response time by going to the cache and displaying stale or out-of-date information.

Fortunately, the HTTP "Expires" and "Cache-Control" headers offer the application servers a mechanism for controlling the browsers' and proxies' caches. The HTTP Expires header dictates to the proxies' caches when the page's "freshness" will expire. The HTTP Cache-Control header, which is new under the HTTP 1.1 Specification, contains attributes that instruct the browsers to prevent caching on any desired page in the Web application. When the Back button encounters such a page, the browser sends the HTTP request to the application server for a new copy of that page. The descriptions for necessary Cache-Control headers' directives follow:

  • no-cache: forces caches to obtain a new copy of the page from the origin server
  • no-store: directs caches not to store the page under any circumstance

For backward compatibility to HTTP 1.0, the Pragma:no-cache directive, which is equivalent to Cache-Control:no-cache in HTTP 1.1, can also be included in the header's response.

By leveraging the HTTP headers' cache directives, the second sample Web application, logoutSampleJSP2, that accompanies this article remedies logoutSampleJSP1. logoutSampleJSP2 differs from logoutSampleJSP1 in that Listing 4's code snippet is placed at the top of all protected JSP pages, such as home.jsp, secure1.jsp, secure2.jsp, and logout.jsp:

Listing 4

// ... respon.setHeader ("Cache-Control", "no-cache"); // Memaksa cache untuk mendapatkan salinan baru halaman dari respons pelayan asal.setHeader ("Cache-Control", "no-store"); // Mengarahkan cache untuk tidak menyimpan halaman dalam keadaan apa pun respons.setDateHeader ("Expires", 0); // Menyebabkan cache proksi melihat halaman sebagai "basi" respons.setHeader ("Pragma", "no-cache"); // Keserasian HTTP 1.0 ke belakang String userName = (String) session.getAttribute ("User"); if (null == userName) {request.setAttribute ("Ralat", "Sesi telah berakhir. Sila log masuk."); RequestDispatcher rd = request.getRequestDispatcher ("login.jsp"); rd.forward (permintaan, tindak balas); } // ...