2011-12-27 35 views
32

Tôi đang sử dụng Spring Security để bảo mật ứng dụng web Struts2. Do các ràng buộc của dự án, tôi đang sử dụng Spring Security 2.06.Triển khai AuthenticationProvider tùy chỉnh trong Spring Security 2.06

đội Mỹ xây dựng một API quản lý tùy chỉnh người dùng mà xác thực người dùng sau khi uống vào tên người dùng và mật khẩu thông số, và trả về một đối tượng người dùng tùy chỉnh chứa danh sách các vai trò và các thuộc tính khác như email, tên, vv

Từ sự hiểu biết của tôi, trường hợp sử dụng bảo mật Spring điển hình sử dụng một UserDetailsService mặc định để lấy ra một đối tượng UserDetails; đối tượng này sẽ chứa (trong số những thứ khác) một trường mật khẩu sẽ được khung công tác sử dụng để xác thực người dùng.

Trong trường hợp của mình, tôi muốn cho API tùy chỉnh của chúng tôi thực hiện xác thực, sau đó trả về đối tượng UserDetails tùy chỉnh chứa các vai trò và các thuộc tính khác (email, v.v ...).

Sau một số nghiên cứu, tôi đã tìm ra rằng tôi có thể thực hiện việc này thông qua triển khai tùy chỉnh AuthenticationProvider. Tôi cũng có triển khai tùy chỉnh UserDetailsService và UserDetails.

Vấn đề của tôi là tôi không thực sự hiểu những gì tôi phải quay trở lại trong CustomAuthenticationProvider. Tôi có sử dụng đối tượng UserDetailsService tùy chỉnh của mình ở đây không? Đó có cần thiết không? Xin lỗi, tôi thực sự bối rối.

CustomAuthenticationProvider:

public class CustomAuthenticationProvider implements AuthenticationProvider { 

private Logger logger = Logger.getLogger(CustomAuthenticationProvider.class); 

private UserDetailsService userDetailsService; //what am i supposed to do with this? 

@Override 
public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
    UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication; 
    String username = String.valueOf(auth.getPrincipal()); 
    String password = String.valueOf(auth.getCredentials()); 

    logger.info("username:" + username); 
    logger.info("password:" + password); 
    /* what should happen here? */ 

    return null; //what do i return? 
} 

@Override 
public boolean supports(Class aClass) { 
    return true; //To indicate that this authenticationprovider can handle the auth request. since there's currently only one way of logging in, always return true 
} 

public UserDetailsService getUserDetailsService() { 
    return userDetailsService; 
} 

public void setUserDetailsService(UserDetailsService userDetailsService) { 
    this.userDetailsService = userDetailsService; 
} 

}

applicationContext-security.xml:

<beans:bean id="customUserDetailsService" scope="prototype" class="com.test.testconsole.security.CustomUserDetailsService"/> 

<beans:bean id="customAuthenticationProvider" class="com.test.testconsole.security.CustomAuthenticationProvider"> 
    <custom-authentication-provider /> 
    <beans:property name="userDetailsService" ref="customUserDetailsService" /> 
</beans:bean> 

Nói tóm lại, đây là những gì tôi cần:

  1. người dùng đăng nhập thông qua biểu mẫu web
  2. Authenticate người dùng sử dụng trong nhà quản lý người dùng API
  3. Đối với người dùng xác thực thành công, cư GrantedAuthories vv
  4. Return một thực thể người dùng chứa vai trò/cơ quan chức năng và các thuộc tính khác như email, tên, vv Tôi nên sau đó có thể truy cập đối tượng này như vậy ..

    //spring security get user name 
    Authentication auth = SecurityContextHolder.getContext().getAuthentication(); 
    userName = auth.getName(); //get logged in username 
    logger.info("username: " + userName); 
    
    //spring security get user role 
    GrantedAuthority[] authorities = auth.getAuthorities(); 
    userRole = authorities[0].getAuthority(); 
    logger.info("user role: " + userRole); 
    

tôi hy vọng điều này có ý nghĩa. Bất kỳ trợ giúp hoặc con trỏ sẽ được đánh giá cao!

