具有 Spring Security 和 Java 配置的自定义身份验证管理器

Posted

技术标签:

【中文标题】具有 Spring Security 和 Java 配置的自定义身份验证管理器【英文标题】:Custom Authentication Manager with Spring Security and Java Configuration 【发布时间】:2015-10-27 20:36:36 【问题描述】:

我正在将 Spring Security 与 SpringMVC 一起使用来创建一个与现有应用程序(我将其称为 BackendApp)对话的 Web 应用程序(为了清楚起见,我将其称为 WebApp)。

我想将身份验证职责委托给 BackendApp(这样我就不需要同步两个应用程序)。

为了实现这一点,我希望 WebApp(运行 Spring Security)使用用户在表单中提供的用户名和密码通过 REST 与 BackendApp 通信,并根据 BackendApp 的响应是 200 OK 还是 401 Unauthorized 进行身份验证.

我知道我需要编写一个自定义身份验证管理器来执行此操作,但是我对 spring 很陌生,找不到有关如何实现它的任何信息。

我相信我需要做这样的事情:

public class CustomAuthenticationManager implements AuthenticationManager

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException 

        String username = authentication.getName();
        String pw       = authentication.getCredentials().toString();

        // Code to make rest call here and check for OK or Unauthorised.
        // What do I return?

    


如果成功则设置 authentication.setAuthenticated(true),否则设置 false,仅此而已?

写完后,如何配置 spring security 以使用 java 配置文件使用此身份验证管理器?

提前感谢您的帮助。

【问题讨论】:

那么,您基本上是想通过 REST 进行身份验证,对吗?此外,您不仅会获得 200 或 401 的响应类型,您还将获得来自 Spring-Security 的 SessionID,您可以通过 REST 直接使用它来访问受保护的资源。 另外,请用一些相关名称提及您的应用程序,这个和那个应用程序令人困惑。 您应该深入了解 Spring Sec。参考,您需要设置一个 REST 客户端,保护服务,建立安全连接,不要将密码存储在字符串中,请记住,如果其他应用程序关闭,这也会关闭等等......最好设置一个LDAP 服务器或仅与其他应用程序共享数据库(只读)。您应该咨询顾问。 @WeareBorg 好点,我编辑了这个问题,希望让它更清楚。我不明白你的第一条评论。 Stefan 感谢您的建议。 对,就是我说的,你的webapp不需要写任何认证码。如果您在其中一个 web 应用程序中使用 Spring-Security,您可以随时调用 j_spring_security_check 并获取响应。根据响应,您可以允许或不允许访问资源。没有委托,什么都不需要。 【参考方案1】:

看看我下面的示例。您必须返回一个 UsernamePasswordAuthenticationToken。它包含主体和 GrantedAuthorities。希望我能帮上忙:)

public Authentication authenticate(Authentication authentication) throws AuthenticationException 
    String username = authentication.getPrincipal() + "";
    String password = authentication.getCredentials() + "";

    User user = userRepo.findOne(username);
    if (user == null) 
        throw new BadCredentialsException("1000");
    
    if (!encoder.matches(password, user.getPassword())) 
        throw new BadCredentialsException("1000");
    
    if (user.isDisabled()) 
        throw new DisabledException("1001");
    
    List<Right> userRights = rightRepo.getUserRights(username);
    return new UsernamePasswordAuthenticationToken(username, null, userRights.stream().map(x -> new SimpleGrantedAuthority(x.getName())).collect(Collectors.toList()));

PS:userRepo 和 rightRepo 是 Spring-Data-JPA 存储库,可以访问我的自定义 User-DB

SpringSecurity JavaConfig:

@Configuration
@EnableWebMvcSecurity
public class MySecurityConfiguration extends WebSecurityConfigurerAdapter 

public MySecurityConfiguration() 
    super(false);


@Override
protected AuthenticationManager authenticationManager() throws Exception 
    return new ProviderManager(Arrays.asList((AuthenticationProvider) new AuthProvider()));



【讨论】:

谢谢,非常有帮助。那么仅在成功的情况下,authenticate 方法才返回此 UsernamePasswordAuthenticationToken 吗?另外,您是如何配置 Spring Security 以使用您的自定义身份验证管理器的? 我已经编辑了我的答案以匹配您的评论(请参阅 SpringSecurity JavaConfig) 感谢您的帮助,但我认为这不是我想要的。据我了解,AuthenticationProvider(即您所展示的)将该令牌传递给 AuthenticationManager,然后它将主体和凭据与用户提供的内容进行比较。我想实现自己的 AuthenticationManager 并告诉它不要将用户名和密码与 AuthenticationProvider 进行比较,而是通过 REST 与另一台服务器对话以确定信息是否正确。很抱歉造成混乱......我的问题可能不清楚:S 我已经更新了我的答案。 JavaConfig 现在的工作方式对您来说应该没问题。 ProviderManager 尝试使用给定的AuthenticationProviders 列表进行身份验证。 -> 您可以在authenticate 方法中使用REST 检查用户名/密码。 出于安全原因,我建议将密码设置为 null,否则它将被保存并在您的身份验证主体中可见。此外,用户启用/禁用检查应该在密码检查之后,以免在不知道密码的情况下暴露禁用用户。【参考方案2】:

