服务端开发(4) Spring Security
2023-08-09 14:53:19 # NJU # 服务端开发

Spring Security

1. 实现过程

1.1 依赖及配置

依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

两种配置

  • 纯Java配置类

    1
    2
    @Configuration
    public class SecurityConfig {}
  • 继承自WebSecurityConfigurerAdapter

    1
    2
    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {}

用户信息存储

  • 内存用户存储
  • JDBC用户存储
  • LDAP用户存储

1.2 UserDetails

使用Spring Data存储库来保存用户

定义领域对象:User,实现了UserDetails接口,Spring Security就可以做到基于用户名密码进行认证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// User.java
public class User implements UserDetails {
...
}

// UserDetails.class
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();

String getPassword();

String getUsername();

boolean isAccountNonExpired();

boolean isAccountNonLocked();

boolean isCredentialsNonExpired();

boolean isEnabled();
}

定义持久化接口:UserRepository,增加自定义方法:findByUsername

1.3 UserDetailsService

在配置类中添加 Bean,提供接口实现,Spring可以用于从用户名获取用户信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Bean
public UserDetailsService userDetailsService(UserRepository userRepo) {
return username -> {
User user = userRepo.findByUsername(username);
if (user != null) {
return user;
}
throw new UsernameNotFoundException(
"User '" + username + "' not found");
};
}

// UserDetailsService.class
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

1.4 PasswordEncoder

提供密码转换器,保证安全

1
2
3
4
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

1.5 创建自定义登录页

当需要认证时转向的登录页:.loginPage("/login")

视图控制器,定义login请求对应的视图:registry.addViewController("/login");

登录的post请求由Spring Security自动处理,名称默认:username、password,可配置

1.6 保护Web请求

在 Controller 前处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests()
.mvcMatchers("/design", "/orders").hasRole("USER")
.anyRequest().permitAll()
.and() // 如果用户没有定义则重定向到登录
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutSuccessUrl("/")
// Make H2-Console non-secured; for debug purposes
.and()
.csrf()
.ignoringAntMatchers("/h2-console/**")
// Allow pages to be loaded in frames from the same origin; needed for H2-Console
.and()
.headers()
.frameOptions()
.sameOrigin()
.and()
.build();
}

2. 权限分类

Authority,权限

Role,角色,===>>>权限,加前缀:ROLE_

1
2
3
4
5
// User.java
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}

3. 实现方法级别的安全

1
2
3
4
5
6
7
8
9
@Configuration
@EnableGlobalMethodSecurity //注意要添加,注解才能生效
public class SecurityConfig extends WebSecurityConfigurerAdapter {}

// OrderAdminService.java
@PreAuthorize("hasRole('ADMIN')")
public void deleteAllOrders() {
orderRepository.deleteAll();
}

4. 获取当前登录的用户

Principal

1
2
3
4
5
6
@ModelAttribute(name = "user") // jdk jaas java底层提供的框架
public User user(Principal principal) {
String username = principal.getName();
User user = userRepo.findByUsername(username);
return user;
}

@AuthenticationPrincipal

1
2
3
4
5
6
@PostMapping
public String processOrder(@Valid TacoOrder order, Errors errors,
SessionStatus sessionStatus,
@AuthenticationPrincipal User user) {
...
}

安全上下文获取

1
2
3
// 可用于任何地方
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();