Melakukan otentikasi pengguna di Java EE / JSF menggunakan j_security_check

156

Saya bertanya-tanya apa pendekatan saat ini mengenai otentikasi pengguna untuk aplikasi web yang menggunakan JSF 2.0 (dan jika ada komponen yang ada) dan mekanisme inti Java EE 6 (login / periksa izin / logout) dengan informasi pengguna disimpan dalam JPA kesatuan. Tutorial Oracle Java EE agak jarang dalam hal ini (hanya menangani servlets).

Ini tanpa menggunakan seluruh kerangka kerja lainnya, seperti Spring-Security (acegi), atau Seam, tetapi mencoba untuk tetap berharap dengan platform Java EE 6 baru (profil web) jika memungkinkan.

ngeek
sumber

Jawaban:

85

Setelah mencari di Web dan mencoba berbagai cara, inilah yang saya sarankan untuk otentikasi Java EE 6:

Siapkan bidang keamanan:

Dalam kasus saya, saya memiliki pengguna dalam database. Jadi saya mengikuti posting blog ini untuk membuat JDBC Realm yang dapat mengotentikasi pengguna berdasarkan nama pengguna dan kata sandi hash MD5 di tabel database saya:

http://blog.gamatam.com/2009/11/jdbc-realm-setup-with-glassfish-v3.html

Catatan: posting berbicara tentang pengguna dan tabel grup dalam database. Saya memiliki kelas Pengguna dengan atribut enum UserType yang dipetakan melalui anotasi javax.persistence ke database. Saya mengkonfigurasi ranah dengan tabel yang sama untuk pengguna dan grup, menggunakan kolom userType sebagai kolom grup dan itu berfungsi dengan baik.

Gunakan otentikasi formulir:

Masih mengikuti posting blog di atas, konfigurasikan web.xml dan sun-web.xml Anda, tetapi alih-alih menggunakan otentikasi BASIC, gunakan FORMULIR (sebenarnya, tidak masalah yang mana yang Anda gunakan, tapi saya akhirnya menggunakan FORMULIR). Gunakan HTML standar, bukan JSF.

Kemudian gunakan tip BalusC di atas tentang malas menginisialisasi informasi pengguna dari database. Dia menyarankan melakukannya dalam kacang yang dikelola mendapatkan kepala sekolah dari konteks wajah. Saya menggunakan kacang sesi stateful untuk menyimpan informasi sesi untuk setiap pengguna, jadi saya menyuntikkan konteks sesi:

 @Resource
 private SessionContext sessionContext;

Dengan kepala sekolah, saya dapat memeriksa nama pengguna dan, menggunakan EJB Entity Manager, mendapatkan informasi Pengguna dari database dan menyimpannya di SessionInformationEJB saya .

Keluar:

Saya juga mencari-cari cara terbaik untuk keluar. Yang terbaik yang saya temukan menggunakan Servlet:

 @WebServlet(name = "LogoutServlet", urlPatterns = {"/logout"})
 public class LogoutServlet extends HttpServlet {
  @Override
  protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
   HttpSession session = request.getSession(false);

   // Destroys the session for this user.
   if (session != null)
        session.invalidate();

   // Redirects back to the initial page.
   response.sendRedirect(request.getContextPath());
  }
 }

Meskipun jawaban saya benar-benar terlambat mengingat tanggal pertanyaan, saya harap ini membantu orang lain yang berakhir di sini dari Google, seperti yang saya lakukan.

Ciao,

Vítor Souza

