如果使用之前说过的基于 JDBC 的认证方案,那么必须遵循一定的规范,比如字段名名称限制等等。所以如果需要完全自定义,我们可以使用直接 Spring Data Repository。
Craig Walls 举了这样一个示例,来说明如何自定义账户权限体系。
1 定义账户实体类
@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
@RequiredArgsConstructor
public class User implements UserDetails {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private final String username;
private final String password;
private final String fullname;
private final String street;
private final String city;
private final String state;
private final String zip;
private final String phoneNumber;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
除了定义了一些账户基本属性之外, User 类还实现了 Spring Security 的 UserDetails 接口。该接口定义一些与权限有关的方法。
方法名 | 说明 |
---|---|
getAuthorities | 返回该账户被授予的权限集合 |
isAccountNonExpired | 账户是否未过期 |
isAccountNonLocked | 账户是否未被锁定 |
isCredentialsNonExpired | 账户凭证是否未过期 |
isEnabled | 账户是否可用 |
示例代码中,以上这些方法全部返回 TRUE,即允许该账户登录使用。
2 账户 Repository 接口
public interface UserRepository extends CrudRepository<User,Long> {
User findByUsername(String username);
}
Spring Data JPA 会在运行时自动生成账户 Repository 接口的实现类。这种方式的好处是不需要写代码。
3 账户 Service 接口
Spring Security 本身定义了 UserDetailsService 接口,代码如下:
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
Service接口中定义了一个依据账号名获取账号的方法。如果找不到相应账号,就会抛出 UsernameNotFoundException 异常。
UserDetailsService 是Spring Security 定义的接口。
定义 UserDetailsService 的实现类:
@Service
public class UserRepositoryUserDetailsService implements UserDetailsService {
private UserRepository userRepository;
@Autowired
public UserRepositoryUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user != null) {
return user;
} else {
throw new UsernameNotFoundException("User '" + username + "' not found");
}
}
}
UserDetailsService 的实现类通过构造器方式将UserRepository 注入进来。然后在 loadUserByUsername 方法中通过 UserRepository 的 findByUsername 方法来查询账户对象。
UserDetailsService 的实现类使用了 @Service 注解,这样 Spring 应用启动时,就会将其初始化为一个 bean。
接下来需要把 UserDetailsService 服务与 Spring Security 绑定起来。在 SecurityConfig 类中,注入并绑定UserDetailsService 服务:
@Autowired
UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
与 JDBC 方式一样,我们也可以配置密码编码器,这样存放在数据库中的密码就是密文的。
@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder(6);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(encoder());
}
首先定义一个返回 PasswordEncoder 对象的方法,然后调用passwordEncoder() 方法,该方法的入参是PasswordEncoder 对象,这样 AuthenticationManagerBuilder 对象就装配上了密码编码器。
因为encoder() 方法定义了 @Bean 注解,所以对encoder() 方法的调用都会被 Spring 拦截,接着 Spring 框架会返回应用上下文中的 PasswordEncoder bean 实例。
通过以上方式,我们就可以实现自定义 Spring Security 权限体系。
网友评论