Cảm ơn!

Cập nhật:

Tôi đã thực hiện một số tiến bộ, tôi nghĩ vậy.

Tôi có một đối tượng tùy chỉnh Xác thực thi giao diện Xác thực:

public class CustomAuthentication implements Authentication { 

    String name; 
    GrantedAuthority[] authorities; 
    Object credentials; 
    Object details; 
    Object principal; 
    boolean authenticated; 

    public CustomAuthentication(String name, GrantedAuthority[] authorities, Object credentials, Object details, Object principal, boolean 
           authenticated){ 
     this.name=name; 
     this.authorities=authorities; 
     this.details=details; 
     this.principal=principal; 
     this.authenticated=authenticated; 

    } 
    @Override 
    public GrantedAuthority[] getAuthorities() { 
     return new GrantedAuthority[0]; //To change body of implemented methods use File | Settings | File Templates. 
    } 

    @Override 
    public Object getCredentials() { 
     return null; //To change body of implemented methods use File | Settings | File Templates. 
    } 

    @Override 
    public Object getDetails() { 
     return null; //To change body of implemented methods use File | Settings | File Templates. 
    } 

    @Override 
    public Object getPrincipal() { 
     return null; //To change body of implemented methods use File | Settings | File Templates. 
    } 

    @Override 
    public boolean isAuthenticated() { 
     return false; //To change body of implemented methods use File | Settings | File Templates. 
    } 

    @Override 
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { 
     //To change body of implemented methods use File | Settings | File Templates. 
    } 

    @Override 
    public String getName() { 
     return null; 
    } 
} 

và cập nhật lớp CustomerAuthenticationProvider tôi:

@Override 
    public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
     UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication; 
     String username = String.valueOf(auth.getPrincipal()); 
     String password = String.valueOf(auth.getCredentials()); 

     logger.info("username:" + username); 
     logger.info("password:" + password); 

     //no actual validation done at this time 

     GrantedAuthority[] authorities = new GrantedAuthorityImpl[1]; 
     authorities[0] = new GrantedAuthorityImpl("ROLE_USER"); 

     CustomAuthentication customAuthentication = new CustomAuthentication("TestMerchant",authorities,"details",username,password,true); 

    return customAuthentication; 

    //return new UsernamePasswordAuthenticationToken(username,password,authorities); 
} 

Nó hoạt động nếu tôi trả về một đối tượng UsernamePasswordAuthenticationToken, nhưng nếu tôi cố gắng quay trở lại CustomAuthentication, tôi nhận được lỗi sau:

java.lang.ClassCastException: com.test.testconsole.security.CustomAuthentication cannot be cast to org.springframework.security.providers.UsernamePasswordAuthenticationToken 
    at com.test.testconsole.security.CustomAuthenticationProvider.authenticate(CustomAuthenticationProvider.java:27) 
    at org.springframework.security.providers.ProviderManager.doAuthentication(ProviderManager.java:188) 
    at org.springframework.security.AbstractAuthenticationManager.authenticate(AbstractAuthenticationManager.java:46) 
    at org.springframework.security.intercept.AbstractSecurityInterceptor.authenticateIfRequired(AbstractSecurityInterceptor.java:319) 
    at org.springframework.security.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:258) 
    at org.springframework.security.intercept.web.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:106) 
    at org.springframework.security.intercept.web.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:83) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.ui.SessionFixationProtectionFilter.doFilterHttp(SessionFixationProtectionFilter.java:67) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.ui.ExceptionTranslationFilter.doFilterHttp(ExceptionTranslationFilter.java:101) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.providers.anonymous.AnonymousProcessingFilter.doFilterHttp(AnonymousProcessingFilter.java:105) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.ui.rememberme.RememberMeProcessingFilter.doFilterHttp(RememberMeProcessingFilter.java:116) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter.doFilterHttp(SecurityContextHolderAwareRequestFilter.java:91) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.ui.basicauth.BasicProcessingFilter.doFilterHttp(BasicProcessingFilter.java:174) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.ui.AbstractProcessingFilter.doFilterHttp(AbstractProcessingFilter.java:278) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.ui.logout.LogoutFilter.doFilterHttp(LogoutFilter.java:89) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.context.HttpSessionContextIntegrationFilter.doFilterHttp(HttpSessionContextIntegrationFilter.java:235) 
    at org.springframework.security.ui.SpringSecurityFilter.doFilter(SpringSecurityFilter.java:53) 
    at org.springframework.security.util.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:390) 
    at org.springframework.security.util.FilterChainProxy.doFilter(FilterChainProxy.java:175) 
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:236) 
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167) 
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157) 
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388) 
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216) 
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182) 
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765) 
    at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418) 
    at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) 
    at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114) 
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152) 
    at org.mortbay.jetty.Server.handle(Server.java:326) 
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:536) 
    at org.mortbay.jetty.HttpConnection$RequestHandler.headerComplete(HttpConnection.java:915) 
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:539) 
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:212) 
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:405) 
    at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:409) 
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582) 

