繁体   English   中英

在Spring Security 2.06中实现自定义AuthenticationProvider

[英]Implement custom AuthenticationProvider in Spring Security 2.06

我正在使用Spring Security来保护Struts2 Web应用程序。 由于项目限制,我使用的是Spring Security 2.06。

我的团队构建了一个自定义用户管理API,用于在接收用户名和密码参数后对用户进行身份验证,并返回包含角色列表和其他属性(如电子邮件,名称等)的自定义用户对象。

根据我的理解,典型的Spring Security用例使用默认的UserDetailsS​​ervice来检索UserDetails对象; 此对象将包含(以及其他内容)框架将用于对用户进行身份验证的密码字段。

在我的情况下,我想让我们的自定义API执行身份验证,然后返回包含角色和其他属性(电子邮件等)的自定义UserDetails对象。

经过一些研究,我发现我可以通过AuthenticationProvider的自定义实现来实现这一点。 我也有UserDetailsS​​ervice和UserDetails的自定义实现。

我的问题是我真的不明白我应该在CustomAuthenticationProvider中返回什么。 我在这里使用自定义UserDetailsS​​ervice对象吗? 甚至需要吗? 对不起,我真的很困惑。

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>

总而言之,这就是我需要的:

  1. 用户通过Web表单登录
  2. 使用内部用户管理API对用户进行身份验证
  3. 对于成功通过身份验证的用户,请填写GrantedAuthories等。
  4. 返回包含角色/权限的用户实体,以及其他属性,如电子邮件,名称等。然后,我应该能够像这样访问此对象。

     //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); 

我希望这是有道理的。 任何帮助或指针将不胜感激!

谢谢!

更新:

我想,我已经取得了一些进展。

我有一个实现Authentication接口的自定义Authentication对象:

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

并更新了我的CustomerAuthenticationProvider类:

    @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); 
}

如果我返回UsernamePasswordAuthenticationToken对象,它可以工作,但如果我尝试返回CustomAuthentication,我会收到以下错误:

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)

这就像是期望不仅仅是任何Authentication对象,而是它的特定实现 - UsernamePasswordAuthenticationToken。 这让我觉得我可能会错过另一个自定义组件..也许是一个过滤器?

如果您要实现自己的AuthenticationProvider ,则不必实现UserDetailsService如果您不想这样做)。 UserDetailsService仅提供用于加载用户信息的标准DAO,并且实现框架内的一些其他类以使用它。

通常,要使用用户名和密码进行身份验证,您可以实例化DaoAuthenticationProvider并将其注入UserDetailsService 这可能仍然是你最好的方法。 如果您实现自己的提供程序,则您负责确保用户提供了正确的密码等。 但是,在某些情况下,这是一种更简单的方法。

回答你的“这里应该发生什么?” 在你的代码中评论,它会是这样的

@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()) ;
}

然后可以使用。访问用户对象

Authentication.getPrincipal()

方法,您可以通过将其转换为自定义用户实现来访问其他属性(电子邮件等)。

如何加载用户数据取决于您。 所有Spring Security都在关注AuthenticationProvider接口。

您还应该使用相同的算法存储散列密码并验证提供的密码,而不是简单的相等检查。

谢谢你发布这个卢克!

救了我更多的脑损伤。

对于任何关心的人来说,我唯一需要注意的是:

我的设置:

  • Grails 2.0.4
  • Groovy 1.8
  • spring-security-core 1.2.7.3
  • spring-security-ui 0.2
  • hibernate 2.0.4

当使用非常赞赏的简化/优雅方法时,Luke建议,不要实现自定义的UserDetails(或UserDetailsS​​ervice)对象 - 并且 - 使用您自己的用户对象,不会扩展任何特殊内容,如果您使用的话, 必须采取额外步骤秒“弹簧安全的自定义标签(当然在您的页面中):

当您实例化一个基本的,非自定义的UsernamePasswordAuthenticationToken时,如果您希望Spring安全自定义间隙标记起作用,则必须再次传递一个扩展Principal的实例。 我做了类似的事情,以尽可能简单(在有用/适当的地方引用我的用户域对象值):

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)

这应该满足grails.plugins.springsecurity.SecurityTagLib.determineSource()中测试的条件,因此,您知道,使用<sec:loggedInUserInfo>将实际渲染:

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

否则,如果您使用您的用户域对象实例化UsernamePasswordAuthenticationToken(在他的示例中为Luke show),那么安全标记lib方法(determineSource())将最好地执行它的级别并返回org.codehaus.groovy的(meta)值.grails.commons.DefaultGrailsDomainClass ,当标记寻找用户名成员变量时,你会收到一个错误:

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

如果没有在我的grails项目中重新实现/子类化spring-security-core插件taglibs,那么就没有办法同时使用taglib并使用你的自定义域User类来实例化从你的过滤器传递给你的提供者的令牌。

再说一次,额外的一行代码是一个非常小的代价:)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM