eladmin——认证流程

一、前言

eladmin采用Spring Security + JWT作为安全框架。因此我们先了解一下Spring Security的认证流程。

二、Spring Security 认证流程

这块内容网上资料较多,可参考:

这里借用一下毕竟尹稳健老哥梳理的Spring Security认证流程图作为参考。

三、 eladmin 认证流程

类比Spring Security的认证流程,在eladmin中的落地实践: 大致流程如图所示,和Spring Security大同小异,主要区别是多了个TokenFilter少了UsernamePasswordAuthenticationFilter。由于eladmin通过SpringSecurityConfig自定义了认证流程,默认的UsernamePasswordAuthenticationFilter失效了。UsernamePasswordAuthenticationFilter的主要作用其实就是把用户名密码封装为UsernamePasswordAuthenticationToken对象,并传给身份验证管理器进行身份验证,这个步骤在eladmin中写在了AuthorizationController的登入接口中,如下图:

下图是eladmin的授权认证模块。接下来我们从TokenFilter开始抽丝剥茧一步一步分析下面这些类是如何搭配使用的。

3.1 TokenFilter(重要)

TokenFilter:顾名思义token的过滤器,它继承了GenericFilterBean成为了过滤器链中的一环。

1. GenericFilterBean

GenericFilterBean 是 Spring Framework 中的一个抽象类,用于实现自定义的 Servlet 过滤器,GenericFilterBean 提供了一个方便的基类,使得创建自定义过滤器变得简单,只需要继承 GenericFilterBean 并实现 doFilter 方法即可。

2. TokenProvider

Token提供者,提供了下列方法用于管理Token:

  • createToken(): 创建Token 设置永不过期,Token 的时间有效性转到Redis 维护。这样可以灵活地管理用户的登录状态,包括主动注销、强制下线等操作。
  • getAuthentication(String token): 依据Token 获取鉴权信息。值得注意的是该方法的返回值:
java 复制代码
Authentication getAuthentication(String token) {
    Claims claims = getClaims(token);
    User principal = new User(claims.getSubject(), "******", new ArrayList<>());
    // 这个构造方法会执行super.setAuthenticated(true)即不需要再次认证。
    return new UsernamePasswordAuthenticationToken(principal, token, new ArrayList<>());
}
  • checkRenewal(String token):处理Token续期,更新redis中token的过期时间。其中SecurityProperties类,该类记录了JWT的参数配置。通过@ConfigurationProperties从yml配置文件中读取,既能解决硬编码问题,也能实现灵活配置。
  • getToken(HttpServletRequest request):从请求头中获取token。
  • loginKey(String token):进一步封装token,作为用户登入的key存在redis中。
补充:InitializingBean

TokenProvider实现了InitializingBean接口。InitializingBean是Spring提供的拓展性接口,InitializingBean接口为bean提供了属性初始化后的处理方法,它只有一个afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。和它功能类似的还有@PostConstruct。在此处,TokenProvider类的afterPropertiesSet方法,对jwtParser,jwtBuilder两个属性做了初始化。

3. SecurityProperties

用于做Jwt参数的配置类。在application.yml中进行如下配置:

yml 复制代码
#jwt
jwt:
  header: Authorization
  # 令牌前缀
  token-start-with: Bearer
  # 必须使用最少88位的Base64对该令牌进行编码
  base64-secret: ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
  # 令牌过期时间 此处单位/毫秒 ,默认2小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html
  token-validity-in-seconds: 7200000
  # 在线用户key
  online-key: "online-token:"
  # 验证码
  code-key: "captcha-code:"
  # token 续期检查时间范围(默认30分钟,单位默认毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期
  detect: 1800000
  # 续期时间范围,默认 1小时,这里单位毫秒
  renew: 3600000

然后在ConfigBeanConfiguration中注入SecurityProperties bean对象。

java 复制代码
/**
 * @apiNote 配置文件转换Pojo类的 统一配置 类
 * @author: liaojinlong
 * @date: 2020/6/10 19:04
 */
@Configuration
public class ConfigBeanConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "login")
    public LoginProperties loginProperties() {
        return new LoginProperties();
    }

    @Bean
    @ConfigurationProperties(prefix = "jwt")
    public SecurityProperties securityProperties() {
        return new SecurityProperties();
    }
}

4. OnlineUserService

在线用户服务,提供了下列方法用于管理在线用户信息:

  • save(JwtUserDto jwtUserDto, String token, HttpServletRequest request):把认证通过的用户信息记录到redis中,记录的信息有"用户名","token","浏览器","IP","登入时间"等,这些信息都封装在OnlineUserDto中。其中token使用对称加密算法,再次加密了一次,提高安全性。
  • getAll(String username):查询某个账号下的所有在线登入信息。当不传username时,获取所有账号下的在线登入信息。
  • kickOutForUsername(String username):踢掉之前登入的用户。可选,如果用户禁止多人登入同一个账号的话就会调用这个方法。值得注意的是 该方法上加了@Ansy注解,意味着该方法将被异步执行,而未在项目中找到继承AsyncConfigurer的类,意味着用的是默认线程池SimpleAsyncTaskExecutor

