Security 权限控制的基本流程
Security的认证流程
先明白几个概念(在源码中找下面的方法)
1. AbstractAuthenticationToken抽象类, 令牌的基类
- 其中的方法和属性
authenticated
:为true表示认证,为false表示未认证,用户有没有认证就是设置这个属性的值authorities
:权限信息getPrincipal()
:返回认证主体(通常是用户信息)。getCredentials():返回认证凭证(例如密码、验证码等)。
实现AbstractAuthenticationToken抽象类的具体类
- 其中的主要的方法和属性
- 构造方法:参数是用户名、密码的构造方法,方法中会设置
authenticated
未为认证状态也就是false
- 构造方法:参数是用户名、密码、权限的构造方法,方法中会设置
authenticated
未为认证状态也就是true
static authenticated()
:调用构造方法,返回一个认证的对象
2. AuthenticationProvider接口,它的实现类就是认证者,其中定义了认证的逻辑
- 其中主要的属性和方法
authenticate()
:通过验证用户名和密码,调用AbstractAuthenticationToken
类的static authenticated()
方法,用户就通过认证- 在
authenticate()
方法中使用了UserDetailsService
的实现类,而这个实现类会调用它的loadUserByUsername
方法,在这个方法中会去数据库查询用户的所有信息,并返回,继续在authenticate()
中认证
- 在
supports()
: 这个认证者支持认证那一类AbstractAuthenticationToken
的实现类
3. AuthenticationManager :Provider的管理者,所有认证者都由他管理
- 作用:选择一个合适的
Provider
执行它里面的authenticate()
- 它只有一个抽象方法:
Authentication authenticate()
- 如何选择合适的
Provider
:
以下代码中的
Token
指的是AbstractAuthenticationToken
的实现类,Provider
指的是AuthenticationProvider
的实现类,不然代码过长
- 当执行
authenticationManager.authenticate(Token)
方法去认证Token
时,authenticationManager
会遍历每一个注册的Provider
从它的supports()
中找到可以认证这个Token
的Provider
Security原生的令牌和认证,执行流程
- 如果使用
Security
提供的登录方式,制作令牌和调用认证都由UsernamePasswordAuthenticationFilter
过滤器处理 - 用户输入用户名、密码点击确定后
UsernamePasswordAuthenticationFilter
过滤器获得用户名和密码,调用Authentication attemptAuthentication
方法制造了一个UsernamePasswordAuthenticationToken
令牌并交给认证者 - 调用
DaoAuthenticationProvider
中的authenticate
方法,这个方法在它的父类AbstractUserDetailsAuthenticationProvider
中,在这个方法调用了以下方法:retrieveUser()
抽象方法,由具体类DaoAuthenticationProvider
实现,作用:返回从数据库查询的用户信息additionalAuthenticationChecks()
抽象方法,由DaoAuthenticationProvider
实现,作用:用户输入的密码与数据库密码做比对createSuccessAuthentication()
抽象方法,由DaoAuthenticationProvider
实现,作用:检查密码是否泄露,并调用父类认证成功的方法createSuccessAuthentication()
具体方法,作用:1. 将令牌UsernamePasswordAuthenticationToken
设置为认证通过状态;2. 执行getAuthorities()
方法设置用户拥有的权限,用于cotroller中鉴权
有来项目认证流程
- 以下是使用用户名和密码登录的流程,因为没有使用使用默认的配置,原来接收用户名和密码的过滤器就无法获取到用户名和密码
- 用户发送用户名和密码,
controller
接收
// package com.youlai.boot.shared.auth.controller;
@RequestMapping("/api/v1/auth")
public class AuthController {@PostMapping("/login")public Result<AuthenticationToken> login( @RequestParam String username,@RequestParam String password) {AuthenticationToken authenticationToken = authService.login(username, password);return Result.success(authenticationToken);}
}
- controller调用service:
AuthenticationToken authenticationToken = authService.login(username, password);
// package com.youlai.boot.shared.auth.service.impl;
@Service
public class AuthServiceImpl implements AuthService {@Overridepublic AuthenticationToken login(String username, String password) {// 1. 创建用于密码认证的令牌(未认证)UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(username.trim(), password);// 2. 执行认证(认证中)Authentication authentication = authenticationManager.authenticate(authenticationToken);// 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证)AuthenticationToken authenticationTokenResponse =tokenManager.generateToken(authentication);SecurityContextHolder.getContext().setAuthentication(authentication);return authenticationTokenResponse;}
}
new UsernamePasswordAuthenticationToken(username.trim(), password)
:生成令牌,其中主要的是用户输入的用户名、密码和认证成功与否的标志Authentication authentication = authenticationManager.authenticate(authenticationToken);
AuthenticationManager
是 Spring Security 提供的一个接口,负责选择一个合适的Provider
去验证- 验证就是调用
Provider
的authenticate
方法。所以在执行authenticationManager.authenticate()
时其实执行的是Provider.authenticate()
- 验证就是调用
Provider.authenticate()
方法的作用:- 获取到用户的数据库数据
- 用户输入的密码与数据库密码做比对
- 比对成功后将
authenticationToken
置为成功状态,并将数据库查询到的权限生成权限集合用于权限校验
SysUserDetailsService.loadUserByUsername
方法userService.getUserAuthInfo
:从数据库获取用户详细信息(主要是密码、权限)
// package com.youlai.boot.core.security.service;
@Service
public class SysUserDetailsService implements UserDetailsService {private final UserService userService;// 根据用户名获取用户信息@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {try {UserAuthInfo userAuthInfo = userService.getUserAuthInfo(username); // UserAuthInfo 数据表对应的实体类if (userAuthInfo == null) {throw new UsernameNotFoundException(username);}return new SysUserDetails(userAuthInfo);} catch (Exception e) {// 记录异常日志log.error("认证异常:{}", e.getMessage());// 抛出异常throw e;}}
}
- 实现了
UserDetails
接口的实体类- 用户的信息和权限就全有了
// package com.youlai.boot.core.security.model;
@Data
@NoArgsConstructor
public class SysUserDetails implements UserDetails {... // 省略了很多属性// 用户角色权限集合private Collection<SimpleGrantedAuthority> authorities;/*** 构造函数:根据用户认证信息初始化用户详情对象** @param user 用户认证信息对象 {@link UserAuthInfo}*/public SysUserDetails(UserAuthInfo user) {this.userId = user.getUserId();this.username = user.getUsername();this.password = user.getPassword();this.enabled = ObjectUtil.equal(user.getStatus(), 1);this.deptId = user.getDeptId();this.dataScope = user.getDataScope();// 初始化角色权限集合this.authorities = CollectionUtil.isNotEmpty(user.getRoles())? user.getRoles().stream()// 角色名加上前缀 "ROLE_",用于区分角色 (ROLE_ADMIN) 和权限 (user:add).map(role -> new SimpleGrantedAuthority(SecurityConstants.ROLE_PREFIX + role)).collect(Collectors.toSet()): Collections.emptySet();}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorities;}
}