2023-05-26 22:15:27
  • 认证
  • 动态配置接口白名单
  • csrf
  • BCrypt

是什么

SpringSecurity是基于Spring的安全管理框架,对系统提供认证、权限控制、安全防护的功能。

  • 认证 用户是否是系统的合法用户
  • 权限控制 用户是否有权限访问系统的资源

Demo

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

引入依赖后再次访问接口会跳转到默认的登录页面:

这是因为Security默认使用的basic认证,在前后端分离的环境下,这样肯定是不行的,我们要给前端返回json响应,由前端来处理页面。但是像在给swagger文档加密的时候可以使用这种简单的处理方式

后面创建配置类继承重写WebSecurityConfigurerAdapter的configure方法就不会走basic认证了。

认证流程

SpringSecurity的原理是基于过滤器链来实现的,在这个过滤器链中有一个Authentication对象,过滤器会对这个Authentication对象认证,最后方的过滤器FilterSecurityInterceptor会检查对象有没有认证,如果认证通过则访问API,没有通过抛出异常,交给ExceptionFilter异常过滤器来处理。(Authentication对象有一个isAuthenticated,认证通过后是true)

  • 绿色部分负责认证
  • 蓝色部分负责处理异常
  • 橙色部分负责授权

Security默认提供的认证过滤器UsernameFilter和BasicFilter是基于form表单和basic的,不适用于前后端分离的场景,前后端分离一般使用Token令牌进行认证,因此需要自定义Token令牌过滤器加入到Security的过滤器链条中

核心组件

在编写代码之前,需要先了解下Security的核心组件:

Authentication:认证接口,定义了认证对象的数据形式。

  • UsernamePasswordAuthenticationToken是Authentication的实现类,基于用户名和密码认证的

AuthenticationManager:用于校验Authentication对象,返回一个认证完成后的Authentication对象。

  • AuthenticationManager只是一个接口,它的实现类是ProviderManger,ProviderManager维护了很多个Provider,由这些Provider去完成校验,具体使用哪个Provider,取决于Provider的supports()方法
  • 比如UsernamePasswordAuthenticationToken,那么由AbstractUserDetailsAuthenticationProvider去认证,AbstractUserDetailsAuthenticationProvider会去调用UserDetailsService,获取到UserDetails用户信息, 然后再去匹配密码,没问题的话就返回Authenticaion,认证成功

SecurityContext:上下文对象,存放Authentication对象,提供了get/set方法

SecurityContextHolder:用于拿到上下文对象的静态工具类。

UserDetails:定义了用户的数据形式,也就是数据库中的用户信息,用户名、密码、手机号、用户类型等等

UserDetailsService:去数据库/缓存查询用户,当AuthenticationManager认证用户的时候会调用UserDetailsService的方法

UsernamePasswordAuthenticationToken是基于用户名和密码认证的,而我们实际的认证场景还有短信登录、手机号登录、微信登录等等,可以针对每一种场景去定义一个Token和一个Provider(因为Provider是校验Token的,Token自定义了,Provider肯定也要自定义),然后在Provider来实现认证的逻辑即可。这样其实也是将认证这块代码解耦合了。

开发

配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

/**
* 权限配置
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeHttpRequests()
.antMatchers( //静态资源不需要认证
"/passport/register",
"/passport/usernameLogin",
"/posts/publish"
).permitAll()
.anyRequest().authenticated(); //其余接口需要认证
http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint());
//因为filter的执行也是有顺序的,我们必须要把我们的filter放在认证过滤器链才有效果。
http.addFilterBefore(new UserAuthTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}

/**
* 这里配置的url不会走过滤器,**前提是过滤器不要交给Spring来管理。**
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/doc.html",
"/v2/api-docs",
"/swagger-resources/configuration/ui",
"/swagger-resources",
"/swagger-resources/configuration/security",
"/swagger-resources/**",
"/webjars/**");
}

/**
* 密码认证器
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

/**
* 进行认证
* @return
* @throws Exception
*/
@Override
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}


/**
* 认证失败的处理
* @return
*/
@Bean
public RestAuthenticationEntryPoint restAuthenticationEntryPoint() {
return new RestAuthenticationEntryPoint();
}

}

过滤器

Prev
2023-05-26 22:15:27
Next