ดำเนินการพิสูจน์ตัวตนผู้ใช้ใน Java EE / JSF โดยใช้ j_security_check


156

ฉันสงสัยว่าวิธีการปัจจุบันเกี่ยวกับการรับรองความถูกต้องของผู้ใช้สำหรับเว็บแอปพลิเคชันที่ใช้ JSF 2.0 (และหากมีส่วนประกอบใด ๆ อยู่) และกลไกหลัก Java EE 6 (เข้าสู่ระบบ / ตรวจสอบสิทธิ์ / ล็อกเอาท์) ด้วยข้อมูลผู้ใช้ เอกลักษณ์ การสอน Oracle Java EE ค่อนข้างกระจัดกระจายในเรื่องนี้

นี่คือโดยไม่ทำให้การใช้กรอบอื่น ๆ ทั้งหมดเช่นฤดูใบไม้ผลิรักษาความปลอดภัย (Acegi) หรือตะเข็บ แต่พยายามที่จะติดหวังกับ Java EE 6 แพลตฟอร์ม (รายละเอียดเว็บ) ใหม่ถ้าเป็นไปได้

คำตอบ:


85

หลังจากค้นหาเว็บและลองใช้วิธีต่างๆมากมายต่อไปนี้คือสิ่งที่ฉันแนะนำสำหรับการตรวจสอบสิทธิ์ Java EE 6:

ตั้งค่าขอบเขตความปลอดภัย:

ในกรณีของฉันฉันมีผู้ใช้ในฐานข้อมูล ดังนั้นฉันจึงติดตามโพสต์บล็อกนี้เพื่อสร้าง JDBC Realm ที่สามารถตรวจสอบผู้ใช้ตามชื่อผู้ใช้และรหัสผ่านที่แฮช MD5 ในตารางฐานข้อมูลของฉัน:

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

หมายเหตุ: การโพสต์พูดถึงผู้ใช้และตารางกลุ่มในฐานข้อมูล ฉันมีคลาสผู้ใช้ที่มีแอ็ตทริบิวต์ enum UserType ที่แม็พผ่านหมายเหตุประกอบ javax.persistence ไปยังฐานข้อมูล ฉันกำหนดค่าขอบเขตด้วยตารางเดียวกันสำหรับผู้ใช้และกลุ่มโดยใช้คอลัมน์ userType เป็นคอลัมน์กลุ่มและทำงานได้ดี

ใช้การตรวจสอบแบบฟอร์ม:

ยังคงติดตามโพสต์บล็อกข้างต้นกำหนดค่า web.xml และ sun-web.xml ของคุณ แต่แทนที่จะใช้การรับรองความถูกต้องพื้นฐานใช้ FORM (จริง ๆ แล้วมันไม่สำคัญว่าคุณใช้อันไหน แต่ฉันลงเอยด้วยการใช้ FORM) ใช้ HTML มาตรฐานไม่ใช่ JSF

จากนั้นใช้คำแนะนำของ BalusC ด้านบนเพื่อเริ่มต้นขี้เกียจเริ่มต้นข้อมูลผู้ใช้จากฐานข้อมูล เขาแนะนำให้ทำใน bean ที่ถูกจัดการเพื่อรับหลักการจากบริบทของใบหน้า ฉันใช้ session bean แทน stateful แทนเพื่อเก็บข้อมูลเซสชันสำหรับผู้ใช้แต่ละคนดังนั้นฉันจึงอัดบริบทเซสชัน:

 @Resource
 private SessionContext sessionContext;

ด้วยหลักการฉันสามารถตรวจสอบชื่อผู้ใช้และใช้ EJB Entity Manager รับข้อมูลผู้ใช้จากฐานข้อมูลและเก็บใน SessionInformation EJB

ออกจากระบบ:

ฉันมองไปรอบ ๆ เพื่อหาทางออกที่ดีที่สุด สิ่งที่ดีที่สุดที่ฉันพบคือใช้ 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());
  }
 }

