【问题标题】:Adding additional details to principal object stored in spring security context向存储在 spring 安全上下文中的主体对象添加其他详细信息
【发布时间】:2013-12-19 10:02:17
【问题描述】:

我正在使用 Spring 3.0 和 Spring Security 3。我能够使用 Spring Security 对数据库进行用户身份验证。使用:

SecurityContextHolder.getContext().getAuthentication().getPrincipal()

我能够检索当前登录用户的用户名。我希望添加其他详细信息,例如用户 ID 和模块访问存储在 Spring Security 上下文中的主体对象,以便我以后可以检索它。如何向主体对象添加其他详细信息,然后如何在 jsp 或 java 类中检索它。如果可能,请提供适当的代码 sn-p。

编辑:我正在使用 JDBC 访问我的数据库。

提前致谢。

【问题讨论】:

  • 创建您自己的UserDetails 实现和您自己的UserDetailsService 实现。有了它,你可以做任何你想做的事情。添加您想要的属性等。
  • 谢谢。我一直在尝试做同样的事情,但我认为我做错了什么。 @M.Deinum 你能不能给我一个代码 sn-p 已经成功实现了

标签: java spring spring-security


【解决方案1】:

为了向经过身份验证的用户添加更多详细信息。您需要首先创建自己的 User 对象实现,该对象应该扩展 spring security User 对象。之后,您可以将要添加的属性添加到经过身份验证的用户。完成此操作后,您需要在 UserDetailService 中返回用户对象的实现(如果您不使用 LDAP 进行身份验证)。此链接提供了向经过身份验证的用户添加更多详细信息的详细信息--

http://javahotpot.blogspot.com/2013/12/spring-security-adding-more-information.html

【讨论】:

  • 您能否在示例链接的 LoginService 中发布 loadUserDetails(username) 的实现。我想知道如何处理失败的身份验证请求。提前致谢
  • 只在提到的链接上回答了..javahotpot.blogspot.in/2013/12/…
  • 您的博客链接并没有完全回答问题,正如要求植入 LoginService。通常,您应该在本网站上回答问题,而不是参考您博客上的帖子。
  • 你为什么说如果你不使用LDAP进行身份验证?此解决方案是否不适用于 LDAP?
  • 因为使用此服务,您需要将密码返回给 spring,并且您无法从 LDAP 读取密码。相反,要使用 LDAP,您可以实现 AuthenticationProvider
【解决方案2】:

这是你需要的:

  1. 扩展 spring User (org.springframework.security.core.userdetails.User) 类以及您需要的任何属性。
  2. 拉伸弹簧UserDetailsService (org.springframework.security.core.userdetails.UserDetailsService) 并填充上面的对象。覆盖 loadUserByUsername 并返回您的扩展用户类
  3. AuthenticationManagerBuilder 中设置您的自定义UserDetailsService

例如

public class CurrentUser extends User{

   //This constructor is a must
    public CurrentUser(String username, String password, boolean enabled, boolean accountNonExpired,
            boolean credentialsNonExpired, boolean accountNonLocked,
            Collection<? extends GrantedAuthority> authorities) {
        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
    }
    //Setter and getters are required
    private String firstName;
    private String lastName;

}

自定义用户详细信息可以是:

@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {

@Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {

    //Try to find user and its roles, for example here we try to get it from database via a DAO object
   //Do not confuse this foo.bar.User with CurrentUser or spring User, this is a temporary object which holds user info stored in database
    foo.bar.User user = userDao.findByUserName(username);

    //Build user Authority. some how a convert from your custom roles which are in database to spring GrantedAuthority
    List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRole());

    //The magic is happen in this private method !
    return buildUserForAuthentication(user, authorities);

}


//Fill your extended User object (CurrentUser) here and return it
private User buildUserForAuthentication(foo.bar.User user, 
List<GrantedAuthority> authorities) {
    String username = user.getUsername();
    String password = user.getPassword();
    boolean enabled = true;
    boolean accountNonExpired = true;
    boolean credentialsNonExpired = true;
    boolean accountNonLocked = true;

    return new CurrentUser(username, password, enabled, accountNonExpired, credentialsNonExpired,
            accountNonLocked, authorities);
   //If your database has more information of user for example firstname,... You can fill it here 
  //CurrentUser currentUser = new CurrentUser(....)
  //currentUser.setFirstName( user.getfirstName() );
  //.....
  //return currentUser ;
}

private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {

    Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();

    // Build user's authorities
    for (UserRole userRole : userRoles) {
        setAuths.add(new SimpleGrantedAuthority(userRole.getRole()));
    }

    return new ArrayList<GrantedAuthority>(setAuths);
}

}

