Thực hiện xác thực người dùng trong Java EE / JSF bằng j_security_check


156

Tôi đang tự hỏi cách tiếp cận hiện tại liên quan đến xác thực người dùng cho ứng dụng web sử dụng JSF 2.0 (và nếu có bất kỳ thành phần nào tồn tại) và các cơ chế cốt lõi của Java EE 6 (đăng nhập / kiểm tra quyền / đăng xuất) với thông tin người dùng được giữ trong JPA thực thể. Hướng dẫn Oracle Java EE hơi thưa thớt về điều này (chỉ xử lý các servlet).

Điều này không sử dụng toàn bộ khung công tác khác, như Spring-Security (acegi) hoặc Seam, nhưng cố gắng gắn bó với nền tảng Java EE 6 mới (hồ sơ web) nếu có thể.

Câu trả lời:


85

Sau khi tìm kiếm trên Web và thử nhiều cách khác nhau, đây là những gì tôi đề xuất cho xác thực Java EE 6:

Thiết lập lĩnh vực bảo mật:

Trong trường hợp của tôi, tôi đã có người dùng trong cơ sở dữ liệu. Vì vậy, tôi đã theo dõi bài đăng trên blog này để tạo ra một Vương quốc JDBC có thể xác thực người dùng dựa trên tên người dùng và mật khẩu băm MD5 trong bảng cơ sở dữ liệu của tôi:

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

Lưu ý: bài đăng nói về người dùng và bảng nhóm trong cơ sở dữ liệu. Tôi đã có một lớp Người dùng với thuộc tính enum UserType được ánh xạ thông qua các chú thích javax.persistence vào cơ sở dữ liệu. Tôi đã cấu hình vương quốc với cùng một bảng cho người dùng và nhóm, sử dụng cột userType làm cột nhóm và nó hoạt động tốt.

Sử dụng xác thực mẫu:

Vẫn theo dõi bài đăng trên blog, định cấu hình web.xml và sun-web.xml của bạn, nhưng thay vì sử dụng xác thực BASIC, hãy sử dụng FORM (thực tế, không quan trọng bạn sử dụng cái nào, nhưng cuối cùng tôi đã sử dụng FORM). Sử dụng HTML tiêu chuẩn, không phải là JSF.

Sau đó sử dụng mẹo của BalusC ở trên về việc lười khởi tạo thông tin người dùng từ cơ sở dữ liệu. Ông đề nghị làm điều đó trong một hạt đậu được quản lý lấy hiệu trưởng từ bối cảnh khuôn mặt. Thay vào đó, tôi đã sử dụng một bean phiên có trạng thái để lưu trữ thông tin phiên cho mỗi người dùng, vì vậy tôi đã chèn bối cảnh phiên:

 @Resource
 private SessionContext sessionContext;

Với hiệu trưởng, tôi có thể kiểm tra tên người dùng và, bằng cách sử dụng Trình quản lý thực thể EJB, lấy thông tin Người dùng từ cơ sở dữ liệu và lưu trữ trong SessionInformationEJB của tôi .

Đăng xuất:

Tôi cũng tìm kiếm cách tốt nhất để đăng xuất. Cách tốt nhất mà tôi đã tìm thấy là sử dụng 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());
  }
 }

Mặc dù câu trả lời của tôi thực sự muộn khi xem xét ngày của câu hỏi, tôi hy vọng điều này sẽ giúp những người khác kết thúc ở đây từ Google, giống như tôi đã làm.

Ciao

Vítor Souza


15
Một lời khuyên nhỏ: bạn đang sử dụng request.getSession (sai) và gọi không hợp lệ () trên đó. request.getSession (sai) có thể trả về null nếu không có phiên. Kiểm tra tốt hơn nếu nó không có giá trị trước;)
Arjan Tijms

@Vitor: Hi..Bạn có muốn nói gì khi chuyển từ bảo mật dựa trên container sang các lựa chọn thay thế như shiro hoặc những người khác không? Xem thêm câu hỏi tập trung tại đây: stackoverflow.com/questions/7782720/
Kẻ

Có vẻ như Glassfish JDBC Realm không hỗ trợ lưu trữ băm mật khẩu muối. Có thực sự tốt nhất để sử dụng nó trong trường hợp đó?
Lii

Xin lỗi, không thể giúp bạn. Tôi không phải là chuyên gia về Glassfish. Có thể hỏi câu hỏi đó trong một chủ đề mới để xem mọi người nói gì?
Vítor E. Silva Souza

