返回介绍

3.1.2 创建自定义的安全配置

发布于 2025-04-21 21:10:07 字数 6793 浏览 0 评论 0 收藏

覆盖自动配置很简单,就当自动配置不存在,直接显式地写一段配置。这段显式配置的形式不限,Spring 支持的 XML 和 Groovy 形式配置都可以。

在编写显式配置时,我们会专注于 Java 形式的配置。在 Spring Security 的场景下,这意味着写一个扩展了 WebSecurityConfigurerAdapter 的配置类。代码清单 3-1 中的 SecurityConfig 就是我们需要的东西。

代码清单 3-1 覆盖自动配置的显式安全配置

package readinglist;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.
                                builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.
                                                         HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.
                                         EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.
                                         WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.
                                            UsernameNotFoundException;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private ReaderRepository readerRepository;

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
        .antMatchers("/").access("hasRole('READER')")    ←---要求登录者有 READER 角色
        .antMatchers("/**").permitAll()

      .and()

      .formLogin()
        .loginPage("/login")     ←---设置登录表单的路径
        .failureUrl("/login?error=true");
  }

@Override
protected void configure(
            AuthenticationManagerBuilder auth) throws Exception {
  auth
    .userDetailsService(new UserDetailsService() {    ←---定义自定义 UserDetailsService
      @Override
      public UserDetails loadUserByUsername(String username)
          throws UsernameNotFoundException {
        return readerRepository.findOne(username);
      }
    });
  }

}

SecurityConfig 是个非常基础的 Spring Security 配置,尽管如此,它还是完成了不少安全定制工作。通过这个自定义的安全配置类,我们让 Spring Boot 跳过了安全自动配置,转而使用我们的安全配置。

扩展了 WebSecurityConfigurerAdapter 的配置类可以覆盖两个不同的 configure() 方法。在 SecurityConfig 里,第一个 configure() 方法指明,“ / ”( ReadingListController 的方法映射到了该路径)的请求只有经过身份认证且拥有 READER 角色的用户才能访问。其他的所有请求路径向所有用户开放了访问权限。这里还将登录页和登录失败页(带有一个 error 属性)指定到了/login。

Spring Security 为身份认证提供了众多选项,后端可以是 JDBC(Java Database Connectivity)、LDAP 和内存用户存储。在这个应用程序中,我们会通过 JPA 用数据库来存储用户信息。第二个 configure() 方法设置了一个自定义的 UserDetailsService ,这个服务可以是任意实现了 UserDetailsService 的类,用于查找指定用户名的用户。代码清单 3-2 提供了一个匿名内部类实现,简单地调用了注入 ReaderRepository (这是一个 Spring Data JPA 仓库接口)的 findOne() 方法。

代码清单 3-2 用来持久化读者信息的仓库接口

package readinglist;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ReaderRepository
         extends JpaRepository<Reader, String> {  ←---通过 JPA 持久化读者
}

BookRepository 类似,你无需自己实现 ReaderRepository 。这是因为它扩展了 JpaRepository ,Spring Data JPA 会在运行时自动创建它的实现。这为你提供了 18 个操作 Reader 实体的方法。

说到 Reader 实体, Reader 类(如代码清单 3-3 所示)就是最后一块拼图了,它就是一个简单的 JPA 实体,其中有几个字段用来存储用户名、密码和用户全名。

代码清单 3-3 定义 Reader 的 JPA 实体

package readinglist;
import java.util.Arrays;
import java.util.Collection;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

@Entity
public class Reader implements UserDetails {

  private static final long serialVersionUID = 1L;

  @Id
  private String username; (以下三行)Reader 字段
  private String fullname;   
  private String password;   

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getFullname() {
    return fullname;
  }

  public void setFullname(String fullname) {
    this.fullname = fullname;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  // UserDetails methods

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {  ←---授予 READER 权限
    return Arrays.asList(new SimpleGrantedAuthority("READER"));
  }

  @Override
  public boolean isAccountNonExpired() {      ←---不过期,不加锁,不禁用
    return true;                                   
  }                                                

  @Override                                        
  public boolean isAccountNonLocked() {       ←---不过期,不加锁,不禁用
    return true;                                   
  }                                                

  @Override                                        
  public boolean isCredentialsNonExpired() {  ←---不过期,不加锁,不禁用
    return true;                                   
  }                                                

  @Override                                        
  public boolean isEnabled() {                ←---不过期,不加锁,不禁用
    return true;
  }

}

如你所见, Reader 用了 @Entity 注解,所以这是一个 JPA 实体。此外,它的 username 字段上有 @Id 注解,表明这是实体的 ID。这个选择无可厚非,因为 username 应该能唯一标识一个 Reader

你应该还注意到 Reader 实现了 UserDetails 接口以及其中的方法,这样 Reader 就能代表 Spring Security 里的用户了。 getAuthorities() 方法被覆盖过了,始终会为用户授予 READER 权限。 isAccountNonExpired()isAccountNonLocked()isCredentialsNonExpired()isEnabled() 方法都返回 true ,这样读者账户就不会过期,不会被锁定,也不会被撤销。

重新构建并重启应用程序后,你应该就能以读者身份登录应用程序了。

保持简单 在一个大型应用程序里,赋予用户的授权本身也可能是实体,它们被维护在独立的数据表里。同样,表示一个账户是否为非过期、非锁定且可用的布尔值也是数据库里的字段。但是,出于演示考虑,我决定让这些细节保持简单,以免分散我们的注意力,影响正在讨论的话题 - 我说的是覆盖 Spring Boot 自动配置。

在安全配置方面,我们还能做更多事情 1 ,但此刻这样就足够了,上面的例子足以演示如何覆盖 Spring Boot 提供的安全自动配置。

1 想要深入了解 Spring Security,可以参考《Spring 实战(第 4 版)》中的第 9 章和第 14 章。

再重申一次,想要覆盖 Spring Boot 的自动配置,你所要做的仅仅是编写一个显式的配置。Spring Boot 会发现你的配置,随后降低自动配置的优先级,以你的配置为准。想弄明白这是如何实现的,让我们揭开 Spring Boot 自动配置的神秘面纱,看看它是如何运作的,以及它是怎么允许自己被覆盖的。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。