5. UserCacheManager

管理用户缓存用于减少数据库查询,提供了下列方法:

  • addUserCache(String userName, JwtUserDto user): 添加JwtUserDto到redis中。
  • getUserCache(String userName):从redis中获取缓存的JwtUserDto
  • cleanUserCache(String userName):删除缓存的JwtUserDto

3.2 UserDetailsServiceImpl(重要)

UserDetailsServiceImpl 是 Spring Security 框架中的一个实现了 UserDetailsService 接口的类。它主要负责从数据源(如数据库)中加载用户信息,并将这些信息封装成一个实现了 UserDetails 接口的对象,以供 Spring Security 在进行身份认证和授权时使用。我们看看eladmin中是如何实现UserDetailsService 接口的:

1. UserService

UserService 用于管理eladmin的系统用户,提供不限于如下方法:

  • updatePass(String username, String encryptPassword):更新用户密码
  • create(User resources):创建用户
  • findById(long id):根据ID查询用户。值得注意的是该方法上添加了@Cacheable(key = "'id:' + #p0")注解,意味着每次查询到的结果都会缓存起来,这样就能提高下次查询效率了。在eladmin项目中RedisConfig继承CachingConfigurerSupport,重写了keyGenerator自定义了缓存key生成策略。

UserDetailsServiceImplUserService用于从数据源中查询用户是否存在。

2. RoleService

RoleService用于管理eladmin中的角色信息(eladmin权限控制采用 RBAC 思想,这一点在鉴权流程中细说),提供不限于如下方法:

  • create(Role resources):创建角色
  • void delete(Set ids):删除角色
  • findByUsersId(Long id):查询某个用户有哪些角色
  • updateMenu(Role resources, RoleDto roleDTO):修改绑定的菜单

UserDetailsServiceImplRoleService用于查询用户权限信息。并封装到JwtUserDto类中。如下图:

其中mapToGrantedAuthorities()方法就是把形如user:list、roles:list这样的用户权限信息封装进List<AuthorityDto>集合中。

3. DataService

DataService数据权限服务类,只提供了一个方法:

  • getDeptIds(UserDto user): 查询用户的数据权限。

3.3 JwtAuthenticationEntryPoint

该类继承了AuthenticationEntryPoint,处理经身份验证的用户尝试访问安全的资源时返回的响应。 补充:当用户没有提供任何凭据尝试访问安全的资源时,会调用AuthenticationEntryPoint的commence()方法,该方法负责发送一个401 Unauthorized的响应给用户,提示用户需要进行身份验证才能继续访问该资源。

3.4 AuthorizationController

AuthorizationController作为认证接口层,提供了登入,登出、获取验证码等接口。这个类中的成员变量前文都已介绍,只有AuthenticationManagerBuilder第一次出场。

1.AuthenticationManagerBuilder

AuthenticationManagerBuilder是Spring Security提供的一个配置类,用于构建和配置AuthenticationManager(身份验证管理器)。AuthenticationManagerBuilder提供了一系列方法,可以用于定义如何进行用户身份验证。它允许开发人员通过链式调用来配置不同的身份验证方式,例如内存验证、数据库验证、LDAP验证等。

以下是一些常用的AuthenticationManagerBuilder方法:

  1. inMemoryAuthentication(): 配置基于内存的用户认证。可以使用该方法添加用户、密码和角色信息。
  2. jdbcAuthentication(): 配置基于JDBC的用户认证。可以使用该方法指定数据源、查询用户信息的SQL语句以及查询用户权限的SQL语句。
  3. userDetailsService(): 指定自定义的UserDetailsService实现类,用于加载用户信息。可以使用该方法自定义用户信息的加载逻辑,例如从数据库、LDAP等获取用户信息。
  4. authenticationProvider(): 添加自定义的身份验证提供者。可以使用该方法将自定义的AuthenticationProvider实现类添加到身份验证流程中。

通过使用这些方法,可以根据应用程序的需求来配置合适的身份验证方式。在配置完成后,AuthenticationManagerBuilder会构建出一个AuthenticationManager实例,用于处理身份验证请求。

总结

上述类相互配合,便完成了eladmin的认证流程。接下来将通过流程图展示这些类之间是如何协调,完成认证的:

相关推荐
向前看-4 小时前
验证码机制
前端·后端
超爱吃士力架5 小时前
邀请逻辑
java·linux·后端
AskHarries7 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
pubuzhixing8 小时前
开源白板新方案:Plait 同时支持 Angular 和 React 啦!
前端·开源·github
isolusion8 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp8 小时前
Spring-AOP
java·后端·spring·spring-aop
忆源9 小时前
3.3.2.3 开源项目有锁队列实现--魔兽世界tinityCore
开源
鹏大师运维9 小时前
聊聊开源的虚拟化平台--PVE
linux·开源·虚拟化·虚拟机·pve·存储·nfs
TodoCoder9 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
凌虚10 小时前
Kubernetes APF(API 优先级和公平调度)简介
后端·程序员·kubernetes