2012-05-15 60 views
8

Tôi khá mới đối với Java và Spring 3 (sử dụng chủ yếu PHP trong 8 năm qua). Tôi đã nhận được an ninh mùa xuân 3 để làm việc với tất cả các userDetails mặc định và userDetailsService và tôi biết tôi có thể truy cập đăng nhập vào tên người dùng của người dùng trong một bộ điều khiển bằng cách sử dụng:Bảo mật mùa xuân: userdetails tùy chỉnh

Authentication auth = SecurityContextHolder.getContext().getAuthentication(); 
String username = auth.getName(); //get logged in username 

Nhưng có hai vấn đề tôi không thể hình ra:

  1. Có rất nhiều chi tiết người dùng khác mà tôi muốn lưu khi người dùng đăng nhập (như DOB, giới tính, v.v.) và có thể truy cập được qua bộ điều khiển sau này. Tôi cần làm gì để đối tượng userDetails được tạo có chứa các trường tùy chỉnh của tôi?

  2. Tôi đã gọi "HttpSession session = request.getSession (true);" ở đầu mỗi phương pháp trong bộ điều khiển của tôi. Có thể lưu trữ userDetails của người dùng đã đăng nhập trong một phiên khi đăng nhập để tôi không cần phải gọi "Authentication auth = SecurityContextHolder.getContext(). GetAuthentication();" ở đầu mỗi phương pháp?

Security-applicationContext.xml:

<global-method-security secured-annotations="enabled"></global-method-security>  
<http auto-config='true' access-denied-page="/access-denied.html"> 
    <!-- NO RESTRICTIONS -->   
    <intercept-url pattern="/login.html" access="IS_AUTHENTICATED_ANONYMOUSLY" /> 
    <intercept-url pattern="/*.html" access="IS_AUTHENTICATED_ANONYMOUSLY" /> 
    <!-- RESTRICTED PAGES --> 
    <intercept-url pattern="/admin/*.html" access="ROLE_ADMIN" /> 
    <intercept-url pattern="/member/*.html" access="ROLE_ADMIN, ROLE_STAFF" /> 

    <form-login login-page="/login.html" 
       login-processing-url="/loginProcess" 
       authentication-failure-url="/login.html?login_error=1" 
       default-target-url="/member/home.html" /> 
    <logout logout-success-url="/login.html"/> 
</http> 

<authentication-manager> 
    <authentication-provider> 
     <jdbc-user-service data-source-ref="dataSource" authorities-by-username-query="SELECT U.username, UR.authority, U.userid FROM users U, userroles UR WHERE U.username=? AND U.roleid=UR.roleid LIMIT 1" /> 
     <password-encoder hash="md5"/> 
    </authentication-provider> 
</authentication-manager> 

login.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %> 
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%> 

<tiles:insertDefinition name="header" /> 
<tiles:insertDefinition name="menu" /> 
<tiles:insertDefinition name="prebody" /> 

<h1>Login</h1> 

<c:if test="${not empty param.login_error}"> 
    <font color="red"><c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>.<br /><br /></font> 
</c:if> 
<form name="f" action="<c:url value='/loginProcess'/>" method="POST"> 
    <table> 
     <tr><td>User:</td><td><input type='text' name='j_username' value='<c:if test="${not empty param.login_error}"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>' /></td></tr> 
      <tr><td>Password:</td><td><input type='password' name='j_password' /></td></tr> 
      <tr><td>&nbsp;</td><td><input type="checkbox" name="_spring_security_remember_me" /> Remember Me</td></tr> 
      <tr><td>&nbsp;</td><td><input name="submit" type="submit" value="Login" /></td></tr> 
     </table> 
    </form> 

<tiles:insertDefinition name="postbody" /> 
<tiles:insertDefinition name="footer" /> 

Trả lời

19

Có rất nhiều điều xảy ra trong câu hỏi này. Tôi sẽ cố gắng giải quyết nó theo từng phần ...