แม้ว่าคำตอบของฉันจะช้าไปเมื่อพิจารณาถึงวันที่ของคำถามฉันหวังว่าสิ่งนี้จะช่วยให้คนอื่น ๆ ที่มาที่นี่จาก Google เหมือนฉันทำ

Ciao,

Vítor Souza


15
คำแนะนำเล็ก ๆ : คุณกำลังใช้ request.getSession (false) และการโทรเป็นโมฆะ () กับสิ่งนั้น request.getSession (false) อาจส่งคืนค่าว่างหากไม่มีเซสชัน ดีกว่าตรวจสอบว่ามันเป็นโมฆะก่อน)
Arjan Tijms

@Vitor: สวัสดี .. คุณต้องการที่จะพูดอะไรเกี่ยวกับเมื่อมันเป็นสิ่งที่ดีที่จะย้ายจากการรักษาความปลอดภัยตามคอนเทนเนอร์ไปยังทางเลือกเช่น shiro หรือคนอื่น ๆ ? ดูคำถามเพ่งความสนใจเพิ่มเติมได้ที่นี่: stackoverflow.com/questions/7782720/…
Rajat Gupta

ดูเหมือนว่า Glassfish JDBC Realm ไม่สนับสนุนการจัดเก็บแฮชของรหัสผ่านที่เค็ม เป็นวิธีที่ดีที่สุดที่จะใช้ในกรณีนี้หรือไม่?
Lii

ขออภัยไม่สามารถช่วยคุณได้ ฉันไม่ใช่ผู้เชี่ยวชาญ Glassfish อาจถามคำถามในชุดข้อความใหม่เพื่อดูว่าผู้คนพูดอะไร
Vítor E. Silva Souza

1
ใช่คุณสามารถทำงานกับเกลือโดยใช้ภาชนะแก้ว กำหนดค่า healm ของคุณไม่ใช้แฮชใด ๆ มันจะเปรียบเทียบค่าธรรมดาที่คุณใส่สำหรับรหัสผ่านบนHttpServletResponse#login(user, password)วิธีการที่คุณจะได้รับจาก DB ของผู้ใช้การทำซ้ำและสิ่งที่คุณใช้สำหรับการทำเกลือใส่รหัสผ่านที่ผู้ใช้ป้อนด้วยเกลือนั้นแล้วขอให้ภาชนะรับรองความถูกต้องHttpServletResponse#login(user, password).
emportella

152

ฉันคิดว่าคุณต้องการรูปแบบการตรวจสอบตามที่ใช้อธิบายการใช้งานj_security_checkและ

คุณสามารถทำได้ใน JSF โดยใช้ชื่อฟิลด์ที่กำหนดไว้ล่วงหน้าj_usernameและj_passwordแสดงให้เห็นในการสอน

เช่น

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

คุณสามารถทำโหลดขี้เกียจในUserทะเยอทะยานที่จะตรวจสอบว่าUserถูกบันทึกไว้แล้วในและถ้าไม่แล้วตรวจสอบว่าPrincipalมีอยู่ในคำขอและถ้าเป็นเช่นนั้นแล้วจะได้รับการเชื่อมโยงกับUserj_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;
    }

}

Userสามารถเข้าถึงได้อย่างเห็นได้ชัดใน JSF EL #{auth.user}โดย

ในการออกจากระบบให้ทำHttpServletRequest#logout()(และตั้งค่าUserเป็นโมฆะ!) คุณสามารถรับหมายเลขอ้างอิงของHttpServletRequestใน JSF ExternalContext#getRequest()ได้ นอกจากนี้คุณยังสามารถทำให้เซสชันใช้งานไม่ได้เลย

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

สำหรับส่วนที่เหลือ (การกำหนดผู้ใช้บทบาทและข้อ จำกัด ใน descriptor การปรับใช้และขอบเขต) เพียงทำตามบทช่วยสอน Java EE 6 และเอกสารประกอบ servletcontainer ด้วยวิธีปกติ


