当前位置: 首页 > 面试题库 >

在Spring Security 2.06中实现自定义AuthenticationProvider

靳茂
2023-03-14
问题内容

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

我的团队构建了一个自定义用户管理API,该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,则不需要实现UserDetailsServiceUserDetailsService只是提供了用于加载用户信息的标准DAO,并且在框架中实现了一些其他类来使用它。

通常,要使用用户名和密码进行身份验证,您可以实例化a
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接口。

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



 类似资料:
  • 在开发过程中,常常需要为一些repository方法添加自定义的实现。Spring Data repository允许开发者自定义repository方法。

  • 看起来Spring总是使用< code > inmemorrelyingpartyregistrationrepository 来返回一个< code > RelyingPartyRegistrationRepository 类型的bean,请参考https://github . com/Spring-projects/Spring-boot/blob/master/Spring-boot-pro

  • 问题内容: 我通过了几个链接,以找到在ios应用程序中实现google customsearchapi的正确步骤,并在此过程中花费了大约6到7个小时。 链接: https://developers.google.com/custom-search/json-api/v1/简介 http://developers.google.com/apis-explorer/#p/customsearch/v1

  • 本文向大家介绍如何在jQuery中实现自定义事件?,包括了如何在jQuery中实现自定义事件?的使用技巧和注意事项,需要的朋友参考一下 自定义事件意味着您可以在jQuery中创建自己的事件。例如,创建一个自定义事件以在按下键盘上的任意键时触发警报框。 示例 您可以尝试运行以下代码以了解如何创建自定义事件,

  • 问题内容: 我收到此错误: sum()得到了意外的关键字参数’out’ 当我运行此代码时: 除了对批处理进行循环并编辑源代码之外,是否可以实现自定义指标? 问题答案: 问题是,并没有与NumPy阵列但无论哪种Theano或TensorFlow张量。这就是为什么您会收到此错误。 您可以定义自定义指标,但必须记住,其参数是那些张量,而不是NumPy数组。

  • 我有一个名为 Expect 的类,在你实例化它之后,你可以构建一个数据结构(为了简单起见,假设它是一棵树)。然后调用 run 方法,该方法遍历树,在每个节点上执行一些操作。这些操作需要一些时间才能完成,以便将来返回最终结果。在伪代码中,它类似于: 我想用它们通常的签名实现map和flatmap,但是它们作为参数接收的函数必须对将来返回的值进行操作。我看不出有任何方法可以实现这一点。