第 40 章 扩展UserDetails

如果希望扩展登录时加载的用户信息,最简单直接的办法就是实现UserDetails接口,定义一个包含所有业务数据的对象。我们下面演示如何将用户邮箱加入UserDetails中。

40.1. 实现UserDetails接口

UserDetails接口中总共声明了六个方法:

public interface UserDetails extends Serializable {
    GrantedAuthority[] getAuthorities();1
    String getPassword();2
    String getUsername();3
    boolean isAccountNonExpired();4
    boolean isAccountNonLocked();5
    boolean isCredentialsNonExpired();6
    boolean isEnabled();7
}
        

1

用户拥有的权限

2

用户名

3

密码

4

用户账号是否过期

5

用户账号是否被锁定

6

用户密码是否过期

7

用户是否可用

我们的任务就是实现这六个接口,同时添加一个getEmail()方法,用以获得用户的邮箱地址。

最初我们的打算是直接继承Spring Security中默认提供的实现类User,但是User为了避免用户信息被外部程序篡改,被设计为只能通过构造方法来为内部数据赋值,没有提供setter方法对其中数据进行修改,因此为了之后演示的方便,我们仿照User类自行实现了一个BaseUserDetails类,在BaseUserDetails中所有属性都被定义为protected,可以暴露给子类进行操作。

在BaseUserDetails的基础上,我们实现了UserInfo类,在它里面添加有关email的属性和方法。

package com.family168.springsecuritybook.ch208;

import org.springframework.security.GrantedAuthority;

public class UserInfo extends BaseUserDetails {

    private static final long serialVersionUID = 1L;

    private String email;

    public UserInfo(String username, String password, boolean enabled, GrantedAuthority[] authorities)
        throws IllegalArgumentException {
        super(username, password, enabled, authorities);
    }

    public String getEmail() {
        return this.email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
        

40.2. 实现UserDetailsService接口

为了将UserInfo提供给权限系统,我们还需要实现自定义的UserDetailsService,这个接口只包含一个方法:

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username)
        throws UsernameNotFoundException, DataAccessException;
}
        

实际运行中,系统会通过这个方法获得登录用户的信息。

下面我们直接实现UserDetailsService接口,在其中创建UserInfo的对象。

public class UserInfoService implements UserDetailsService {
    private Map<String, UserInfo> userMap = null;

    public UserInfoService() {
        userMap = new HashMap<String, UserInfo>();

        UserInfo userInfo = null;
        userInfo = new UserInfo("user", "user", true,
                Collections.singletonList(
                    new GrantedAuthorityImpl("ROLE_USER")));
        userInfo.setEmail("user@family168.com");
        userMap.put("user", userInfo);

        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        authorities.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
        authorities.add(new GrantedAuthorityImpl("ROLE_USER"));
        userInfo = new UserInfo("admin", "admin", true, authorities);
        userInfo.setEmail("admin@family168.com");
        userMap.put("admin", userInfo);
    }

    public UserDetails loadUserByUsername(String username)
        throws UsernameNotFoundException, DataAccessException {
        return userMap.get(username);
    }
}
        

40.3. 修改配置文件

将UserInfoService添加到配置文件中:

<authentication-provider user-service-ref="userDetailsService"/>

<beans:bean id="userDetailsService" class="com.family168.springsecuritybook.ch208.UserInfoService"/>
        

定义userDetailsService之后,然后使用user-service-ref为authentication-provider设置对UserDetailsService的引用,这样在系统中就会从我们自定义的UserInfoService中获取用户信息了。

40.4. 测试运行

修改过配置文件后,在ch208中启动mvn,还是通过登录页面进入系统,在登录成功页面中就可以看到用户对应的邮箱地址了。

显示邮箱地址信息

图 40.1. 显示邮箱地址信息


这时保存在SecurityContext中的Principal已经变为了UserInfo类型的对象,我们可以直接使用taglib获得启动的邮件信息。

email : <sec:authentication property="principal.email"/>
        

如果希望获得UserInfo对象,可以使用如下代码:

UserInfo userInfo = (UserInfo) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        

实例在ch208中。