อัปเดต : คุณยังสามารถใช้ Servlet 3.0 ใหม่HttpServletRequest#login()เพื่อทำการเข้าสู่ระบบแบบเป็นโปรแกรมแทนการใช้j_security_checkซึ่งอาจไม่สามารถเข้าถึงได้โดยผู้แจกจ่ายในบาง servletcontainers ในกรณีนี้คุณสามารถใช้แบบฟอร์ม JSF แบบเต็มรูปแบบและ bean ด้วยusernameและpasswordคุณสมบัติและloginวิธีการที่มีลักษณะดังนี้:

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

และมุมมองนี้กำหนดขอบเขตของ bean ที่จัดการซึ่งจำหน้าแรกที่ร้องขอได้

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

วิธีนี้Userสามารถเข้าถึงได้ใน JSF EL #{user}โดย


1
ฉันได้อัปเดตคำถามเพื่อรวมข้อจำกัดความรับผิดชอบที่j_security_checkอาจส่งผลต่อ servletcontainers ทั้งหมด
BalusC

1
ลิงค์จากการสอน Java เกี่ยวกับการใช้ความปลอดภัยแบบเป็นโปรแกรมกับเว็บแอปพลิเคชัน: java.sun.com/javaee/6/docs/tutorial/doc/gjiie.html (ใช้ Servlets): ในคลาส servlet คุณสามารถใช้: @WebServlet(name="testServlet", urlPatterns={"/ testServlet "}) @ServletSecurity(@HttpConstraint(rolesAllowed = {"testUser", "admin”})) และตามวิธีการ ระดับ: @ServletSecurity(httpMethodConstraints={ @HttpMethodConstraint("GET"), @HttpMethodConstraint(value="POST", rolesAllowed={"testUser"})})
ngeek

3
และประเด็นของคุณคือ .. ไม่ว่าจะเป็นสิ่งที่ใช้ใน JSF? ใน JSF มี servlet เพียงอันเดียวFacesServletและคุณไม่สามารถ (และไม่ต้องการ) แก้ไขได้
BalusC

1
@BalusC - เมื่อคุณพูดว่าข้างต้นเป็นวิธีที่ดีที่สุดที่คุณหมายถึงการใช้ j_security_check หรือเข้าสู่ระบบโปรแกรม?
simgineer

3
@simgineer: URL ที่ร้องขอมีให้เป็นแอตทริบิวต์คำขอที่มีชื่อตามที่กำหนดRequestDispatcher.FORWARD_REQUEST_URIไว้ แอตทริบิวต์ที่ขออยู่ใน JSF ExternalContext#getRequestMap()โดยสามารถใช้ได้
BalusC

7

ควรกล่าวถึงว่าเป็นตัวเลือกในการปล่อยปัญหาการตรวจสอบสิทธิ์ให้กับคอนโทรลเลอร์ด้านหน้าอย่างสมบูรณ์เช่น Apache Webserver และประเมิน HttpServletRequest.getRemoteUser () แทนซึ่งเป็นตัวแทน JAVA สำหรับตัวแปรสภาพแวดล้อม REMOTE_USER สิ่งนี้อนุญาตให้มีการล็อกอินแบบซับซ้อนเช่นการพิสูจน์ตัวตน Shibboleth การกรองคำร้องขอไปยังคอนเทนเนอร์เซิร์ฟเล็ตผ่านเว็บเซิร์ฟเวอร์เป็นการออกแบบที่ดีสำหรับสภาพแวดล้อมการใช้งานจริงซึ่งมักจะใช้ mod_jk ทำเช่นนั้น


4

ปัญหาHttpServletRequest.login ไม่ได้ตั้งค่าสถานะการรับรองความถูกต้องในเซสชันได้รับการแก้ไขใน 3.0.1 อัปเดต glassfish เป็นเวอร์ชันล่าสุดและเสร็จสิ้น

การอัปเดตค่อนข้างตรงไปตรงมา:

glassfishv3/bin/pkg set-authority -P dev.glassfish.org
glassfishv3/bin/pkg image-update

4
ลิงก์เสีย คุณหมายถึงปัญหาใด
simgineer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.