Security - 概念与权限
笔记
学习 Spring Security,首先要认识一些概念,以及常用的类。
2021-12-25 @Young Kbt
# 权限管理概念
主体(principal):使用系统的用户或设备或从其他系统远程登录的用户等等。简单说就是谁使用系统谁就是主体。在 Spring Security 中,主体就是用户名。
认证(authentication):权限管理系统确认一个主体的身份,允许主体进入系统。简单说就是「主体」证明自己是谁。笼统的认为就是 Spring Security 判断当前用户曾经是否登录过。
授权(authorization):将操作系统的 权力授予主体,这样主体就具备了操作系统中特定功能的能力。所以简单来说,授权就是给用户分配权限,比如登录才能访问资源。
# 基本原理
Spring Security 本质是一个过滤器链,当用户访问项目时,首先会 Security 被拦截,判断是否满足权限,是的话才允许访问。
Spring Security 有大量的过滤器链,这些类部分 从高到低 如下:
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
org.springframework.security.web.context.SecurityContextPersistenceFilter
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.session.SessionManagementFilter
org.springframework.security.web.access.ExceptionTranslationFilter
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
2
3
4
5
6
7
8
9
10
11
12
13
14
虽然过滤器类有很多,但是重点看看三个过滤器:
FilterSecurityInterceptor
:是一个方法级的权限过滤器, 基本位于过滤链的最底部部分源码如下:
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException { if (isApplied(filterInvocation) && this.observeOncePerRequest) { // filter already applied to this request and user wants us to observe // once-per-request handling, so don't re-do security checking filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse()); return; } // first time this request being called, so perform security checking if (filterInvocation.getRequest() != null && this.observeOncePerRequest) { filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } InterceptorStatusToken token = super.beforeInvocation(filterInvocation); try { filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse()); } finally { super.finallyInvocation(token); } super.afterInvocation(token, null); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20super.beforeInvocation(fi)
表示查看之前的 filter 是否通过。filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse())
表示真正的调用后台的服务。ExceptionTranslationFilter
:是个异常过滤器,用来处理在认证授权过程中抛出的异常UsernamePasswordAuthenticationFilter
:对/login
的 POST 请求做拦截,校验表单中用户名,密码。从源码可以知道,默认的登录请求是
/login
,并且请求方式是 POST,请求,你可以修改登录的请求,但是不要修改请求方式
# 常用接口详解
# UserDetailsService
当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑。
如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。接口内容如下:
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
2
3
4
5
可以看出只有一个方法,这个方法很重要,一般自定义类实现这个接口,然后实现这个方法,主要用于查询数据库的用户信息,然后交给 Spring Security 保管。
例子如下:
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查询该用户的信息,必须包括:用户名、密码、权限
User user = userService.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("该用户不存在");
}
// 用户权限列表,Role 自己定义,和数据库字段保持一致
List<Role> permissions = userService.findPermissions(username);
// Spring Security 要求是这个集合和泛型
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
for (Role permission : permissions) {
grantedAuthorities.add(new SimpleGrantedAuthority(permission.getRoleName()));
}
return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
可以看到,查询用户的信息不仅有用户名和密码,还必须要有权限。
查询数据库返回的 User 类是自己定义的,方法最后返回的 User 类是 Spring Security 提供的,权限要求是 List 集合。
# UserDetails
这个类是系统默认的用户「主体」,可以看看源码:
public interface UserDetails extends Serializable {
/**
* 用户权限列表
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* 用户密码
*/
String getPassword();
/**
* 用户名
*/
String getUsername();
/**
* 用户登录状态是否过期
*/
boolean isAccountNonExpired();
/**
* 用户是否被锁定
*/
boolean isAccountNonLocked();
/**
* 密码是否过期
*/
boolean isCredentialsNonExpired();
/**
* 用户是否可用
*/
boolean isEnabled();
}
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
那么谁实现这个接口呢?上面提到的 User 就是实现了这个接口:
以后我们只需要使用 User 这个实体类即可,如果该 User 类无法满足你的需求,可以自定义类实现该接口。但是大部分情况下,User 已经满足。
# PasswordEncoder
这个接口类是密码加密相关,它只有三个方法可以实现:
public interface PasswordEncoder {
/**
* 密码加密,解析规则看实现类的设计
*/
String encode(CharSequence rawPassword);
/**
* 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。
* 如果密码匹配,则返回 true;反之返回 false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
*/
boolean matches(CharSequence rawPassword, String encodedPassword);
/**
* 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回false。
* 默认返回 false。
*/
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
你可以自定义类实现该接口,然后写自己的密码加密和解密规则,但是官方也提供了多个加密规则,而官方最推荐的密码解析器是 BCryptPasswordEncoder
类,我们平时也直接用这个密码解析器。
BCryptPasswordEncoder
是对 bcrypt 强散列方法的具体实现。是基于 Hash 算法实现的单向加密。可以通过 strength 控制加密强度,默认 10.
@Test
public void test01(){
// 创建密码解析器
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
// 对密码进行加密
String enPassword = bCryptPasswordEncoder.encode("kele");
// 打印加密之后的数据
System.out.println("加密之后数据:\t" + enPassword);
//判断原字符加密后和加密之前是否匹配
boolean result = bCryptPasswordEncoder.matches("kele", enPassword);
// 打印比较结果
System.out.println("比较结果:\t" + result); // true
}
2
3
4
5
6
7
8
9
10
11
12
13
# 常用类详解
其实一些常用类已经在 常用接口详解 讲解了,因为这些类都是实现了常用接口,当然属于常用类啦。
# UsernamePasswordAuthenticationToken
这个类很重要,它仅仅有两个属性,principal
用户名、credentials
密码,所以我们需要使用它来存储我们的用户名和密码。
当然,如果你不仅仅需要用户名和密码,你可以自定义类继承它,手动添加用户名、密码以及其他的属性。
# User
该类实现了 UserDetails 接口,具体作用看 常用接口详解。
它和 UsernamePasswordAuthenticationToken
的区别:前者是封装用户大部分信息,如用户名、密码、是否过期等;后者仅仅封装用户名和密码,存入上下文,以后用户访问资源,则冲上下文获取后者来判断是否权限。