Nó giống như một cái gì đó được mong đợi không chỉ bất kỳ đối tượng xác thực, nhưng thực hiện cụ thể của nó - UsernamePasswordAuthenticationToken. Điều này làm cho tôi nghĩ rằng tôi có thể thiếu một thành phần tùy chỉnh khác .. có thể là một bộ lọc?

Trả lời

44

Nếu bạn đang triển khai AuthenticationProvider của riêng mình, bạn không phải triển khai UserDetailsService nếu bạn không muốn. UserDetailsService chỉ cung cấp DAO tiêu chuẩn để tải thông tin người dùng và một số lớp khác trong khuôn khổ được triển khai để sử dụng nó.

Thông thường, để xác thực bằng cách sử dụng tên người dùng và mật khẩu, bạn sẽ khởi tạo DaoAuthenticationProvider và chèn mã đó với số UserDetailsService. Đó có thể vẫn là cách tiếp cận tốt nhất của bạn. Nếu bạn triển khai nhà cung cấp của riêng mình, bạn chịu trách nhiệm đảm bảo rằng người dùng đã cung cấp đúng mật khẩu và vân vân. Tuy nhiên, trong một số trường hợp, đây là một cách tiếp cận đơn giản hơn.

Để trả lời "điều gì sẽ xảy ra ở đây?" bình luận trong mã của bạn, nó sẽ là một cái gì đó giống như

@Override 
public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
    UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication; 
    String username = String.valueOf(auth.getPrincipal()); 
    String password = String.valueOf(auth.getCredentials()); 

    logger.info("username:" + username); 
    logger.info("password:" + password); // Don't log passwords in real app 

    // 1. Use the username to load the data for the user, including authorities and password. 
    YourUser user = .... 

    // 2. Check the passwords match (should use a hashed password here). 
    if (!user.getPassword().equals(password)) { 
    throw new BadCredentialsException("Bad Credentials"); 
    } 

    // 3. Preferably clear the password in the user object before storing in authentication object 
    user.clearPassword(); 

    // 4. Return an authenticated token, containing user data and authorities 

    return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()) ; 
} 

Đối tượng người dùng sau đó sẽ có thể truy cập bằng cách sử dụng phương pháp

Authentication.getPrincipal() 

, và bạn có thể truy cập vào các tính chất bổ sung (email vv) bằng cách đúc nó để triển khai người dùng tùy chỉnh của bạn.

Cách bạn tải dữ liệu người dùng tùy thuộc vào bạn. Tất cả Spring Security đều quan tâm đến đây là giao diện AuthenticationProvider.

Bạn cũng nên lưu trữ mật khẩu băm và xác thực mật khẩu được cung cấp bằng cách sử dụng cùng một thuật toán, thay vì kiểm tra bình đẳng đơn giản.

+0

Hi Luke, nhờ trả lời của bạn! Tôi thực sự đã thực hiện một chút tiến bộ sau khi đăng câu hỏi và những gì tôi đã làm trông giống như những gì bạn đề nghị. Tôi vẫn có vấn đề không thể thêm các trường bổ sung như (email, v.v.) vào đối tượng Authentication. Tôi đã triển khai lớp xác thực tùy chỉnh nhưng gặp lỗi. Vui lòng xem nội dung cập nhật của tôi .. – shaunlim

