日常提交
This commit is contained in:
@@ -160,3 +160,178 @@ public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
### 基本Authentication
|
||||
#### 基本Authentication的认证流程
|
||||
1. 用户向私有资源发送未认证请求,其中对私有资源的访问并没有被授权
|
||||
2. SpringSecurity的FilterSecurityInterceptor表明该未认证的请求被拒绝访问,抛出AccessDeniedException
|
||||
3. 由于该请求没有经过身份认证,故而ExceptionTranslationFilter启动身份认证,被配置好的AuthenticationEntryPoint是一个BasicAuthenticationEntryPoint类的实例,该实例会发送WWW-Authentication的header。RequestCache通常是一个NullRequestCache,不会保存任何的http request请求,因为客户端能够重新发送其原来发送过的请求。
|
||||
4. 当客户端获取到WWW-Authentication的header,客户端会知道其接下来会通过username和password重新尝试,重新发送http请求。
|
||||
|
||||
默认情况下,basic authentication是被开启的。但是,如果有任何基于基于servlet的配置被提供,那么必须通过如下方式显式开启basic authentication。
|
||||
```java
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
http
|
||||
// ...
|
||||
.httpBasic(withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
```
|
||||
|
||||
### Digest Authentication(摘要认证,***不安全***)
|
||||
在目前,不应该在现代应用程序中使用Digest Authentication,因为使用摘要认证时必须将password通过纯文本、加密或MD5的格式存储(MD5已经被证实不安全)。相对的,应该使用单向的密码散列(如bCrypt, PBKDF2, SCrypt)来存储认证凭证,但是这些都不被Digest Authentication所支持。
|
||||
> 摘要认证主要用来解决Basic Authentication中存在的问题,摘要认证确保了认证凭证在网络上不会以明文的方式传输。
|
||||
> 如果想要使用非https的方式并且最大限度的加强认证过程,那么可以考虑使用Digest Authentication。
|
||||
|
||||
#### 摘要认证中的随机数
|
||||
摘要认证中的核心是随机数,该随机数的值由服务端产生,Spring Security中随机数次啊用如下格式:
|
||||
```txt
|
||||
base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
|
||||
expirationTime: The date and time when the nonce expires, expressed in milliseconds
|
||||
key: A private key to prevent modification of the nonce token
|
||||
```
|
||||
需要为存储不安全的密码文本配置使用NoOpPasswordEncoder。可以通过如下方式来配置Digest Authentication。
|
||||
```java
|
||||
@Autowired
|
||||
UserDetailsService userDetailsService;
|
||||
|
||||
DigestAuthenticationEntryPoint entryPoint() {
|
||||
DigestAuthenticationEntryPoint result = new DigestAuthenticationEntryPoint();
|
||||
result.setRealmName("My App Relam");
|
||||
result.setKey("3028472b-da34-4501-bfd8-a355c42bdf92");
|
||||
}
|
||||
|
||||
DigestAuthenticationFilter digestAuthenticationFilter() {
|
||||
DigestAuthenticationFilter result = new DigestAuthenticationFilter();
|
||||
result.setUserDetailsService(userDetailsService);
|
||||
result.setAuthenticationEntryPoint(entryPoint());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// ...
|
||||
.exceptionHandling(e -> e.authenticationEntryPoint(authenticationEntryPoint()))
|
||||
.addFilterBefore(digestFilter());
|
||||
return http.build();
|
||||
}
|
||||
```
|
||||
## 密码存储方式
|
||||
### 内存中存储密码
|
||||
Spring Security中InMemoryUserDetailsManager实现了UserDetailsService,用于向基于存储在内存中的密码认证提供支持。
|
||||
InMemoryUserDetailsManager通过实现UserDetailsManager接口来提供对UserDetails的管理。基于UserDetails的认证主要用来接受基于用户名/密码的认证。
|
||||
InMemoryUserDetailsManager可以通过如下方式进行配置:
|
||||
```java
|
||||
@Bean
|
||||
public UserDetailsService users() {
|
||||
UserDetails user = User.builder()
|
||||
.username("user")
|
||||
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
|
||||
.roles("USER")
|
||||
.build();
|
||||
UserDetails admin = User.builder()
|
||||
.username("admin")
|
||||
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
|
||||
.roles("USER", "ADMIN")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user, admin);
|
||||
}
|
||||
```
|
||||
#### 内存中存储密码时使用defaultPasswordEncoder
|
||||
***通过defaultPasswordEncoder来指定密码编码器时,无法防止通过反编译字节码来获取密码的攻击。***
|
||||
```java
|
||||
@Bean
|
||||
public UserDetailsService users() {
|
||||
// The builder will ensure the passwords are encoded before saving in memory
|
||||
UserBuilder users = User.withDefaultPasswordEncoder();
|
||||
UserDetails user = users
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build();
|
||||
UserDetails admin = users
|
||||
.username("admin")
|
||||
.password("password")
|
||||
.roles("USER", "ADMIN")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user, admin);
|
||||
}
|
||||
```
|
||||
|
||||
### JDBC Authentication
|
||||
Spring Security的JdbcDaoImpl实现了UserDetailsService来基于username/password的认证提供从jdbc获取密码的支持。JdbcUserDetailsManager继承了JdbcDaoImpl来通过DetailsManager的接口提供对UserDetails的管理。
|
||||
Spring Security为基于jdbc的认证提供了默认的查询语句。
|
||||
#### User Schema
|
||||
JdbcDaoImpl需要数据表来导入密码、账户状态和用户的一系列权限。JdbcDaoImpl默认需要的schema如下:
|
||||
```sql
|
||||
# 创建用户表和权限表,并且将用户表和权限表之间用外键关联
|
||||
# 用户表需要提供username、password、用户状态
|
||||
# 权限表需要提供用户名和权限名称
|
||||
create table users(
|
||||
username varchar_ignorecase(50) not null primary key,
|
||||
password varchar_ignorecase(500) not null,
|
||||
enabled boolean not null
|
||||
);
|
||||
|
||||
create table authorities (
|
||||
username varchar_ignorecase(50) not null,
|
||||
authority varchar_ignorecase(50) not null,
|
||||
constraint fk_authorities_users foreign key(username) references users(username)
|
||||
);
|
||||
create unique index ix_auth_username on authorities (username,authority);
|
||||
```
|
||||
#### Group Schema
|
||||
如果你的程序中使用了Group,那么还额外需要一张group的表,默认如下:
|
||||
```sql
|
||||
# 如果要为group配置权限,需要引入三张表,group表,权限表和group_member表
|
||||
create table groups (
|
||||
id bigint auto_increment primary key,
|
||||
group_name varchar_ignorecase(50) not null
|
||||
);
|
||||
|
||||
create table group_authorities (
|
||||
group_id bigint not null,
|
||||
authority varchar(50) not null,
|
||||
constraint fk_group_authorities_group foreign key(group_id) references groups(id)
|
||||
);
|
||||
|
||||
create table group_members (
|
||||
id bigint auto_increment primary key,
|
||||
username varchar(50) not null,
|
||||
group_id bigint not null,
|
||||
constraint fk_group_members_group foreign key(group_id) references groups(id)
|
||||
);
|
||||
```
|
||||
|
||||
#### 配置Datasource
|
||||
```java
|
||||
// 生产环境时,应该通过对外部数据库的连接来建立数据源
|
||||
@Bean
|
||||
DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.setType(H2)
|
||||
.addScript(JdbcDaoImpl.DEFAULT_USER_SCHEMA_DDL_LOCATION)
|
||||
.build();
|
||||
}
|
||||
```
|
||||
#### 创建JdbcUserDetailsManager Bean对象
|
||||
```java
|
||||
@Bean
|
||||
UserDetailsManager users(DataSource dataSource) {
|
||||
UserDetails user = User.builder()
|
||||
.username("user")
|
||||
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
|
||||
.roles("USER")
|
||||
.build();
|
||||
UserDetails admin = User.builder()
|
||||
.username("admin")
|
||||
.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
|
||||
.roles("USER", "ADMIN")
|
||||
.build();
|
||||
JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
|
||||
users.createUser(user);
|
||||
users.createUser(admin);
|
||||
return users;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user