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
要创建自定义安全类,我们需要使用 `@EnableWebSecurity` 注解,并使用 `@WebSecurityConfigurerAdapter` 注解扩展该类,以便我们可以重新定义该类提供的一些方法。Spring Security 还强制要求您对密码进行哈希处理,以避免以明文形式保存。在接下来的示例中,我们可以使用`PasswordEncoder`,当然,这不应该在生产环境中使用,但对于本示例来说足够了。另一种选择是`BCryptPasswordEncoder`。
验证
在 IntelliJ 中,按 cmd+N 查看可以重写的方法。我们将使用带有AuthenticationManagerBuilder参数的configure方法。auth类有多种方法,例如jdbcAuthentication、ldapAuthentication、userDetailsService等,但在这个简单示例中,我们将使用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();
}
}
授权
在这里,我们定义哪些 URL 路径需要保护,哪些不需要。现在我们使用带有HttpSecurity参数的configure方法。我授予所有具有 SENSEI 角色的用户访问权限。
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").hasRole("SENSEI")
.and().formLogin();
}
如果我们尝试使用用户 user2 登录,则会授予访问权限。/** 表示允许访问当前层级及其所有内部层级。我们可以创建不同的端点并设置不同的限制,如下例所示。需要注意的是,限制性最强的规则应该放在最上面。
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/sensei").hasRole("SENSEI")
.antMatchers("/apprentice").hasRole("APPRENTICE")
.antMatchers("/").permitAll()
.and().formLogin();
}
如果您想设置不同的角色,可以尝试使用hasAnyRole() 函数。我们还可以添加过滤器,它们的目的是拦截请求,每个过滤器都有自己的职责(我们稍后会添加一个)。
.hasAnyRole("ROLE1", "ROLE2", "ROLE3")
.addFilterBefore()
.addFilterAfter()
用户详细信息服务
我们将配置 Spring Security 以依赖于 UserDetailsService。它主要用于加载用户特定数据。auth类有一个名为userDetailsService()的方法,允许基于 UserDetailsService 接口进行自定义身份验证。
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailService myUserDetailService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailService);
}
让我们创建自己的 UserDetailService 和 UserDetail 类。在MyUserDetailService中,我们重写loadUserByUsername方法,该方法将接收用户名作为参数并将其传递给MyUserDetails。
@Service
public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new MyUserDetails(username);
}
}
在实现 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;
}
}
流程如下:
- WebSecurity 类已加载
- 当用户输入用户名+密码时,身份验证过滤器会拦截该请求,并使用请求凭据创建一个 UsernamePasswordAuthenticationToken 对象。
- loadUserByUsername 接收用户名。
- 使用发送的任何用户名和所有其他硬编码内容(假装是我们数据库中的用户)创建 MyUserDetails 对象,并将其与 UsernamePasswordAuthenticationToken 对象进行比较。
- 如果一切正常😃,否则⛔️
与其在服务中硬编码 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);
}
}
我会在另一篇文章中继续介绍 JWT,所以这篇文章不会太长。
文章来源:https://dev.to/jhonifaber/getting-started-with-spring-security-authentication-and-authorization-32de

