SpringSecurity整合JWT实现RABC权限系统的原理

 Spring Security 整合 JWT 实现前后端分离的鉴权

Spring Security 最核心的两个功能就是验证和鉴权。

3.1 Spring Security 的原理

认证就是验证你是谁,鉴权就是你能干什么。权限系统的设计也必须按照这两步来进行思考和设计。

Spring Security 本质就是一系列的拦截器组成的拦截器链。(如下图)

SpringSecurity整合JWT实现RABC权限系统的原理_第1张图片

3.2 Spring Boot 整合 Spring Security 的实现

3.2.1 认证

我们从上图可以看出,认证跟 Spring Security 提供的 UserPasswordAuthenticationFilter 有关。

我们对这个类的源码进行分析一下。

通过源码我们可以看出,它主要拦截我们的登录请求并拿到我们的登录名和密码,然后生成通过用户名和密码生成一个未认证的 UsernamePasswordAuthenticationToken 对象,等待我们验证。就你登录请求过来,我这个拦截器就会拦截到然后拿到你的用户名、密码,然后封装成一个为认证的东西,方便我后边对你进行认证。

3.2.2 拦截器源码分析

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter
    // 你一按登录就会请求认证
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        // 判断是否为POST方式的请求
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            // 我们请求的登录中获取到用户名和密码
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            // 进一步验证
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
}

这个未认证的 UsernamePasswordAuthenticationToken 的构造方法,我们通过源码就知道它到底是个什么东西。

UsernamePasswordAuthenticationToken 源码:

public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
    //当前登录的用户名具有什么权限,由于刚进行认证,这里的权限我们赋值为空
    super((Collection)null);
    this.principal = principal;
    this.credentials = credentials;
    // 当前登录的用户名是否已通过验证,由于这里还没进行认证,所以我们赋值为为认证
    this.setAuthenticated(false);
}

接着 AuthenticationManager 就会对我们上面的这个 UsernamePasswordAuthenticationToken 来进行认证。但 AuthenticationManager 并不是真正干活的人,真正做事的是 ProviderManager。我们通过源码可以看出 ProviderManager 继承于我的 AuthenticationManager。然后它会选择合适的 Provider 对我们的 UsernamePasswordAuthenticationToken 来进行认证。这个因为是 UsernamePasswordAuthenticationToken,所以选择的是 DaoAuthenticationProvider。

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
    public Authentication authenticate(Authentication authentication) throws AuthenticationException { 
        Class toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();
        Iterator var6 = this.getProviders().iterator();

        while(var6.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var6.next();
            // 1.判断是否有provider支持该Authentication
            if (provider.supports(toTest)) {
                result = provider.authenticate(authentication);
            }
    }
}

开始对登录请求进行认证。去调用自己实现的 UserDetailsService,返回 UserDetails。

对 UserDetails 的信息进行校验,主要是帐号是否被冻结,是否过期等对密码进行检查,这里调用了 PasswordEncoder 检查 UserDetails 是否可用。这个时候就返回经过认证的 Authentication。

成功认证的话就会去调用我的 successHandler 返回,失败就去调用我的 failHandler。

3.2.3 认证的总体思路流程

SpringSecurity整合JWT实现RABC权限系统的原理_第2张图片

根据上面的思路,整个代码来具体的讲解一下是如何实现的。

首先我们要自定义 DaoAuthenticationProvider 对我们的 UsernamePasswordAuthenticationToken 来进行认证。我们在 SecurityConfig 中来进行定义(这里需要对用户名和密码校验)。

UserDetailsService:

SpringSecurity整合JWT实现RABC权限系统的原理_第3张图片

SpringSecurity整合JWT实现RABC权限系统的原理_第4张图片

认证成功和认证失败的处理器:

SpringSecurity整合JWT实现RABC权限系统的原理_第5张图片

认证成功生成 token 并返回:

SpringSecurity整合JWT实现RABC权限系统的原理_第6张图片

认证:

SpringSecurity整合JWT实现RABC权限系统的原理_第7张图片

拿到我的用户名,看是否有这个用户,并返回继承了 UserDetails 格式的 User(此时还是未认证):

SpringSecurity整合JWT实现RABC权限系统的原理_第8张图片

