Skip to main content

Spring Boot Security

使用 Spring Boot Security 保护 web 应用

创建 web 项目,并引入 security 依赖。

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
}

定义一个 controller 用于测试

@RequestMapping("/hello")
@ResponseBody
public String hello() {
return "hello";
}

启动项目,由于自动配置,日志会打印密码。

Using generated security password: 645debf5-5eff-4941-a1eb-a5122c48258b

This generated password is for development use only. Your security configuration must be updated before running your application in production.

浏览器访问任何 url 会跳转到登陆页面,登陆成功会在 response header 里面 set cookie ,后续请求带上 JSESSIONID 即可访问

set-cookie:
JSESSIONID=359740A39A193A7E36CE4330444BBD5B; Path=/; HttpOnly

curl 'http://localhost:8080/hello' -b 'JSESSIONID=359740A39A193A7E36CE4330444BBD5B'

也可以直接使用 http basic 认证

curl -u user:645debf5-5eff-4941-a1eb-a5122c48258b http://localhost:8080/hello

SecurityFilterChain 用于配置 Security 的核心接口,配置哪些请求需要认证,如何处理认证和授权,等等

UserDetailsService 用于管理 user
PasswordEncode 用于加密密码

@Configuration
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.formLogin(withDefaults());

return http.build();
}

@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User
.withUsername("user")
.password(passwordEncoder().encode("password"))
.roles("USER")
.build();

return new InMemoryUserDetailsManager(user);
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

使用 h2 和 jpa 存储用户信息

spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password: ''
h2:
console:
enabled: true
path: /h2-console
jpa:
hibernate:
ddl-auto: update
show-sql: true

自定义 User 和 UserDetailService

public class MyUser {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;

@Column(name = "username", nullable = false, unique = true)
String username;

@Column(name = "password", nullable = false)
String password;


@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
@Column(name = "role")
private Set<String> roles;

}


@Service
@AllArgsConstructor
public class MyUserDetailService implements UserDetailsService {

private MyUserRepository myUserRepository;

private PasswordEncoder passwordEncoder;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
MyUser user = myUserRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));

List<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> (GrantedAuthority) new SimpleGrantedAuthority(role))
.toList();
return new User(user.getUsername(), user.getPassword(), authorities);
}

public void addUser(MyUser user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
myUserRepository.save(user);
}
}

启动会自动创建表

Hibernate: create table my_user (id bigint generated by default as identity, password varchar(255) not null, username varchar(255) not null, primary key (id))
Hibernate: create table user_roles (user_id bigint not null, role varchar(255))
Hibernate: alter table if exists my_user drop constraint if exists UKsfp0l65piri344cgr5yiugcd3
Hibernate: alter table if exists my_user add constraint UKsfp0l65piri344cgr5yiugcd3 unique (username)
Hibernate: alter table if exists user_roles add constraint FK9oewexa178ua52tkqsfuouoml foreign key (user_id) references my_user

方法鉴权

配置类加上注解 @EnableMethodSecurity ,在需要鉴权方法上加上 @PreAuthorize 注解

@RequestMapping("/user")
@ResponseBody
@PreAuthorize("hasAuthority('USER')")
public String user() {
return "user";
}

@RequestMapping("/admin")
@ResponseBody
@PreAuthorize("hasAuthority('ADMIN')")
public String admin() {
return "admin";
}