From e8484aa581fef695d36368cf751cbaa501d97ff2 Mon Sep 17 00:00:00 2001 From: Rikako Wu <496063163@qq.com> Date: Sun, 9 Oct 2022 23:26:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=A5=E5=B8=B8=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spring/Spring Security/Spring Security.md | 175 ++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/spring/Spring Security/Spring Security.md b/spring/Spring Security/Spring Security.md index 93884c2..95bdd78 100644 --- a/spring/Spring Security/Spring Security.md +++ b/spring/Spring Security/Spring Security.md @@ -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; +} +``` +