返回 jwtUser:

SpringSecurity整合JWT实现RABC权限系统的原理_第9张图片

拿到密码对密码进行校验:

SpringSecurity整合JWT实现RABC权限系统的原理_第10张图片

加入认证通过,调用我的 successHandiler 处理类:

SpringSecurity整合JWT实现RABC权限系统的原理_第11张图片

并将已认证对象 Authentication(这个类有用户名,权限等信息)放入到我的 SecurityContextHolder 上下文中。这样我们就可以在需要用到的时候从 SecurityContextHolder 中取出这个认证对象就好了。

3.2.4 认证前先校验头部是否有 token

如果不是调用的不是 login 登录接口,我们就需要检验头部是否有 token,就是第一次登录后我们返回的认证标识。

那么如何实现呢?因为认证是经过我们的 UsernamePasswordAuthenticationFilter 拦截器,那么我们可以在这个拦截器前先添加我的自定 JWT 拦截器来校验头部是否有 token。如果有就进行认证。

自定义的 JWT 拦截器:

SpringSecurity整合JWT实现RABC权限系统的原理_第12张图片

将拦截器添加到我的 UsernamePasswordAuthenticationFilter 拦截器以实现先校验头部是否有 token,并进行认证。

SpringSecurity整合JWT实现RABC权限系统的原理_第13张图片

3.2.5 鉴权

认证完后就要进行鉴权了。就是判断当前用户是否有访问请求的 url 的权限。

思路:通过拦截器来判断当前的请求的 url 是否哪些角色能够访问以及当前用户是否有这些角色。如果匹配不上就鉴权失败,匹配上就鉴权成功。

鉴权我们主要借助于 Spring Security 的 FilterSecurityInterceptor 拦截器来进行。

根据资源表和资源角色表拿出访问当前 url 需要的角色:

SpringSecurity整合JWT实现RABC权限系统的原理_第14张图片

SpringSecurity整合JWT实现RABC权限系统的原理_第15张图片

根据用户表和角色表将当前用户拥有的角色拿出来(用户的角色我们已经在认证的时候就已经拿出来了)。

SpringSecurity整合JWT实现RABC权限系统的原理_第16张图片

SpringSecurity整合JWT实现RABC权限系统的原理_第17张图片

进行鉴权判断(上图中认证对象角色是我们在认证的时候已经放入的了。

这里放入权限。这里的 SecurityContexthold 是一个安全的上下文,我们将通过上面认证的 Authentication 对象会放在这里面)。

SpringSecurity整合JWT实现RABC权限系统的原理_第18张图片

最后将我们自定义的拦截器添加到 FilterSecurityInceptor 之前就可以进行认证。

SpringSecurity整合JWT实现RABC权限系统的原理_第19张图片

JWT 的封装:

SpringSecurity整合JWT实现RABC权限系统的原理_第20张图片

 

 RABC 权限系统前置知识与项目整体把握

2.1 权限和角色和用户的关系

在基于角色的 RABC 权限系统中,我们通过资源与角色的绑定,来确定哪些角色拥有哪些资源,然后在通过角色与用户的绑定来确定哪些用户是什么角色,进而确定用户有哪些资源的访问权限。

SpringSecurity整合JWT实现RABC权限系统的原理_第21张图片

所以我们在设计权限系统的表的时候就需要创建用户表、角色表、用户角色表、资源表、资源角色表。

用户表

SpringSecurity整合JWT实现RABC权限系统的原理_第22张图片

角色表

SpringSecurity整合JWT实现RABC权限系统的原理_第23张图片

资源表

SpringSecurity整合JWT实现RABC权限系统的原理_第24张图片

用户角色表

SpringSecurity整合JWT实现RABC权限系统的原理_第25张图片

角色资源表

SpringSecurity整合JWT实现RABC权限系统的原理_第26张图片

在上面我用红框框出来的是所有表的一个共性,在实际项目中就可以把它封装成一个 BaseEntity 类,这个类会出现在我的项目的 core 子模块下。对应地我们在执行 Dao 的插入、删除操作的时候,也是需要将对共同部分的操作封装成一个基础操作,然后我们在编写 dao、service、controller 的时候,对应的继承才是最正确的。

你可能感兴趣的:(springboot)