配置spring安全上下文

@Configuration
@EnableWebSecurity
@PropertySource("classpath://configs.properties")
public class SecurityContextConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("userDetailsService")
    private UserDetailsService userDetailsService;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

一切都搞定了!

您可以致电(CurrentUser)getAuthentication().getPrincipal() 获取新的CurrentUser 或设置一些属性。

【讨论】:

  • 这行得通,但是当服务器重新启动时,我们遇到了现有会话的问题:java.lang.ClassCastException: be.storefront.imicloud.security.MyUserDetails cannot be cast to be.storefront.imicloud。安全性.MyUserDetails。这个应该怎么处理?
  • @Wouter 请使用完整的堆栈跟踪提出不同的问题!
  • 解决方案很好,但请确保不要将(CurrentUser)getAuthentication().getPrincipal() 转换为无需身份验证即可访问的控制器/服务方法。因为anonymous 将由getAuthentication().getPrincipal() 返回,因此ClassCastException: java.lang.String cannot be cast to com.sample.CurrentUser
  • 当您直接更改数据库上的其他参数(如“名字”上面的参数)时,我发现这很危险,持久性框架仍然显示以前的值....
  • @Wouter 对于上面的示例,当“//Setter 和 getter 需要”和“//如果您的数据库有更多用户信息,例如名字时,会发生转换异常,...你可以在这里填写“不符合”。
【解决方案3】:

(我假设你有一个基本的 Spring Security 配置并且知道基本组件是如何协同工作的)

最“正确”的方法是提供您自己的AuthenticationProvider 实现,返回自定义Authentication 实现。然后你可以用你需要的一切填写这个Authentication 实例。例如:

public class MyAuthentication extends UsernamePasswordAuthenticationToken implements Authentication {

    public MyAuthentication(Object principal, Object credentials, int moduleCode) {
        super(principal, credentials);
        this.moduleCode = moduleCode;
    }

    public MyAuthentication(Object principal, Object credentials,  Collection<? extends GrantedAuthority> authorities,int moduleCode) {
        super(principal, credentials, authorities);
        this.moduleCode = moduleCode;
    }

    private int moduleCode;

    public getModuleCode() {
        return moduleCode;
    }   
}


public class MyAuthenticationProvider extends DaoAuthenticationProvider {

    private Collection<GrantedAuthority> obtainAuthorities(UserDetails user) {
        // return granted authorities for user, according to your requirements
    }

    private int obtainModuleCode(UserDetails user) {
        // return moduleCode for user, according to your requirements
    }

    @Override
    public Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        // Suppose this user implementation has a moduleCode property
        MyAuthentication result = new MyAuthentication(authentication.getPrincipal(),
                                                       authentication.getCredentials(),
                                                       obtainAuthorities(user),
                                                       obtainModuleCode(user));
        result.setDetails(authentication.getDetails());
        return result;
    }
}

然后,在applicationContext.xml:

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

<bean id="myAuthenticationProvider" class="MyAuthenticationProvider" scope="singleton">
    ...
</bean>

我想您可以通过提供 AuthenticationDetailsAuthenticationDetailsSource 的自定义实现来使其正常工作,但我认为这是一种不太干净的方法。

【讨论】:

    【解决方案4】:

    您需要做的“唯一”事情是创建自己的 UserDetailsService 实现,它返回您自己的 UserDetails 对象实现。

    有关实现基于 JPA 的 UserDetailsService 的教程,请参阅 here

    【讨论】:

    • 我正在使用 JDBC 连接到数据库。您能否指定我必须在本教程中进行的更改,因为它是基于 JPA 的。
    • 当我覆盖 UserDetailsS​​ervice 的 loadUserByUsername(String username) 时,我不确定如何使用 JDBC 处理失败的身份验证请求。你能帮我解决这个问题吗?
    • 为什么需要处理失败的身份验证请求? Spring 会为您做到这一点,并且在您实现自己的 UserDetailsService 时不会改变。
    • 以用户名和密码不匹配的场景为例,当我覆盖该方法时应该如何处理。在这种情况下我应该只返回一个空对象吗?
    • 你不处理,弹簧安全为你处理。 UserDetailsService 用于查找用户而不是用于检查密码。正如您注意到只有一个方法loadUserByUsername 方法的名称说明了一切,方法的属性也是如此,没有密码那么您将如何验证密码?!
    猜你喜欢
    • 2020-09-13
    • 2012-11-13
    • 2017-01-01
    • 1970-01-01
    • 2021-12-01
    • 2017-04-20
    • 2019-03-08
    • 1970-01-01
    • 2017-10-02
    相关资源
    最近更新 更多