简介

在权限管理框架中,往往离不开认证 (authentication)授权(authorization) 这两个概念,这两个单词"很像",表示的意思却大相径庭。

  • 认证:对用户信息进行身份认证 (Who are you? 你是谁?)

  • 授权:给认证过的用户赋予访问资源的权限 (What can you do?你可以做什么?)

本文浅析的java权限管理框架 spring security 也是围绕这两个概念展开的,它依托于同spring生态良好配合,完整全面的功能模块,收获了一大批开发人员的青睐。

最简单的应用

为了方便代码的调试,我们借助spring boot搭建一个简单的权限管理项目。

首先,我们使用maven来管理包引用,pom.xml 文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <name>security演示项目</name>
    <groupId>xin.sunce</groupId>
    <artifactId>spring-boot-security</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
    </parent>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>
    
</project>

创建启动类并开发一个简单的Rest接口

@EnableWebMvc
@RestController
@SpringBootApplication
public class Application {


    @GetMapping("/home")
    public String home() {
        return "hello world";
    }


    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

执行Application#main启动应用,由于我们没有做任何的配置,security会使用默认的用户名: user,并生成默认的密码,输出在启动日志中;例如

Using generated security password: dacbfd04-716c-4be2-bba2-f40a2d9ea454

也可以在配置文件中指定用户名密码,配置如下:

spring.security.user.name=root
spring.security.user.password=123456

启动应用后,访问地址 http://127.0.0.1:8080/home 会默认跳转到security默认的登录页面;至此,我们可以一起开启快乐的debug之旅了。

security 概述

假如,你的开发负责人需要你编码一个权限管理的功能;你会如何来实现呢?

很多朋友可能会想到通过 AOPFilter来实现,而security 正是通过 Filter 来实现的。

当初始化spring security时,会创建一个 FilterChainProxy 的过滤器,它是javax.servlet.Filter 子类,因此所有的请求都会经过它。FilterChainProxy 是一个代理类,真正发挥作用的是FilterChainProxy中SecurityFilterChain的各个Filter,而这些Filter也正式security的核心。

基本处理流程如下:

处理流程.png

我们之前说过权限管理的两大核心:认证,授权;这些Filter很重要,但是它们并不直接参与认证,授权;其中认证环节是通过 AuthenticationManager 来完成的,而授权环节是通过AccessDecisionManager 完成的。

认证授权流程.png

认证流程

采用security简单示例的认证成功时序图如下:

认证成功时序图.png

  • UsernamePasswordAuthenticationFilter拦截到登录信息,并将其封装成UsernamePasswordAuthenticationToken,随后尝试认证,
    image

  • 调用 AuthenticationManager的子类ProviderManager#authenticate方法

image

  • DaoAuthenticationProvider是AuthenticationManager的子类调用 UserDetailsService#loadUserByUsername 获取UserDetails 信息

image

  • 获取到UserDetails信息后与请求的User信息做密码匹配校验,校验通过后填充相关权限信息

image.png

  • 返回认证过的UsernamePasswordAuthenticationToken (Authentication 子类)

image.png

  • 将认证成功的 Authentication 放置到 SecurityContextHolder.getContext() 的上下文中

image.png

至此整个认证流程告一段落。

授权流程

同样的授权流程也是通过Filter实现的,我们先看一下时序图,如下:

image.png

  • 认证成功后访问受保护资源,会调用FilterSecurityInterceptor会在doFilter方法中调用invoke()

image.png

  • 调用父类AbstractSecurityInterceptor#beforeInvocation

image.png

  • 从SecurityMetadataSource#getAttributes获取可访问资源

image.png

  • 通过accessDecisionManager#decide 觉得资源是否放行

image.png

  • 其实现类中通过 AccessDecisionVoter 来投票是否放行

image.png

以上便是授权的基本流程。

作者在研读源码的过程中受到 CSDN 博主 借汝之光,得以光明颇多启发 ,https://blog.csdn.net/weixin_44588495/article/details/105907312;万分感谢。

参考资料:

CSDN:
https://blog.csdn.net/weixin_44588495/article/details/105907312

知乎:
https://zhuanlan.zhihu.com/p/100014456

作者是一个技术肥宅,还在不断的学习进步中,十分欢迎各位 杠精 读者指出文中不足之处,欢迎您来留言提问;也欢迎转载,烦请注明出处。