1
Lii, bạn có thể làm việc với muối bằng cách sử dụng hộp đựng cá thủy tinh. Cấu hình healm của bạn không sử dụng bất kỳ băm. Nó sẽ so sánh giá trị đơn giản mà bạn chèn cho mật khẩu HttpServletResponse#login(user, password), theo cách đó bạn có thể nhận được từ DB muối, lặp và bất cứ thứ gì bạn sử dụng để tạo muối, băm mật khẩu người dùng đã nhập bằng muối đó và sau đó yêu cầu bộ chứa xác thực HttpServletResponse#login(user, password).
emportella

152

Tôi cho rằng bạn muốn xác thực dựa trên mẫu bằng cách sử dụng các mô tả triển khaij_security_check.

Bạn cũng có thể thực hiện điều này trong JSF bằng cách chỉ sử dụng cùng tên trường được xác định trước j_usernamej_passwordnhư được trình bày trong hướng dẫn.

Ví dụ

<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>

Bạn có thể lười tải trong Usergetter để kiểm tra xem Userđã đăng nhập chưa và nếu không, sau đó kiểm tra xem Principalnó có trong yêu cầu không và nếu có, thì hãy Userliên kết với 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;
    }

}

Các Userrõ ràng là dễ tiếp cận trong JSF EL bằng #{auth.user}.

Để đăng xuất, hãy thực hiện HttpServletRequest#logout()(và đặt Userthành null!). Bạn có thể xử lý HttpServletRequesttrong JSF bằng cách ExternalContext#getRequest(). Bạn cũng có thể làm mất hiệu lực phiên hoàn toàn.

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

Đối với phần còn lại (xác định người dùng, vai trò và các ràng buộc trong mô tả triển khai và lĩnh vực), chỉ cần làm theo hướng dẫn Java EE 6 và tài liệu của người phục vụ theo cách thông thường.


Cập nhật : bạn cũng có thể sử dụng Servlet 3.0 mới HttpServletRequest#login()để thực hiện đăng nhập theo chương trình thay vì sử dụng j_security_checkmà có thể không truy cập được bởi một người điều phối trong một số trình điều khiển dịch vụ. Trong trường hợp này, bạn có thể sử dụng một biểu mẫu JSF đầy đủ và một bean với usernamepasswordcác thuộc tính và một loginphương thức giống như sau:

<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>

Và khung nhìn này được quản lý trong phạm vi được quản lý cũng nhớ trang được yêu cầu ban đầu:

@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.
}

Bằng cách Usernày, có thể truy cập được trong JSF EL #{user}.


1
Tôi đã cập nhật câu hỏi để bao gồm một tuyên bố từ chối trách nhiệm rằng việc gửi vào j_security_checkcó thể không hoạt động trên tất cả các nhà cung cấp dịch vụ.
BalusC

1
Liên kết từ Hướng dẫn Java về Sử dụng bảo mật lập trình với các ứng dụng web: java.sun.com/javaee/6/docs/tutorial/doc/gjiie.html (sử dụng Servlets): Trên lớp servlet, bạn có thể sử dụng: @WebServlet(name="testServlet", urlPatterns={"/ testServlet "}) @ServletSecurity(@HttpConstraint(rolesAllowed = {"testUser", "admin”})) Và trên mỗi phương thức cấp độ: @ServletSecurity(httpMethodConstraints={ @HttpMethodConstraint("GET"), @HttpMethodConstraint(value="POST", rolesAllowed={"testUser"})})
ngeek

3
Và quan điểm của bạn là ..? Liệu điều này có thể áp dụng được trong JSF không? Chà, trong JSF chỉ có một servlet, FacesServletvà bạn không thể (và không muốn) sửa đổi nó.
BalusC

1
@BalusC - Khi bạn nói ở trên là cách tốt nhất bạn có nghĩa là sử dụng j_security_check hoặc đăng nhập theo chương trình?
simgineer

3
@simgineer: URL được yêu cầu có sẵn dưới dạng một thuộc tính yêu cầu với tên được xác định bởi RequestDispatcher.FORWARD_REQUEST_URI. Các thuộc tính yêu cầu có trong JSF có sẵn bởi ExternalContext#getRequestMap().
BalusC

7

Cần phải đề cập rằng đó là một tùy chọn để hoàn toàn để lại các vấn đề xác thực cho bộ điều khiển phía trước, ví dụ: Máy chủ web Apache và đánh giá httpServletRequest.getRemoteUser (), là đại diện của JAVA cho biến môi trường REMOTE_USER. Điều này cũng cho phép đăng nhập tinh vi trong các thiết kế như xác thực Shibboleth. Lọc các yêu cầu tới một thùng chứa servlet thông qua một máy chủ web là một thiết kế tốt cho môi trường sản xuất, thường mod_jk được sử dụng để làm như vậy.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.