我的解决方法和第一个答案差不多:

1) 你需要一个实现 Authentication Provider 的类

@Service
@Configurable
public class CustomAuthenticationProvider implements AuthenticationProvider    
      @Override
      public Authentication authenticate(Authentication authentication) throws AuthenticationException 
    // Your code of custom Authentication


2) 与第一个答案相反,如果您只有此自定义提供程序,您不需要在您的 WebSecurityConfiguration 中包含以下代码。

@Override
protected AuthenticationManager authenticationManager() throws Exception 
     return new ProviderManager(Arrays.asList((AuthenticationProvider) new  AuthProvider()));

问题是 Spring 会寻找可用的提供者,如果没有找到其他提供者,则使用默认值。但是由于您有 AuthenticationProvider 的实现 - 您的实现将被使用。

【讨论】:

【参考方案3】:

最简单的:

@Override
    public Authentication authenticate(Authentication auth) throws AuthenticationException 
        String username = auth.getName();
        String password = auth.getCredentials().toString();
        // to add more logic
        List<GrantedAuthority> grantedAuths = new ArrayList<>();
        grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
        return new UsernamePasswordAuthenticationToken(username, password, grantedAuths);
    

【讨论】:

【参考方案4】:

首先,您必须配置 Spring 安全性以使用您的自定义 AuthenticationProvider。 因此,在您的 spring-security.xml (或等效的配置文件)中,您必须定义实现此功能的 wich 类。例如:

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

<!-- Bean implementing AuthenticationProvider of Spring Security -->
<beans:bean id="myAuthenticationProvider" class="com.teimas.MyAutenticationProvider">
</beans:bean>

其次,您必须像示例中那样实现 AuthenticationProvider。特别是您的休息调用必须使用的方法 authenticate(Authentication authentication)。例如:

public Authentication authenticate(Authentication authentication) throws AuthenticationException 
    User user = null;
    try 
        //use a rest service to find the user. 
        //Spring security provides user login name in authentication.getPrincipal()
            user = userRestService.loadUserByUsername(authentication.getPrincipal().toString());
     catch (Exception e) 
        log.error("Error loading user, not found: " + e.getMessage(), e);
    

    if (user == null) 
        throw new UsernameNotFoundException(String.format("Invalid credentials", authentication.getPrincipal()));
     else if (!user.isEnabled()) 
        throw new UsernameNotFoundException(String.format("Not found enabled user for username ", user.getUsername()));
    
    //check user password stored in authentication.getCredentials() against stored password hash
    if (StringUtils.isBlank(authentication.getCredentials().toString())
        || !passwordEncoder.isPasswordValid(user.getPasswordHash(), authentication.getCredentials().toString()) 
        throw new BadCredentialsException("Invalid credentials");
    

    //doLogin makes whatever is necesary when login is made (put info in session, load other data etc..)
    return doLogin(user);
 

【讨论】:

感谢您的建议,我假设您的意思是 AuthenticationManager 而不是 AuthenticationProvider?我认为我不需要 AuthenticationProvider,因为我不想比较任何凭据...您碰巧知道身份验证管理器的配置的 java 版本吗? 我说的是 AuthenticationProvider,它是另一个与 authenticationManager 几乎相同的接口。如果您愿意,可以使用实施 AuthenticationManager。 只是为了澄清......我最初认为身份验证管理器使用身份验证提供程序来检索用户详细信息......似乎我误解了弹簧安全性的工作原理。据我现在了解,身份验证提供者可以单独负责身份验证和设置安全上下文。如果我错了,请纠正我:S

以上是关于具有 Spring Security 和 Java 配置的自定义身份验证管理器的主要内容,如果未能解决你的问题,请参考以下文章

具有角色的经过身份验证的用户的 Spring Security Java 配置

具有 CAS 身份验证和自定义授权的 Spring Security

具有 Spring Security 的公共和私有 REST API

具有数据库角色的 Spring Security SAML

具有 Active Directory 和数据库角色的 Spring Security

具有Spring Security的多个用户