发布于 2026-01-06 0 阅读
0

Spring Security 入门 - 身份验证和授权

Spring Security 入门 - 身份验证和授权

最近几天我一直在研究 Spring Security,所以我想和大家分享一下我学到的一些东西。

身份验证:确认用户身份。
授权:用户拥有哪些权限。

首先,在 pom.xml 文件中添加 Spring Boot Starter Security 依赖项以启用基本身份验证。
在控制器中创建一个端点并尝试使用它,此时将显示 Spring Security 提供的登录表单。

登录表单

运行应用程序时,请查看控制台,您应该会看到自动生成的密码,用户名默认为“user”。

控制台消息

我们甚至可以在 application.properties 文件中配置我们自己的用户凭据。

spring.security.user.name=jhoni
spring.security.user.password=1234
Enter fullscreen mode Exit fullscreen mode

要创建自定义安全类,我们需要使用 `@EnableWebSecurity` 注解,并使用 `@WebSecurityConfigurerAdapter` 注解扩展该类,以便我们可以重新定义该类提供的一些方法。Spring Security 还强制要求您对密码进行哈希处理,以避免以明文形式保存。在接下来的示例中,我们可以使用`PasswordEncoder`,当然,这不应该在生产环境中使用,但对于本示例来说足够了。另一种选择是`BCryptPasswordEncoder`

验证

在 IntelliJ 中,按 cmd+N 查看可以重写的方法。我们将使用带有AuthenticationManagerBuilder参数的configure方法。auth有多种方法,例如jdbcAuthenticationldapAuthenticationuserDetailsS​​ervice等,但在这个简单示例中,我们将使用inMemoryAuthentication。顾名思义,用户凭据存储在内存中。

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user1")
                .password("123")
                .roles("APPRENTICE")
                .and()
                .withUser("user2")
                .password("123")
                .roles("SENSEI");
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}
Enter fullscreen mode Exit fullscreen mode


授权

在这里,我们定义哪些 URL 路径需要保护,哪些不需要。现在我们使用带有HttpSecurity参数的configure方法。我授予所有具有 SENSEI 角色的用户访问权限。

@Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/**").hasRole("SENSEI")
                .and().formLogin();

    }
Enter fullscreen mode Exit fullscreen mode

如果我们尝试使用用户 user2 登录,则会授予访问权限。/** 表示允许访问当前层级及其所有内部层级。我们可以创建不同的端点并设置不同的限制,如下例所示。需要注意的是,限制性最强的规则应该放在最上面。

@Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/sensei").hasRole("SENSEI")
                .antMatchers("/apprentice").hasRole("APPRENTICE")
                .antMatchers("/").permitAll()
                .and().formLogin();
    }
Enter fullscreen mode Exit fullscreen mode

如果您想设置不同的角色,可以尝试使用hasAnyRole() 函数。我们还可以添加过滤器,它们的目的是拦截请求,每个过滤器都有自己的职责(我们稍后会添加一个)。

.hasAnyRole("ROLE1", "ROLE2", "ROLE3")
.addFilterBefore()
.addFilterAfter()
Enter fullscreen mode Exit fullscreen mode


用户详细信息服务

我们将配置 Spring Security 以依赖于 UserDetailsS​​ervice。它主要用于加载用户特定数据。auth类有一个名为userDetailsS​​ervice()的方法,允许基于 UserDetailsS​​ervice 接口进行自定义身份验证。

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailService myUserDetailService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailService);
    }
Enter fullscreen mode Exit fullscreen mode

让我们创建自己的 UserDetailService 和 UserDetail 类。在MyUserDetailService中,我们重写loadUserByUsername方法,该方法将接收用户名作为参数并将其传递给MyUserDetails

@Service
public class MyUserDetailService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return new MyUserDetails(username);
    }
}
Enter fullscreen mode Exit fullscreen mode

在实现 UserDetails 接口时,我们可以重写多个方法。我将创建一个名为username 的字段,用于获取用户登录时传入的用户名。其余方法将使用硬编码值。getAuthorities方法返回授予用户的权限,在本例中,我将添加 SENSEI 角色。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyUserDetails implements UserDetails {

    private String username;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() { 
        return List.of(new SimpleGrantedAuthority("ROLE_SENSEI"));
    }

    @Override
    public String getPassword() {
        return "pass";
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
Enter fullscreen mode Exit fullscreen mode

流程如下:

  1. WebSecurity 类已加载
  2. 当用户输入用户名+密码时,身份验证过滤器会拦截该请求,并使用请求凭据创建一个 UsernamePasswordAuthenticationToken 对象。
  3. loadUserByUsername 接收用户名。
  4. 使用发送的任何用户名和所有其他硬编码内容(假装是我们数据库中的用户)创建 MyUserDetails 对象,并将其与 UsernamePasswordAuthenticationToken 对象进行比较。
  5. 如果一切正常😃,否则⛔️

与其在服务中硬编码 MyUserDetails 的创建过程,不如注入存储库并从数据库中检索用户详细信息。当然,MyUserDetails类需要进行修改,使其接收User 对象而不是像以前那样接收字符串。

@Service
@RequiredArgsConstructor
public class MyUserDetailService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
              .orElseThrow(() -> new UsernameNotFoundException("Username does not exist"));
        return new MyUserDetails(user);
    }
}
Enter fullscreen mode Exit fullscreen mode

我会在另一篇文章中继续介绍 JWT,所以这篇文章不会太长。

文章来源:https://dev.to/jhonifaber/getting-started-with-spring-security-authentication-and-authorization-32de