+0

Có vẻ như bạn đang thực hiện một dàn diễn viên không hợp lệ trong CustomAuthenticationProvider, dòng 27 của bạn vào UsernamePasswordAuthenticationToken. Thường không cần phải tạo một đối tượng xác thực tùy chỉnh. –

+0

oh .. tôi hiểu rồi. Bạn có nghĩa là thay vì đi qua trong tên người dùng như là chính cho constructor UsernamePasswordAuthenticationToken, tôi nên vượt qua trong đối tượng người dùng tùy chỉnh của tôi có chứa tất cả các trường thêm tôi cần? sau đó khi tôi cần dữ liệu, tôi sẽ chỉ gọi getPrincipal sau khi truy xuất đối tượng Authentication? – shaunlim

3

cảm ơn vì đã đăng bài Lu-ca này!

Đã lưu tôi từ nhiều tổn thất về não khác.

điều Chỉ chú ý tôi chạy vào, cho những ai quan tâm:

thiết lập của tôi:

  • Grails 2.0.4
  • Groovy 1,8
  • mùa xuân-an ninh-core 1.2.7.3
  • spring-security-ui 0.2
  • hibernate 2.0.4

Khi sử dụng các đánh giá rất đơn giản/thanh lịch cách tiếp cận Luke cho thấy, KHÔNG thực hiện một UserDetails tùy chỉnh (hoặc UserDetailsService) đối tượng -and- sử dụng tài khoản riêng bạn miền đối tượng mà không mở rộng bất cứ điều gì đặc biệt, bạn phải thực hiện thêm một bước nếu bạn đang sử dụng thẻ tùy chỉnh "giây" từ bảo mật mùa xuân (trong các trang của bạn):

Khi bạn khởi tạo Tên người dùng cơ bản, không tùy chỉnhPasswordAuthenticationToken, bạn PHẢI chuyển nó một cái gì đó mở rộng hiệu trưởng, một lần nữa, nếu bạn muốn mùa xuân của bạn thẻ khoảng cách tùy chỉnh bảo mật để hoạt động. Tôi đã làm một cái gì đó như thế này, để giữ cho nó càng đơn giản càng tốt (tham khảo giá trị đối tượng sử dụng tên miền của tôi nơi hữu ích/thích hợp):

def principalUser = new org.springframework.security.core.userdetails.User(user.username, user.password, user.enabled, !user.accountExpired, !user.passwordExpired,!user.accountLocked, authorities) 
def token = new UsernamePasswordAuthenticationToken(principalUser, presentedPassword, authorities) 

này phải thoả mãn các điều kiện thử nghiệm tại grails.plugins.springsecurity.SecurityTagLib.determineSource() như vậy, bạn đã biết, các trang của bạn có sử dụng <sec:loggedInUserInfo> sẽ thực sự render:

if (principal.metaClass.respondsTo(principal, 'getDomainClass')) { 
      return principal.domainClass 
} 

Ngược lại, nếu bạn nhanh chóng UsernamePasswordAuthenticationToken với đối tượng miền tài khoản của bạn (như Luke chương trình trong ví dụ của mình), mà thẻ an ninh phương pháp lib (defineSource()) sẽ chỉ làm mức tốt nhất và trả về giá trị (meta) của org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass và bạn sẽ nhận được một lỗi khi thẻ đi tìm kiếm các biến thành viên tên nêu:

Error executing tag <sec:ifLoggedIn>: Error executing tag <sec:loggedInUserInfo>: No such property: username for class: org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass 

ngắn tái thực hiện/subclassing mùa xuân-an ninh Taglibs -core plugin trong dự án grails của tôi, không có cách nào để cả hai sử dụng taglibs AND sử dụng miền tùy chỉnh của bạn User class để khởi tạo token được truyền từ bộ lọc của bạn tới nhà cung cấp của bạn.

Sau đó, một lần nữa, một dòng thêm mã là rất giá nhỏ trả :)

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