Q # 1: Có một vài cách tiếp cận có thể có tại đây.

Phương pháp # 1: Nếu bạn có các thuộc tính khác mà bạn muốn thêm vào đối tượng UserDetails, thì bạn nên cung cấp triển khai thay thế giao diện UserDetails của riêng bạn bao gồm các thuộc tính đó cùng với các getters và setters tương ứng. Điều này sẽ yêu cầu bạn cũng cung cấp việc triển khai thay thế giao diện UserDetailsService của riêng bạn. Thành phần này sẽ phải hiểu cách duy trì các thuộc tính bổ sung này vào kho dữ liệu bên dưới, hoặc khi đọc từ kho dữ liệu đó, sẽ phải hiểu cách điền các thuộc tính bổ sung đó. Bạn muốn dây tất cả điều này lên như vậy:

<beans:bean id="userDetailsService" class="com.example.MyCustomeUserDetailsService"> 
<!-- ... --> 
</beans:bean> 

<authentication-manager alias="authenticationManager"> 
    <authentication-provider ref="authenticationProvider"/> 
</authentication-manager> 

<beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> 
    <beans:property name="userDetailsService" ref="userDetailsService"/> 
</beans:bean> 

Approache # 2: Giống như tôi, bạn có thể tìm thấy (đặc biệt là trong khoảng thời gian vài lần lặp) mà bạn đang phục vụ tốt hơn để giữ cho người sử dụng tên miền cụ thể/chi tiết tài khoản riêng biệt từ chi tiết tài khoản/người dùng cụ thể của Spring Security. Điều này có thể hoặc có thể không phải là trường hợp của bạn. Nhưng nếu bạn có thể tìm thấy bất kỳ sự khôn ngoan nào trong cách tiếp cận này, thì bạn nên gắn bó với thiết lập hiện tại và thêm một đối tượng miền User/Account bổ sung, kho lưu trữ tương ứng/DAO, v.v.Nếu bạn muốn lấy người dùng/tài khoản tên miền cụ thể, bạn có thể làm như vậy như sau:

User user = userDao.getByUsername(SecurityContextHolder.getContext().getAuthentication().getName()); 

Q # 2: Xuân An tự động lưu trữ các UserDetails trong phiên (trừ khi bạn đã thực hiện một cách rõ ràng các bước để ghi đè hành vi đó). Vì vậy, bạn không cần phải tự làm điều này trong mỗi phương pháp điều khiển của mình. Đối tượng SecurityContextHolder mà bạn đã xử lý thực sự được phổ biến (bằng SS) với SecurityContext bao gồm đối tượng Authentication, UserDetails, vv vào đầu mỗi yêu cầu. Ngữ cảnh này được xóa ở cuối mỗi yêu cầu, nhưng dữ liệu luôn nằm trong phiên. Tuy nhiên, cần lưu ý rằng nó không thực sự là một thực tế tuyệt vời để đối phó với các đối tượng HttpServletRequest, HttpSession, vv trong bộ điều khiển Spring MVC nếu bạn có thể tránh được nó. Mùa xuân hầu như luôn cung cấp các phương tiện rõ ràng hơn, thành ngữ hơn để đạt được mọi thứ mà không cần sự cần thiết phải làm như vậy. Ưu điểm của điều đó là chữ ký và logic của phương thức điều khiển sẽ không phụ thuộc vào những thứ khó khăn trong thử nghiệm đơn vị (ví dụ: HttpSession) và thay vì phụ thuộc vào các đối tượng miền của riêng bạn (hoặc sơ đồ/mocks của các đối tượng miền đó)). Điều này làm tăng đáng kể khả năng kiểm tra của bộ điều khiển của bạn ... và do đó làm tăng khả năng mà bạn thực sự S test kiểm tra bộ điều khiển của bạn. :)

Hy vọng điều này sẽ giúp một số.

+0

