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的认证流程。接下来将通过流程图展示这些类之间是如何协调,完成认证的:

相关推荐
冬奇Lab2 小时前
每日一个开源项目(第152篇):SAG - 用 SQL JOIN 代替 PageRank 做多跳 RAG 检索
人工智能·开源
IT_陈寒5 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
机智的张尼玛5 小时前
我基于 Kotlin Multiplatform 实现了一套跨平台的弱网离线同步引擎
android·开源·kotlin
易协同低代码6 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo6 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志7 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..7 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects7 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰7 小时前
一些基础和问题解决
后端
陈随易7 小时前
前端项目部署只要30秒
前端·后端·程序员