权限管理中的相关概念
主体 principal
- 使用系统的用户或设备或从其他系统远程登录的用户等等。简单说就是谁使用系 统谁就是主体。
认证 authentication
- 权限管理系统确认一个主体的身份,允许主体进入系统。简单说就是“主体”证 明自己是谁。 笼统的认为就是以前所做的登录操作。
授权 authorization
- 将操作系统的“权力”“授予”“主体”,这样主体就具备了操作系统中特定功 能的能力。 所以简单来说,授权就是给用户分配权限。
SpringSecurity的基本原理
- SpringSecurity 本质是一个过滤器链,它采用的是责任链的设计模式,就是通过一个又一个过滤器最终通过认证的,下面对各个过滤器进行说明:
WebAsyncManagerIntegrationFilter
:将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。SecurityContextPersistenceFilter
:在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder 中的信息清除,例如在Session中维护一个用户的安全信息就是这个过滤器处理的。HeaderWriterFilter
:用于将头信息加入响应中。- CsrfFilter:用于处理跨站请求伪造。
LogoutFilter
:用于处理退出登录。UsernamePasswordAuthenticationFilter
:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改。DefaultLoginPageGeneratingFilter
:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。BasicAuthenticationFilter
:检测和处理 http basic 认证。RequestCacheAwareFilter
:用来处理请求的缓存。SecurityContextHolderAwareRequestFilter
:主要是包装请求对象request。AnonymousAuthenticationFilter
:检测 SecurityContextHolder 中是否存在- Authentication 对象,如果不存在为其提供一个匿名 Authentication。
SessionManagementFilter
:管理 session 的过滤器ExceptionTranslationFilter
:处理 AccessDeniedException 和AuthenticationException 异常。FilterSecurityInterceptor
:可以看做过滤器链的出口。RememberMeAuthenticationFilter
:当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。
- 比较关键的有三个过滤器:
FilterSecurityInterceptor
:是一个方法级的权限过滤器, 基本位于过滤链的最底部。super.beforeInvocation(fi) 表示查看之前的 filter 是否通过。 fi.getChain().doFilter(fi.getRequest(), fi.getResponse());表示真正的调用后台的服务。- ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常。
- UsernamePasswordAuthenticationFilter :对/login 的 POST 请求做拦截,校验表单中用户 名,密码。这里要求登录发出的请求必须是Post请求,并且请求的参数一个是username,一个是password,参数的键值不允许更改,否则后端将接受不到用户名和密码。
关键的接口
UserDetailService
- 当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中 账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。
- 如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。接口定义如下:
1 | public interface UserDetailsService { |
- 使用时,我们只需要将我们自定义的
UserService
实现UserDetailService
接口,并实现loadUserByUsername
方法,该方法返回null时,代表认证失败,返回UserDetails
实现类的对象时,代表认证成功。
UserDetail
- UserDetails即为用户主体,它的定义如下:
1 | public interface UserDetails extends Serializable { |
- 使用UserDetails有三种方式:
- 我们可以直接使用它的实现类User
- 我们可以自定义User来实现UserDetails,然后使用自定义的User。
- 我们可以使用自定义的User,然后将自定义的User转换为已有的User。
PasswordEncoder
- 该接口表示密码的加密器,即使用某种编码对密码进行加密,它的定义如下:
1 | public interface PasswordEncoder { |
PasswordEncoder
的一个常用的实现类是BCryptPasswordEncoder
,它使用BCrypt强哈希函数的PasswordEncoder实现。下面是测试的一个例子
1 |
|
例子
添加依赖
1 | <dependency> |
创建数据库
- 准备用户表、角色表和用户角色关联表。配置数据源这部分忽略。
1 | create table sys_user( |
编写配置类
- 下面的各种页面,可以根据需要自行编写,有一个地方需要注意的是:当url同时制定了权限认证和角色认证的时候,优先使用角色认证,如果角色认证不通过,即使权限是对的,也会认证失败。
1 |
|
编写html界面
- 这里需要注意的是,提交表单的请求必须是post请求,提交表单的参数名必须是username和password。
1 | <!--login.html--> |
1 | <!--failure.html--> |
1 | <!--unauth.html--> |
编写UserDetailsService的实现类
- 这里可以根据需要结合不同的数据库访问技术,比如JDBC、MyBatis等,这里使用的是MyBatisPlus。
1 |
|
编写一个测试的控制器
1 | "/test") ( |
- 最后运行测试。