Cảm ơn Kent, điều đó khá hữu ích! Xin lỗi vì sự lộn xộn, lần đầu tiên mã hóa Spring. Tôi thích cách tiếp cận # 2, tôi sẽ đặt dòng mã đó ở đâu ở đầu mỗi phương pháp? Và tôi đoán dựa trên câu hỏi đó, nếu tôi đi với phương pháp thứ 2 đó là có thể lưu trữ đối tượng người dùng để tôi không ' t cần phải tạo một đối tượng người dùng mới và đi đến db cho cùng một dữ liệu mỗi lần? – Felix

+1

Câu trả lời là, "nó phụ thuộc." Tôi thấy rằng với cách tiếp cận # 2, tôi rất hiếm khi cần truy cập vào tài khoản của người dùng (từ phối cảnh miền). Có lẽ tôi chỉ truy cập vào mục đích hiển thị hoặc chỉnh sửa "hồ sơ" của họ. Nếu đúng như vậy, tôi sẽ đọc qua kho dữ liệu cho thông tin này khi cần. Nếu bạn cần truy cập thường xuyên các thuộc tính của người dùng (từ phối cảnh tên miền; có lẽ một số thuộc tính của đối tượng người dùng cần xuất hiện trong tiêu đề và do đó cần thiết trong mọi yêu cầu), thì có thể bạn nên xem xét lại phương pháp # 1. –

+1

Cảm ơn Kent. Tôi đã làm một giải pháp kiểu lai. Khi tôi cần người dùng đã đăng nhập, tôi chạy một phương thức trong trình điều khiển trình bao bọc để lấy dữ liệu. Phương thức này kiểm tra xem phiên có chứa một đối tượng người dùng hay không, và nếu nó kiểm tra nếu tên người dùng của đối tượng bằng với tên người dùng securityContext. Nếu không (hoặc nếu đối tượng người dùng phiên là null) nó sẽ lấy dữ liệu người dùng từ cơ sở dữ liệu và lưu trữ đối tượng trong phiên. Hai thêm nếu tuyên bố, nhưng 1 cuộc gọi cơ sở dữ liệu ít hơn. – Felix

1

Truy cập vào phiên giao dịch trực tiếp là một chút lộn xộn, và có thể dễ bị lỗi. Ví dụ: nếu người dùng được xác thực bằng cách sử dụng ghi nhớ tôi hoặc một số cơ chế khác không liên quan đến chuyển hướng, phiên sẽ không được điền cho đến khi yêu cầu đó hoàn tất.

Tôi sẽ sử dụng giao diện người dùng tùy chỉnh để bao bọc các cuộc gọi đến SecurityContextHolder. Xem my answer to this related question.

+0

Cuộc gọi tốt, đã lâu rồi kể từ khi tôi sử dụng phiên. – Felix

2

Theo ý kiến ​​của tôi, triển khai Custom UserDetails thật tuyệt vời nhưng chỉ nên được sử dụng cho các đặc điểm bất biến của người dùng của bạn.

Khi đối tượng người dùng tùy chỉnh của bạn ghi đè UserDetails, nó không dễ dàng thay đổi. Bạn phải tạo một đối tượng xác thực hoàn toàn mới với các chi tiết được sửa đổi và không thể chỉ gắn đối tượng UserDetails đã sửa đổi trở lại ngữ cảnh bảo mật. Trong ứng dụng mà tôi đang xây dựng, tôi đã nhận ra điều này và phải nghiên cứu lại nó để sau khi các chi tiết xác thực thành công về người dùng đang thay đổi theo mọi yêu cầu (nhưng tôi không muốn tải lại từ db vào ngày tháng năm 2006). mỗi lần tải trang) sẽ cần được giữ riêng trong phiên, nhưng vẫn chỉ có thể truy cập/có thể thay đổi sau khi kiểm tra xác thực.

Cố gắng tìm hiểu xem WebArgumentResolver được đề cập trong https://stackoverflow.com/a/8769670/1411545 có phải là giải pháp tốt hơn cho tình huống của tôi hay không.

Các vấn đề liên quan