Vítor E. Silva Souza
sumber
15
Sedikit nasihat: Anda menggunakan request.getSession (false) dan menelepon invalidate () untuk itu. request.getSession (false) dapat mengembalikan nol jika tidak ada sesi. Lebih baik periksa apakah batal dulu;)
Arjan Tijms
@Vitor: Hai..Apakah Anda ingin mengatakan sesuatu tentang kapan sebaiknya pindah dari keamanan berbasis wadah ke alternatif seperti shiro atau yang lain? Lihat lebih banyak pertanyaan terfokus di sini: stackoverflow.com/questions/7782720/…
Rajat Gupta
Sepertinya GlassF JDBC Realm tidak mendukung penyimpanan hash kata sandi asin. Apakah benar-benar praktik terbaik untuk menggunakannya dalam kasus itu?
Lii
Maaf, tidak dapat membantu Anda. Saya bukan ahli Glassfish. Mungkin tanyakan pertanyaan itu di utas baru untuk melihat apa yang orang katakan?
Vítor E. Silva Souza
1
Lii, Anda bisa bekerja dengan asin menggunakan wadah glassfish. Konfigurasikan healm Anda untuk tidak menggunakan hash. Ini akan membandingkan nilai polos yang Anda masukkan untuk kata sandi HttpServletResponse#login(user, password), dengan cara itu Anda bisa mendapatkan dari DB garam pengguna, iterasi dan apa pun yang Anda gunakan untuk pengasinan, hash kata sandi yang dimasukkan pengguna menggunakan garam itu dan kemudian minta wadah untuk mengotentikasi dengan HttpServletResponse#login(user, password).
emportella
152

Saya kira Anda ingin otentikasi berbasis formulir menggunakan deskriptor penyebaran dan j_security_check .

Anda juga dapat melakukan ini di JSF hanya dengan menggunakan nama bidang j_usernamedanj_password seperti yang ditunjukkan dalam tutorial.

Misalnya

<form action="j_security_check" method="post">
    <h:outputLabel for="j_username" value="Username" />
    <h:inputText id="j_username" />
    <br />
    <h:outputLabel for="j_password" value="Password" />
    <h:inputSecret id="j_password" />
    <br />
    <h:commandButton value="Login" />
</form>

Anda dapat melakukan pemuatan malas di Userpengambil untuk memeriksa apakah Usersudah masuk dan jika tidak, lalu periksa apakah Principalada dalam permintaan dan jika demikian, maka dapatkan yang Userterkait dengannya j_username.

package com.stackoverflow.q2206911;

import java.io.IOException;
import java.security.Principal;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;

@ManagedBean
@SessionScoped
public class Auth {

    private User user; // The JPA entity.

    @EJB
    private UserService userService;

    public User getUser() {
        if (user == null) {
            Principal principal = FacesContext.getCurrentInstance().getExternalContext().getUserPrincipal();
            if (principal != null) {
                user = userService.find(principal.getName()); // Find User by j_username.
            }
        }
        return user;
    }

}

The Userjelas diakses di JSF EL oleh #{auth.user}.

Untuk keluar, lakukan a HttpServletRequest#logout()(dan setel Userke nol!). Anda bisa mendapatkan pegangan HttpServletRequestdi JSF dengan ExternalContext#getRequest(). Anda juga dapat membatalkan sesi secara keseluruhan.

public String logout() {
    FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
    return "login?faces-redirect=true";
}

Untuk yang tersisa (mendefinisikan pengguna, peran, dan kendala dalam penerapan descriptor dan ranah), cukup ikuti tutorial Java EE 6 dan dokumentasi servletcontainer dengan cara biasa.


Pembaruan : Anda juga dapat menggunakan Servlet 3.0 baru HttpServletRequest#login()untuk melakukan login terprogram alih-alih menggunakan j_security_checkyang mungkin tidak dapat dijangkau oleh operator di beberapa server servlet. Dalam hal ini, Anda bisa menggunakan formulir JSF yang layak dan kacang dengan usernamedan passwordproperti dan loginmetode yang terlihat seperti ini:

<h:form>
    <h:outputLabel for="username" value="Username" />
    <h:inputText id="username" value="#{auth.username}" required="true" />
    <h:message for="username" />
    <br />
    <h:outputLabel for="password" value="Password" />
    <h:inputSecret id="password" value="#{auth.password}" required="true" />
    <h:message for="password" />
    <br />
    <h:commandButton value="Login" action="#{auth.login}" />
    <h:messages globalOnly="true" />
</h:form>

Dan tampilan ini mencakup kacang yang dikelola yang juga mengingat halaman yang awalnya diminta:

@ManagedBean
@ViewScoped
public class Auth {

    private String username;
    private String password;
    private String originalURL;

    @PostConstruct
    public void init() {
        ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
        originalURL = (String) externalContext.getRequestMap().get(RequestDispatcher.FORWARD_REQUEST_URI);

        if (originalURL == null) {
            originalURL = externalContext.getRequestContextPath() + "/home.xhtml";
        } else {
            String originalQuery = (String) externalContext.getRequestMap().get(RequestDispatcher.FORWARD_QUERY_STRING);

            if (originalQuery != null) {
                originalURL += "?" + originalQuery;
            }
        }
    }

    @EJB
    private UserService userService;

    public void login() throws IOException {
        FacesContext context = FacesContext.getCurrentInstance();
        ExternalContext externalContext = context.getExternalContext();
        HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();

        try {
            request.login(username, password);
            User user = userService.find(username, password);
            externalContext.getSessionMap().put("user", user);
            externalContext.redirect(originalURL);
        } catch (ServletException e) {
            // Handle unknown username/password in request.login().
            context.addMessage(null, new FacesMessage("Unknown login"));
        }
    }

    public void logout() throws IOException {
        ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
        externalContext.invalidateSession();
        externalContext.redirect(externalContext.getRequestContextPath() + "/login.xhtml");
    }

    // Getters/setters for username and password.
}

Dengan cara Userini dapat diakses di JSF EL oleh #{user}.

BalusC
sumber
1
Saya memperbarui pertanyaan untuk menyertakan penafian bahwa pengiriman ke j_security_checkmungkin tidak berfungsi pada semua servletcontainers.
BalusC
1
Tautan dari Tutorial Java tentang Menggunakan Keamanan Programmatic dengan Aplikasi Web: java.sun.com/javaee/6/docs/tutorial/doc/gjiie.html (menggunakan Servlets): Pada kelas servlet Anda dapat menggunakan: @WebServlet(name="testServlet", urlPatterns={"/ testServlet "}) @ServletSecurity(@HttpConstraint(rolesAllowed = {"testUser", "admin”})) Dan pada metode per level: @ServletSecurity(httpMethodConstraints={ @HttpMethodConstraint("GET"), @HttpMethodConstraint(value="POST", rolesAllowed={"testUser"})})
ngeek
3
Dan maksud Anda adalah ..? Apakah ini berlaku di JSF? Nah, di JSF hanya ada satu servlet, FacesServletdan Anda tidak bisa (dan tidak mau) memodifikasinya.
BalusC
1
@ BalusC - Ketika Anda mengatakan di atas adalah cara terbaik yang Anda maksud menggunakan j_security_check atau login terprogram?
simgineer
3
@simgineer: URL yang diminta tersedia sebagai atribut permintaan dengan nama yang ditentukan oleh RequestDispatcher.FORWARD_REQUEST_URI. Atribut permintaan tersedia di JSF oleh ExternalContext#getRequestMap().
BalusC
7

Harus disebutkan bahwa ini adalah opsi untuk sepenuhnya meninggalkan masalah otentikasi ke pengontrol depan, misalnya Apache Webserver dan mengevaluasi HttpServletRequest.getRemoteUser () sebagai gantinya, yang merupakan representasi JAVA untuk variabel lingkungan REMOTE_USER. Ini juga memungkinkan desain log in yang canggih seperti otentikasi Shibboleth. Memfilter Permintaan ke wadah servlet melalui server web adalah desain yang baik untuk lingkungan produksi, sering mod_jk digunakan untuk melakukannya.

Matthias Ronge
sumber
4

Masalah HttpServletRequest.login tidak mengatur status otentikasi dalam sesi telah diperbaiki di 3.0.1. Perbarui glassfish ke versi terbaru dan Anda selesai.

Memperbarui cukup mudah:

glassfishv3/bin/pkg set-authority -P dev.glassfish.org
glassfishv3/bin/pkg image-update
Hans Bacher
sumber
4
tautan rusak. masalah apa yang Anda maksud?
simgineer