【SpringSecurity】基础入门

在这里插入图片描述

目录

  • 权限管理
    • 什么是权限管理
    • 认证
    • 授权
    • 权限管理解决方案
      • Shiro
      • 开发者自定义
      • Spring Security
  • Spring Security
    • 特性
    • Spring、Spring Boot 和 Spring Security 三者的关系
    • 整体架构
      • 1.认证
        • AuthenticationManager
        • Authentication
        • SecurityContextHolder
      • 2.授权
        • AccessDecisionManager
        • AccessDecisionVoter
        • ConfigAttribute
    • 入门案例
      • 1.引入依赖
      • 2.创建控制器(Controller)
      • 3.启动项目
    • SpringSecurity认证
      • 1.数据库准备
      • 2.原理解析
      • 3.认证方式
        • 3.1 HttpBasic认证
        • 3.2 formLogin登录认证模式
      • 4.表单认证
        • 4.1自定义表单登录页面
          • 问题一: localhost将您重定向次数过多
          • 问题二: 访问login.html 报404错误
          • 问题三: 访问login.html 后发现页面没有相关样式
        • 4.2.表单登录
          • 页面源码(DEMO)
          • Controller源码
        • 4.3基于数据库实现认证功能
          • 4.3.1编写MyUserDetailsService并实现UserDetailsService接口,重写loadUserByUsername方法
          • 4.3.2在SecurityConfiguration配置类中指定自定义用户认证
          • 4.3.3密码加密认证
            • BCrypt算法介绍
            • 在项目中使用BCrypt
          • 4.3.4获取当前登录用户
            • 传统实现
            • Spring Security 还提供了2种方式可以获取.
            • remember me 记住我
            • 前端代码
            • 后台代码
            • 再次完成登录功能.
          • 4.3.5Cookie窃取伪造演示
          • 4.3.6安全验证
          • 4.3.7自定义登录成功处理和失败处理
            • 代码实现登录成功或失败的自定义处理
            • 异步用户登录实现
          • 4.3.8退出登录
    • SpringSecurity授权
      • 1.内置权限表达式
      • 2.url安全表达式
        • 2.1设置url访问权限
        • 2.MyAccessDeniedHandler自定义权限不足类
        • 2.3设置用户对应的角色权限
      • 3.在Web 安全表达式中引用自定义Bean授权
        • 3.1定义自定义授权类
        • 3.2配置类
        • 3.3携带路径变量
      • 4.Method安全表达式
        • 4.1开启方法级别的注解配置
        • 4.2在方法上使用注解
          • @ProAuthorize : 注解适合进入方法前的权限验证
          • @PostAuthorize:
          • @PreFilter: 可以用来对集合类型的参数进行过滤, 将不符合条件的元素剔除集合
          • @PostFilter: 可以用来对集合类型的返回值进行过滤, 将不符合条件的元素剔除集合
      • 基于数据库的RBAC数据模型的权限控制
        • 1.RBAC权限模型简介
        • 2.RBAC的演化进程
          • 用户与权限直接关联
          • 用户与角色关联
          • 基于RBAC设计权限表结构
        • 3.基于Spring Security 实现RBAC权限管理
          • 3.1动态查询数据库中用户对应的权限
          • 3.2给登录用户授权
          • 3.3设置访问权限

权限管理

什么是权限管理

  • 基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。

  • ​权限管理包括用户身份认证鉴权(授权)两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。

认证

  • 身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。
  • 对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。

授权

  • 授权,即访问控制,控制谁能访问哪些资源。
  • 主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的

权限管理解决方案

Shiro

Shiro 本身是一个老牌的安全管理框架,有着众多的优点,例如轻量、简单、易于集成、可以在JavaSE环境中使用等。不过,在微服务时代,Shiro 就显得力不从心了,在微服务面前和扩展方面,无法充分展示自己的优势。

开发者自定义

也有很多公司选择自定义权限,即自己开发权限管理。但是一个系统的安全,不仅仅是登录和权限控制这么简单,我们还要考虑种各样可能存在的网络政击以及防彻策略,从这个角度来说,开发者白己实现安全管理也并非是一件容易的事情,只有大公司才有足够的人力物力去支持这件事情。

Spring Security

Spring Security,作为spring 家族的一员,在和 Spring 家族的其他成员如 Spring Boot Spring Clond等进行整合时,具有其他框架无可比拟的优势,同时对 OAuth2 有着良好的支持,再加上Spring Cloud对 Spring Security的不断加持(如推出 Spring Cloud Security ),让 Spring Securiy 不知不觉中成为微服务项目的首选安全管理方案。

Spring Security

  • 官网:https://spring.io/projects/spring-security
  • Spring Security是一个功能强大、可高度定制的身份验证和访问控制框架。它是保护基于Spring的应用程序的事实标准。
  • Spring Security是一个面向Java应用程序提供身份验证和安全性的框架。与所有Spring项目一样,Spring Security的真正威力在于它可以轻松地扩展以满足定制需求。
  • Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC(Inversion of Control 控制反转),DI(Dependency Injection 依赖注入)和 AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

特性

  • 对身份验证和授权的全面且可扩展的支持
  • 防御会话固定、点击劫持,跨站请求伪造等攻击
  • 支持 Servlet API 集成
  • 支持与 Spring Web MVC 集成

Spring、Spring Boot 和 Spring Security 三者的关系

在这里插入图片描述

整体架构

在的架构设计中,认证和授权 是分开的,无论使用什么样的认证方式。都不会影响授权,这是两个独立的存在,这种独立带来的好处之一,就是可以非常方便地整合一些外部的解决方案。
在这里插入图片描述

1.认证

AuthenticationManager

在Spring Security中认证是由AuthenticationManager接口来负责的,接口定义为:

public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
//返回 Authentication 表示认证成功
//返回 AuthenticationException 异常,表示认证失败。

AuthenticationManager 主要实现类为 ProviderManager,在ProviderManager 中管理了众多AuthenticationProvider 实例。在一次完整的认证流程中,Spring Security 允许存在多个AuthenticationProvider ,用来实现多种认证方式,这些 AuthenticationProvider 都是由ProviderManager 进行统一管理的。
在这里插入图片描述

Authentication

认证以及认证成功的信息主要是由 Authentication 的实现类进行保存的,其接口定义为:

public interface Authentication extends Principal, Serializable {Collection<? extends GrantedAuthority> getAuthorities();Object getCredentials();Object getDetails();Object getPrincipal();boolean isAuthenticated();void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
//getAuthorities 获取用户权限信息
//getCredentials 获取用户凭证信息,一般指密码
//getDetails 获取用户详细信息
//getPrincipal 获取用户身份信息,用户名、用户对象等
//isAuthenticated 用户是否认证成功
SecurityContextHolder
  • SecurityContextHolder 用来获取登录之后用户信息。
  • Spring Security 会将登录用户数据保存在 Session 中。
  • 但是,为了使用方便,Spring Security在此基础上还做了一些改进,其中最主要的一个变化就是线程绑定。
  • 当用户登录成功后,Spring Security 会将登录成功的用户信息保存到 SecurityContextHolder 中。
  • SecurityContextHolder 中的数据保存默认是通过ThreadLocal 来实现的,使用 ThreadLocal 创建的变量只能被当前线程访问,不能被其他线程访问和修改,也就是用户数据和请求线程绑定在一起。
  • 当登录请求处理完毕后,Spring Security 会将 SecurityContextHolder 中的数据拿出来保存到 Session 中,同时将 SecurityContexHolder 中的数据清空。
  • 以后每当有请求到来时,Spring Security 就会先从 Session 中取出用户登录数据,保存到 SecurityContextHolder 中,方便在该请求的后续处理过程中使用,同时在请求结束时将 SecurityContextHolder 中的数据拿出来保存到 Session 中,然后将 Security SecurityContextHolder 中的数据清空。
  • 这一策略非常方便用户在 Controller、Service 层以及任何代码中获取当前登录用户数据。

2.授权

当完成认证后,接下来就是授权了。在 Spring Security 的授权体系中,有两个关键接口

AccessDecisionManager

访问决策管理器,用来决定此次访问是否被允许。
在这里插入图片描述

AccessDecisionVoter

访问决定投票器,投票器会检查用户是否具备应有的角色,进而投出赞成、反对或者弃权票。
在这里插入图片描述AccesDecisionVoter 和 AccessDecisionManager 都有众多的实现类,在 AccessDecisionManager 中会换个遍历 AccessDecisionVoter,进而决定是否允许用户访问,因而 AaccesDecisionVoter 和 AccessDecisionManager 两者的关系类似于 AuthenticationProvider 和 ProviderManager 的关系。

ConfigAttribute

用来保存授权时的角色信息
在这里插入图片描述在 Spring Security 中,用户请求一个资源(通常是一个接口或者一个 Java 方法)需要的角色会被封装成一个 ConfigAttribute 对象,在 ConfigAttribute 中只有一个 getAttribute方法,该方法返回一个 String 字符串,就是角色的名称。一般来说,角色名称都带有一个 ROLE_ 前缀,投票器 AccessDecisionVoter 所做的事情,其实就是比较用户所具各的角色和请求某个
资源所需的 ConfigAtuibute 之间的关系。

入门案例

1.引入依赖

当我们引入Spring Security依赖时,所有接口都会被默认保护

<!--引入spring security依赖-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.创建控制器(Controller)

@RestController
public class HelloController {@GetMapping("hello")public String hello(){return "Hello Spring security";}
}

3.启动项目

  • 访问 http://localhost:9090/hello发现直接跳转到登录页面。其实这时候我们的请求已经被保护起来了,要想访问,需要先登录。
  • Spring Security 默认提供了一个用户名为 user 的用户,其密码在控制台可以找到
    在这里插入图片描述在这里插入图片描述在这里插入图片描述

SpringSecurity认证

1.数据库准备

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for t_permission
-- ----------------------------
DROP TABLE IF EXISTS `t_permission`;
CREATE TABLE `t_permission`  (`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',`permission_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名称',`permission_tag` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限标签',`permission_url` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限地址',PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;-- ----------------------------
-- Records of t_permission
-- ----------------------------
INSERT INTO `t_permission` VALUES (1, '查询所有用户', 'user:findAll', '/user/findAll');
INSERT INTO `t_permission` VALUES (2, '用户添加或修改', 'user:saveOrUpdate', '/user/saveOrUpadate');
INSERT INTO `t_permission` VALUES (3, '用户删除', 'user:delete', '/delete/{id}');
INSERT INTO `t_permission` VALUES (4, '根据ID查询用户', 'user:getById', '/user/{id}');
INSERT INTO `t_permission` VALUES (5, '查询所有商品', 'product:findAll', '/product/findAll');
INSERT INTO `t_permission` VALUES (6, '商品添加或修改', 'product:saveOrUpdate', '/product/saveOrUpadate');
INSERT INTO `t_permission` VALUES (7, '商品删除', 'product:delete', '/product//delete/{id}');
INSERT INTO `t_permission` VALUES (8, '商品是否显示', 'product:show', '/product/show/{id}/{isShow}');-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role`  (`ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',`ROLE_NAME` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',`ROLE_DESC` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述',PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;-- ----------------------------
-- Records of t_role
-- ----------------------------
INSERT INTO `t_role` VALUES (1, 'ADMIN', '超级管理员');
INSERT INTO `t_role` VALUES (2, 'USER', '用户管理');
INSERT INTO `t_role` VALUES (3, 'PRODUCT', '商品管理员');
INSERT INTO `t_role` VALUES (4, 'PRODUCT_INPUT', '商品录入员');
INSERT INTO `t_role` VALUES (5, 'PRODUCT_SHOW', '商品审核员');-- ----------------------------
-- Table structure for t_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `t_role_permission`;
CREATE TABLE `t_role_permission`  (`RID` int(11) NOT NULL COMMENT '角色编号',`PID` int(11) NOT NULL COMMENT '权限编号',PRIMARY KEY (`RID`, `PID`) USING BTREE,INDEX `FK_Reference_12`(`PID`) USING BTREE,CONSTRAINT `FK_Reference_11` FOREIGN KEY (`RID`) REFERENCES `t_role` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,CONSTRAINT `FK_Reference_12` FOREIGN KEY (`PID`) REFERENCES `t_permission` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;-- ----------------------------
-- Records of t_role_permission
-- ----------------------------
INSERT INTO `t_role_permission` VALUES (1, 1);
INSERT INTO `t_role_permission` VALUES (2, 1);
INSERT INTO `t_role_permission` VALUES (1, 2);
INSERT INTO `t_role_permission` VALUES (2, 2);
INSERT INTO `t_role_permission` VALUES (1, 3);
INSERT INTO `t_role_permission` VALUES (2, 3);
INSERT INTO `t_role_permission` VALUES (1, 4);
INSERT INTO `t_role_permission` VALUES (2, 4);
INSERT INTO `t_role_permission` VALUES (1, 5);
INSERT INTO `t_role_permission` VALUES (3, 5);
INSERT INTO `t_role_permission` VALUES (4, 5);
INSERT INTO `t_role_permission` VALUES (5, 5);
INSERT INTO `t_role_permission` VALUES (1, 6);
INSERT INTO `t_role_permission` VALUES (3, 6);
INSERT INTO `t_role_permission` VALUES (4, 6);
INSERT INTO `t_role_permission` VALUES (1, 7);
INSERT INTO `t_role_permission` VALUES (3, 7);
INSERT INTO `t_role_permission` VALUES (4, 7);
INSERT INTO `t_role_permission` VALUES (1, 8);
INSERT INTO `t_role_permission` VALUES (3, 8);
INSERT INTO `t_role_permission` VALUES (5, 8);-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,`password` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,`status` int(1) NULL DEFAULT NULL COMMENT '用户状态1-启用 0-关闭',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact;-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'admin', '$2a$10$m8WqgTzr0TO.XG.aR91.jegJJmDnGSvWs69aMWPR.WNvCzemHpLum', 1);
INSERT INTO `t_user` VALUES (2, 'zhaoyang', '$2a$10$m8WqgTzr0TO.XG.aR91.jegJJmDnGSvWs69aMWPR.WNvCzemHpLum', 1);
INSERT INTO `t_user` VALUES (3, 'user1', '$2a$10$m8WqgTzr0TO.XG.aR91.jegJJmDnGSvWs69aMWPR.WNvCzemHpLum', 1);
INSERT INTO `t_user` VALUES (4, 'user2', '$2a$10$m8WqgTzr0TO.XG.aR91.jegJJmDnGSvWs69aMWPR.WNvCzemHpLum', 1);
INSERT INTO `t_user` VALUES (5, 'user3', '$2a$10$Wk1jWJPoMQ5s7UIp0S/tu.WTcUZUspUUQH6K3BQpa8uHXWRUQc3/a', 1);-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role`  (`UID` int(11) NOT NULL COMMENT '用户编号',`RID` int(11) NOT NULL COMMENT '角色编号',PRIMARY KEY (`UID`, `RID`) USING BTREE,INDEX `FK_Reference_10`(`RID`) USING BTREE,CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `t_role` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `t_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;-- ----------------------------
-- Records of t_user_role
-- ----------------------------
INSERT INTO `t_user_role` VALUES (1, 1);
INSERT INTO `t_user_role` VALUES (2, 2);
INSERT INTO `t_user_role` VALUES (3, 4);
INSERT INTO `t_user_role` VALUES (4, 5);SET FOREIGN_KEY_CHECKS = 1;

2.原理解析

在使用SpringSecurity框架,该框架会默认自动地替我们将系统中的资源进行保护,每次访问资源的时候都必须经过一层身份的校验,如果通过了则重定向到我们输入的url中,否则访问是要被拒绝的。那么SpringSecurity框架是如何实现的呢? Spring Security功能的实现主要是由一系列过滤器相互配合完成。也称之为过滤器链
在这里插入图片描述过滤器是一种典型的AOP思想,下面简单了解下这些过滤器链,

  1. org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
    根据请求封装获取WebAsyncManager,从WebAsyncManager获取/注册的安全上下文可调用处理拦截器

  2. org.springframework.security.web.context.SecurityContextPersistenceFilter
    SecurityContextPersistenceFilter主要是使用SecurityContextRepository在session中保存或更新一个SecurityContext,并将SecurityContext给以后的过滤器使用,来为后续fifilter建立所需的上下文。SecurityContext中存储了当前用户的认证以及权限信息。

  3. org.springframework.security.web.header.HeaderWriterFilter
    向请求的Header中添加相应的信息,可在http标签内部使用security:headers来控制

  4. org.springframework.security.web.csrf.CsrfFilter
    csrf又称跨域请求伪造,SpringSecurity会对所有post请求验证是否包含系统生成的csrf的token信息,如果不包含,则报错。起到防止csrf攻击的效果。

  5. org.springframework.security.web.authentication.logout.LogoutFilter
    匹配URL为/logout的请求,实现用户退出,清除认证信息。

  6. org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
    表单认证操作全靠这个过滤器,默认匹配URL为/login且必须为POST请求。

  7. org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
    如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面。

  8. org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
    由此过滤器可以生产一个默认的退出登录页面

  9. org.springframework.security.web.authentication.www.BasicAuthenticationFilter
    此过滤器会自动解析HTTP请求中头部名字为Authentication,且以Basic开头的头信息。

  10. org.springframework.security.web.savedrequest.RequestCacheAwareFilter
    通过HttpSessionRequestCache内部维护了一个RequestCache,用于缓存HttpServletRequest

  11. org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
    针对ServletRequest进行了一次包装,使得request具有更加丰富的API

  12. org.springframework.security.web.authentication.AnonymousAuthenticationFilter
    当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder中。spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。

  13. org.springframework.security.web.session.SessionManagementFilter
    securityContextRepository限制同一用户开启多个会话的数量

  14. org.springframework.security.web.access.ExceptionTranslationFilter
    异常转换过滤器位于整个springSecurityFilterChain的后方,用来转换整个链路中出现的异常

  15. org.springframework.security.web.access.intercept.FilterSecurityInterceptor
    获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来决定其是否有权限。

Spring Security默认加载15个过滤器, 但是随着配置可以增加或者删除一些过滤器.

3.认证方式

3.1 HttpBasic认证
  • HttpBasic登录验证模式是Spring Security实现登录验证最简单的一种方式,也可以说是最简陋的一种方式。它的目的并不是保障登录验证的绝对安全,而是提供一种“防君子不防小人”的登录验证。

  • 在使用的Spring Boot早期版本为1.X版本,依赖的Security 4.X版本,那么就无需任何配置,启动项目访问则会弹出默认的httpbasic认证。现在使用的是spring boot2.0以上版本(依赖Security 5.X版本),HttpBasic不再是默认的验证模式,在spring security 5.x默认的验证模式已经是表单模式。

  • HttpBasic模式要求传输的用户名密码使用Base64模式进行加密。如果用户名是 “admin” ,密码是“ admin”,则将字符串"admin:admin" 使用Base64编码算法加密。加密结果可能是:YWtaW46YWRtaW4=。HttpBasic模式真的是非常简单又简陋的验证模式,Base64的加密算法是可逆的,想要破解并不难.

3.2 formLogin登录认证模式

Spring Security的HttpBasic模式,该模式比较简单,只是进行了通过携带Http的Header进行简单的登录验证,而且没有定制的登录页面,所以使用场景比较窄。对于一个完整的应用系统,与登录验证相关的页面都是高度定制化的,非常美观而且提供多种登录方式。这就需要Spring Security支持我们自己定制登录页面, spring boot2.0以上版本(依赖Security 5.X版本)默认会生成一个登录页面.

4.表单认证

4.1自定义表单登录页面

在config包下编写SecurityConfiguration配置类

package com.kgc.config;import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;/*** @author: zjl* @datetime: 2024/3/27* @desc:*/
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {/*http.httpBasic()//开启httpbasic认证.and().authorizeRequests().anyRequest().authenticated();//所有请求都需要登录认证才能访问*/http.formLogin()//开启表单认证.and().authorizeRequests().anyRequest().authenticated();//所有请求都需要登录认证才能访问;}
}
问题一: localhost将您重定向次数过多

因为设置登录页面为login.html 后面配置的是所有请求都登录认证,陷入了死循环. 所以需要将login.html放行不需要登录认证

http.formLogin().loginPage("/login.html")//开启表单认证.and().authorizeRequests().antMatchers("/login.html").permitAll()//放行登录页面.anyRequest().authenticated();//所有请求都需要登录认证才能访问;
问题二: 访问login.html 报404错误

spring boot整合thymeleaf 之后 所有的静态页面以放在resources/templates下面,所以得通过请求访问到模板页面, 将/login.html修改为/toLoginPage

http.formLogin().loginPage("/toLoginPage")//开启表单认证.and().authorizeRequests().antMatchers("/toLoginPage").permitAll()//放行登录页面.anyRequest().authenticated();//所有请求都需要登录认证才能访问;
问题三: 访问login.html 后发现页面没有相关样式

因为访问login.html需要一些js , css , image等静态资源信息, 所以需要将静态资源放行, 不需要认证

    @Overridepublic void configure(WebSecurity web) throws Exception {//解决静态资源被拦截的问题web.ignoring().antMatchers("/css/**", "/images/**", "/js/**", "/favicon.ico");}

Spring Security中,安全构建器HttpSecurity和WebSecurity的区别是 :

  • WebSecurity不仅通过HttpSecurity定义某些请求的安全控制,也通过其他方式定义其他某些请求可以忽略安全控制;

  • HttpSecurity仅用于定义需要安全控制的请求(当然HttpSecurity也可以指定某些请求不需要安全控制);

  • 可以认为HttpSecurity是WebSecurity的一部分,WebSecurity是包含HttpSecurity的更大的一个概念;

  • 构建目标不同

    • WebSecurity构建目标是整个Spring Security安全过滤器FilterChainProxy`,
    • HttpSecurity的构建目标仅仅是FilterChainProxy中的一个SecurityFilterChain。
4.2.表单登录

通过讲解过滤器链中我们知道有个过滤器UsernamePasswordAuthenticationFilter是处理表单登录的. 那么下面我们来通过源码观察下这个过滤器.

    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";private String usernameParameter = "username";private String passwordParameter = "password";
...public UsernamePasswordAuthenticationFilter() {super(new AntPathRequestMatcher("/login", "POST"));}

在源码中可以观察到, 表单中的input的name值是username和password, 并且表单提交的路径为/login, 表单提交方式method为post, 这些可以修改为自定义的值.

/*** http请求处理方法** @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {/*http.httpBasic()//开启httpbasic认证.and().authorizeRequests().anyRequest().authenticated();//所有请求都需要登录认证才能访问*/http.formLogin()//开启表单认证.loginPage("/toLoginPage")//自定义登录页面.loginProcessingUrl("/login")// 登录处理Url.usernameParameter("username").passwordParameter("password")//修改自定义表单name值..successForwardUrl("/")// 登录成功后跳转路径.and().authorizeRequests().antMatchers("/toLoginPage").permitAll()//放行登录页面.anyRequest().authenticated();//所有请求都需要登录认证才能访问;// 关闭csrf防护http.csrf().disable();}
页面源码(DEMO)
//login.tml
<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Security登录</title>
</head>
<body>
<h1>Security登录</h1>
<form method="post" th:action="@{/login}"><p>用户名:<input name="username" type="text"/></p><p>密码:<input name="password" type="password"/></p><p><input type="submit" value="登录"/></p>
</form>
</body>
</html>//index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>Hello Spring security</title>
</head>
<body>
<h1>Hello Spring security,Login Success!!!</h1>
</body>
</html>
Controller源码
package com.kgc.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;/*** @author: zjl* @datetime: 2024/3/27* @desc:*/
@Controller
public class LoginController {@RequestMapping("/toLoginPage")public String login() {return "login";}@PostMapping("/")public String doLogin(){return "index";}
}

备注:如果用到了iframe,还需要配置允许iframe加载

http.formLogin()//开启表单认证
...
http.csrf().disable();
// 允许iframe加载页面
http.headers().frameOptions().sameOrigin();	
4.3基于数据库实现认证功能

之前我们所使用的用户名和密码是来源于框架自动生成的, 那么我们如何实现基于数据库中的用户名和密码功能呢? 要实现这个得需要实现security的一个UserDetailsService接口, 重写这个接口里面loadUserByUsername即可

4.3.1编写MyUserDetailsService并实现UserDetailsService接口,重写loadUserByUsername方法
package com.kgc.service.impl;import com.kgc.mapper.UserMapper;
import com.kgc.pojo.User;
import com.kgc.service.UserService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;/*** @author: zjl* @datetime: 2024/3/27* @desc: 基于数据库中完成认证*/
@Service
public class MyUserDetailsService implements UserDetailsService {@Resourceprivate UserMapper userMapper;/*** 根据username查询用户实体** @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userMapper.findByUsername(username);if (user == null) {throw new UsernameNotFoundException(username);// 用户名没有找到}// 先声明一个权限集合, 因为构造方法里面不能传入nullCollection<? extends GrantedAuthority> authorities = new ArrayList<>();// 需要返回一个SpringSecurity的UserDetails对象UserDetails userDetails =new org.springframework.security.core.userdetails.User(user.getUsername(),"{noop}" + user.getPassword(),// {noop}表示不加密认证。true, // 用户是否启用 true 代表启用true,// 用户是否过期 true 代表未过期true,// 用户凭据是否过期 true 代表未过期true,// 用户是否锁定 true 代表未锁定authorities);return userDetails;}
}
4.3.2在SecurityConfiguration配置类中指定自定义用户认证
 /*** 身份验证管理器* @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailsService);// 使用自定义用户认证}	
4.3.3密码加密认证
  • 在基于数据库完成用户登录的过程中,我们所是使用的密码是明文的,规则是通过对密码明文添加{noop}前缀。
  • Spring Security 中PasswordEncoder就是我们对密码进行编码的工具接口。该接口只有两个功能: 一个是 匹配验证。另一个是 密码编码
BCrypt算法介绍
  • 任何应用考虑到安全,绝不能明文的方式保存密码。密码应该通过哈希算法进行加密。 有很多标准的算法比如SHA或者MD5,结合salt(盐)是一个不错的选择。 Spring Security 提供了BCryptPasswordEncoder类,实现Spring的PasswordEncoder接口使用BCrypt强哈希方法来加密密码。BCrypt强哈希方法 每次加密的结果都不一样,所以更加的安全。
  • bcrypt算法相对来说是运算比较慢的算法,在密码学界有句常话:越慢的算法越安全。黑客破解成本越高.通过salt和const这两个值来减缓加密过程,它的加密时间(百ms级)远远超过md5(大概1ms左右)。对于计算机来说,Bcrypt 的计算速度很慢,但是对于用户来说,这个过程不算慢。bcrypt是单向的,而且经过salt和cost的处理,使其受攻击破解的概率大大降低,同时破解的难度也提升不少,相对于MD5等加密方式更加安全,而且使用也比较简单
  • bcrypt加密后的字符串形如:$2a$10$wouq9P/HNgvYj2jKtUN8rOJJNRVCWvn1XoWy55N3sCkEHZPo3lyWq。(其中$是分割符,无意义;2a是bcrypt加密版本号;10是const的值;而后的前22位是salt值;再然后的字符串就是密码的密文了;这里的const值即生成salt的迭代次数,默认值是10,推荐值12。)
在项目中使用BCrypt

首先看下PasswordEncoderFactories 密码器工厂
在这里插入图片描述之前我们在项目中密码使用的是明文的是noop , 代表不加密使用明文密码, 现在用BCrypt只需要将noop换成bcrypt即可

	@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userMapper.findByUsername(username);if (user == null) {throw new UsernameNotFoundException(username);// 用户名没有找到}// 先声明一个权限集合, 因为构造方法里面不能传入nullCollection<? extends GrantedAuthority> authorities = new ArrayList<>();// 需要返回一个SpringSecurity的UserDetails对象UserDetails userDetails =new org.springframework.security.core.userdetails.User(user.getUsername(),"{bcrypt}" + user.getPassword(),// {noop}表示不加密认证。{bcrypt} 加密认证true, // 用户是否启用 true 代表启用true,// 用户是否过期 true 代表未过期true,// 用户凭据是否过期 true 代表未过期true,// 用户是否锁定 true 代表未锁定authorities);return userDetails;}

同时需要将数据库中的明文密码修改为加密密码

	public static void main(String[] args) {BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();String encode1 = bCryptPasswordEncoder.encode("123456");String encode2 = bCryptPasswordEncoder.encode("123456");System.out.println(encode1);System.out.println(encode2);}//$2a$10$0i8cYHtb91REhnef7G2hTu8IGgbtcwKSI.iCS/W2R8JrOF61Mnwqa//$2a$10$qgRZb.Iv5L9q6Bc5CvwWf.WW3EjDz.ZBEnJeIjIMvzMG0vrOkrdZ6//每次加密数据都不一样,选择一个放入数据库即可,反解出来的明文会一样
4.3.4获取当前登录用户

在传统web系统中, 我们将登录成功的用户放入session中, 在需要的时候可以从session中获取用户, 那么Spring Security中我们如何获取当前已经登录的用户呢?

  • SecurityContextHolder
    保留系统当前的安全上下文SecurityContext,其中就包括当前使用系统的用户的信息。
  • SecurityContext
    安全上下文,获取当前经过身份验证的主体或身份验证请求令牌
传统实现
/*** 获取当前登录用户** @return*/@RequestMapping("/loginUser1")@ResponseBodypublic UserDetails getCurrentUser() {UserDetails userDetails = (UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();return userDetails;}
Spring Security 还提供了2种方式可以获取.
    /*** 获取当前登录用户** @return*/@RequestMapping("/loginUser2")@ResponseBodypublic UserDetails getCurrentUser(Authentication authentication) {UserDetails userDetails = (UserDetails) authentication.getPrincipal();return userDetails;}/*** 获取当前登录用户** @return*/@RequestMapping("/loginUser3")@ResponseBodypublic UserDetails getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {return userDetails;}
remember me 记住我

在大多数网站中,都会实现RememberMe这个功能,方便用户在下一次登录时直接登录,避免再次输入用户名以及密码去登录,Spring Security针对这个功能已经帮助我们实现, 下面我们来看下他的原理图

  • 简单的Token生成方法
    在这里插入图片描述Token=MD5(username+分隔符+expiryTime+分隔符+password)
    注意: 这种方式不推荐使用, 有严重的安全问题. 就是密码信息在前端浏览器cookie中存放. 如果cookie被盗取很容易破解.
  • 持久化的Token生成方法
    在这里插入图片描述

存入数据库Token包含:

  • token: 随机生成策略,每次访问都会重新生成
  • series: 登录序列号,随机生成策略。用户输入用户名和密码登录时,该值重新生成。使用remember-me功能,该值保持不变
  • expiryTime: token过期时间。

​ CookieValue=encode(series+token)

前端代码

前端页面需要增加remember-me的复选框

 <div class="form-group"><div ><!--记住我 name为remember-me value值可选true  yes 1 on 都行--><input type="checkbox"  name="remember-me" value="true"/>记住我</div>
</div>
后台代码

后台代码开启remember-me功能

	/*** http请求处理方法** @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {/*http.httpBasic()//开启httpbasic认证.and().authorizeRequests().anyRequest().authenticated();//所有请求都需要登录认证才能访问*/http.formLogin()//开启表单认证.loginPage("/toLoginPage")//自定义登录页面.loginProcessingUrl("/login")// 登录处理Url//.usernameParameter().passwordParameter(). 修改自定义表单name值..successForwardUrl("/")// 登录成功后跳转路径.and().authorizeRequests().antMatchers("/toLoginPage").permitAll()//放行登录页面与静态资源.anyRequest().authenticated()//所有请求都需要登录认证才能访问;.and().rememberMe()//开启记住我功能.tokenValiditySeconds(1209600)// token失效时间默认2周.rememberMeParameter("remember-me")// 自定义表单name值.tokenRepository(getPersistentTokenRepository());// 设置tokenRepository// 关闭csrf防护http.csrf().disable();}@Resourceprivate DataSource dataSource;/*** 持久化token,负责token与数据库之间的相关操作* @return*/@Beanpublic PersistentTokenRepository getPersistentTokenRepository() {JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();tokenRepository.setDataSource(dataSource);//设置数据源// 启动时创建一张表, 第一次启动的时候创建, 第二次启动的时候需要注释掉, 否则会报错tokenRepository.setCreateTableOnStartup(true);return tokenRepository;}

项目启动成功后,观察数据库,会帮助我们创建persistent_logins表

再次完成登录功能.

观察数据库,会插入一条记录.说明持久化token方式已经生效
在这里插入图片描述在这里插入图片描述

4.3.5Cookie窃取伪造演示
  • 使用网页登录系统,记录remember-me的值
  • 使用ApiPost伪造cookie
    在这里插入图片描述
4.3.6安全验证
  /*** 根据用户ID查询用户** @return*/@GetMapping("/{id}")@ResponseBodypublic User getById(@PathVariable Integer id) {//获取认证信息Authentication authentication = SecurityContextHolder.getContext().getAuthentication();// 判断认证信息是否来源于RememberMeif (RememberMeAuthenticationToken.class.isAssignableFrom(authentication.getClass())) {throw new RememberMeAuthenticationException("认证信息来源于RememberMe,请重新登录");}User user = userService.getById(id);return user;}
4.3.7自定义登录成功处理和失败处理

在某些场景下,用户登录成功或失败的情况下用户需要执行一些后续操作,比如登录日志的搜集, 或者在现在目前前后端分离的情况下用户登录成功和失败后需要给前台页面返回对应的错误信息, 有前台主导登录成功或者失败的页面跳转. 这个时候需要要到用到AuthenticationSuccessHandler与AnthenticationFailureHandler.

  • 自定义成功处理:
    实现AuthenticationSuccessHandler接口,并重写onAnthenticationSuccesss()方法.

  • 自定义失败处理:
    实现AuthenticationFailureHandler接口,并重写onAuthenticationFailure()方法;

代码实现登录成功或失败的自定义处理

MyAuthenticationService类

package com.kgc.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;/*** @author: zjl* @datetime: 2024/3/27* @desc:自定义登录成功或失败处理类*/
@Service
public class MyAuthenticationService implements AuthenticationSuccessHandler, AuthenticationFailureHandler {private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {}@Resourceprivate ObjectMapper objectMapper;@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {System.out.println("登录成功后续处理....");//redirectStrategy.sendRedirect(request, response, "/");Map result = new HashMap();result.put("code", HttpStatus.OK.value());// 设置响应码result.put("message", "登录成功");// 设置响应信息response.setContentType("application/json;charset=UTF-8");response.getWriter().write(objectMapper.writeValueAsString(result));}@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {System.out.println("登录失败后续处理....");//redirectStrategy.sendRedirect(request, response, "/toLoginPage");Map result = new HashMap();result.put("code", HttpStatus.UNAUTHORIZED.value());// 设置响应码result.put("message", exception.getMessage());// 设置错误信息response.setContentType("application/json;charset=UTF-8");response.getWriter().write(objectMapper.writeValueAsString(result));}
}
异步用户登录实现

前端页面改造

<input type="button" onclick="login()" value="登录"><script>function login() {$.ajax({type: "POST",//方法类型dataType: "json",//服务器预期返回类型url: "/login",   // 登录urldata: $("#formLogin").serialize(),success: function (data) {console.log(data)if (data.code == 200) {window.location.href = "/";} else {alert(data.message);}}});}
</script>
4.3.8退出登录
  • org.springframework.security.web.authentication.logout.LogoutFilter:匹配URL为/logout的请求,实现用户退出,清除认证信息。
  • 只需要发送请求,请求路径为/logout即可, 当然这个路径也可以自行在配置类中自行指定, 同时退出操作也有对应的自定义处理LogoutSuccessHandler,退出登录成功后执行,退出的同时如果有remember-me的数据,同时一并删除
  • 前端
<a href="/logout"><span></span>退出登录</a>
  • 后端
public class MyAuthenticationService implements AuthenticationSuccessHandler, AuthenticationFailureHandler, LogoutSuccessHandler{
...
@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {System.out.println("退出成功后续处理....");redirectStrategy.sendRedirect(request, response, "/toLoginPage");}
}
.and().logout().logoutUrl("/logout")//设置退出url.logoutSuccessHandler(myAuthenticationService)//自定义退出处理

SpringSecurity授权

原理图
在这里插入图片描述在我们应用系统里面,如果想要控制用户权限,需要有2部分数据。

  1. 系统配置信息数据:写着系统里面有哪些URL,每一个url拥有哪些权限才允许被访问。

  2. 另一份数据就是用户权限信息:请求用户拥有权限
    系统用户发送一个请求:系统配置信息和用户权限信息作比对,如果比对成功则允许访问。

当一个系统授权规则比较简单,基本不变时候,系统的权限配置信息可以写在我们的代码里面去的。比如前台门户网站等权限比较单一,可以使用简单的授权配置即可完成,如果权限复杂, 例如办公OA, 电商后台管理系统等就不能使用写在代码里面了. 需要RBAC权限模型设计

1.内置权限表达式

Spring Security 使用Spring EL来支持,主要用于Web访问和方法安全上, 可以通过表达式来判断是否具有访问权限. 下面是Spring Security常用的内置表达式. ExpressionUrlAuthorizationConfigurer定义了所有的表达式

表达式说明
permitAll指定任何人都允许访问。
denyAll指定任何人都不允许访问
anonymous指定匿名用户允许访问。
rememberMe指定已记住的用户允许访问。
authenticated指定任何经过身份验证的用户都允许访问,不包含anonymous
fullyAuthenticated指定由经过身份验证的用户允许访问,不包含anonymous和rememberMe
hasRole(role)指定需要特定的角色的用户允许访问, 会自动在角色前面插入’ROLE_’
hasAnyRole([role1,role2])指定需要任意一个角色的用户允许访问, 会自动在角色前面插入’ROLE_’
hasAuthority(authority)指定需要特定的权限的用户允许访问
hasAnyAuthority([authority,authority])指定需要任意一个权限的用户允许访问
hasIpAddress(ip)指定需要特定的IP地址可以访问

2.url安全表达式

基于web访问使用表达式保护url请求路径.

2.1设置url访问权限
// 设置/user/** 访问需要ADMIN角色
http.authorizeRequests().antMatchers("/user/**").hasRole("ADMIN");
// 设置/user/** 访问需要PRODUCT角色和IP地址为127.0.0.1  .hasAnyRole("PRODUCT,ADMIN")
http.authorizeRequests().antMatchers("/product/**").access("hasAnyRole('ADMIN,PRODUCT') and hasIpAddress('127.0.0.1')");
// 设置自定义权限不足信息.
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
2.MyAccessDeniedHandler自定义权限不足类
package com.kgc.handler;import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/*** @author: zjl* @datetime: 2024/3/27* @desc:自定义权限不足信息*/
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException {resp.setStatus(HttpServletResponse.SC_FORBIDDEN);resp.setContentType("text/html;charset=UTF-8");resp.getWriter().write("权限不足,请联系管理员!");}
}
2.3设置用户对应的角色权限
// 先声明一个权限集合, 因为构造方法里面不能传入null
Collection<GrantedAuthority> authorities = new ArrayList<>();
if ("admin".equalsIgnoreCase(user.getUsername())) {authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
} else {authorities.add(new SimpleGrantedAuthority("ROLE_PRODUCT"));
}

3.在Web 安全表达式中引用自定义Bean授权

3.1定义自定义授权类
package com.kgc.service;import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
/*** @author: zjl* @datetime: 2024/3/27* @desc:自定义授权类*/
@Component
public class MyAuthorizationService {/*** 检查用户是否有对应的访问权限** @param authentication 登录用户* @param request        请求对象* @return*/public boolean check(Authentication authentication, HttpServletRequest request) {User user = (User) authentication.getPrincipal();// 获取用户所有权限Collection<GrantedAuthority> authorities = user.getAuthorities();// 获取用户名String username = user.getUsername();// 如果用户名为admin,则不需要认证if (username.equalsIgnoreCase("admin")) {return true;} else {// 循环用户的权限, 判断是否有ROLE_ADMIN权限, 有返回truefor (GrantedAuthority authority : authorities) {String role = authority.getAuthority();if ("ROLE_ADMIN".equals(role)) {return true;}}}return false;}
}
3.2配置类
//使用自定义Bean授权
http.authorizeRequests().antMatchers("/user/**").access("@myAuthorizationService.check(authentication,request)");
3.3携带路径变量
 /*** 检查用户是否有对应的访问权限** @param authentication 登录用户* @param request        请求对象* @param id             参数ID* @return*/public boolean check(Authentication authentication, HttpServletRequest request, Integer id) {if (id > 10) {return false;}return true;}
//使用自定义Bean授权,并携带路径参数http.authorizeRequests().antMatchers("/user/delete/{id}").access("@myAuthorizationService.check(authentication,request,#id)");

4.Method安全表达式

针对方法级别的访问控制比较复杂,spring security提供了4种注解分别是@PreAuthorize,@PostAuthorize,@PreFilter,@PostFilter.

4.1开启方法级别的注解配置

在security配置类中添加注解

/*** Security配置类*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启注解支持
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter	
4.2在方法上使用注解
@ProAuthorize : 注解适合进入方法前的权限验证
/*** 查询所有用户** @return*/@RequestMapping("/findAll")@PreAuthorize("hasRole('ADMIN')")//需要ADMIN权限public String findAll(Model model) {List<User> userList = userService.list();model.addAttribute("userList", userList);return "user_list";}/*** 用户修改页面跳转** @return*/@RequestMapping("/update/{id}")@PreAuthorize("#id<10")//针对参数权限限定 id<10可以访问public String update(@PathVariable Integer id, Model model) {User user = userService.getById(id);model.addAttribute("user", user);return "user_update";}
@PostAuthorize:

@PostAuthorize在方法执行后再进行权限验证,适合验证带有返回值的权限,Spring EL提供返回对象能够在表达式语言中获取到返回对象的 returnObject

 /*** 根据ID查询用户** @return*/@GetMapping("/{id}")@ResponseBody@PostAuthorize("returnObject.username== authentication.principal.username")//判断查询用户信息是否是当前登录用户信息.否则没有权限public User getById(@PathVariable Integer id) {User user = userService.getById(id);return user;}

returnObject : 代表return返回的值

@PreFilter: 可以用来对集合类型的参数进行过滤, 将不符合条件的元素剔除集合
/*** 商品删除-多选删除** @return*/@GetMapping("/delByIds")@PreFilter(filterTarget = "ids", value = "filterObject%2==0")//剔除参数为基数的值public String delByIds(@RequestParam(value = "id") List<Integer> ids) {for (Integer id : ids) {System.out.println(id);}return "redirect:/user/findAll";}
@PostFilter: 可以用来对集合类型的返回值进行过滤, 将不符合条件的元素剔除集合
/*** 查询所有用户-返回json数据** @return*/@RequestMapping("/findAllTOJson")@ResponseBody@PostFilter("filterObject.id%2==0")//剔除返回值ID为偶数的值public List<User> findAllTOJson() {List<User> userList = userService.list();return userList;}

基于数据库的RBAC数据模型的权限控制

我们开发一个系统,必然面临权限控制的问题,不同的用户具有不同的访问、操作、数据权限。形成理论的权限控制模型有:自主访问控制(DAC: Discretionary Access Control)、强制访问控制(MAC: Mandatory Access Control)、基于属性的权限验证(ABAC: Attribute-Based Access Control)等。最常被开发者使用也是相对易用、通用的就是RBAC权限模型(Role-Based Access Control)

1.RBAC权限模型简介

RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。模型中有几个关键的术语:

  • 用户:系统接口及访问的操作者
  • 权限:能够访问某接口或者做某操作的授权资格
  • 角色:具有一类相同操作权限的总称

RBAC权限模型核心授权逻辑如下:

  • 某用户是什么角色?
  • 某角色具有什么权限?
  • 通过角色对应的权限推导出用户的权限
2.RBAC的演化进程
用户与权限直接关联

想到权限控制,人们最先想到的一定是用户与权限直接关联的模式,简单地说就是:某个用户具有某些权限。如图:
在这里插入图片描述

  • 张三具有所有权限他可能是一个超级管理员.
  • 李四,王五 具有添加商品和审核商品的权限有可能是一个普通业务员

这种模型能够清晰的表达用户与权限之间的关系,足够简单。但同时也存在问题:

  1. 现在用户是张三、李四,王五以后随着人员增加,每一个用户都需要重新授权
  2. 操作人员的他的权限发生变更后,需要对每个一个用户重新授予新的权限
用户与角色关联

-这样只需要维护角色和权限之间的关系就可以了. 如果业务员的权限发生变更, 只需要变动业务员角色和权限之前的关系进行维护就可以了. 用户和权限就分离开来了. 如下图
在这里插入图片描述

基于RBAC设计权限表结构
  • 一个用户有一个或多个角色
  • 一个角色包含多个用户
  • 一个角色有多种权限
  • 一个权限属于多个角色
    在这里插入图片描述
3.基于Spring Security 实现RBAC权限管理
3.1动态查询数据库中用户对应的权限
package com.kgc.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.kgc.pojo.Permission;
import org.apache.ibatis.annotations.Select;import java.util.List;public interface PermissionMapper extends BaseMapper<Permission> {/*** 根据用户ID查询权限** @param id* @return*/@Select("SELECT p.*  FROM t_permission p,t_role_permission rp,t_role r,t_user_role ur,t_user u " +"WHERE p.id = rp.PID AND rp.RID = r.id AND r.id = ur.RID AND ur.UID = u.id AND u.id =#{id}")List<Permission> findByUserId(Integer id);
}
3.2给登录用户授权
 // 先声明一个权限集合, 因为构造方法里面不能传入null
Collection<GrantedAuthority> authorities = new ArrayList<>();
// 查询用户对应所有权限
List<Permission> permissions = permissionService.findByUserId(user.getId());
for (Permission permission : permissions) {// 授权authorities.add(new SimpleGrantedAuthority(permission.getPermissionTag()));
}
3.3设置访问权限
// 查询数据库所有权限列表
List<Permission> permissions = permissionService.list();
for (Permission permission : permissions) {//添加请求权限
http.authorizeRequests().antMatchers(permission.getPermissionUrl()).hasAuthority(permission.getPermissionTag());
}
  • 项目
    • 项目
      • 项目

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/287984.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

卷起来——高级数据分析师

要成为一名高级数据分析师&#xff0c;需要掌握一系列的技能&#xff0c;包括数据处理、统计分析、机器学习、数据可视化以及业务理解等&#xff0c;喜欢或者想往这方面发展的童鞋们&#xff0c;卷起来&#xff0c;点击以下链接中的链接&#xff0c;备注"分析"进群交…

电商产品效果图渲染用什么工具更方便?

​在电子商务的快速发展中&#xff0c;产品的视觉呈现变得至关重要。对于电商行业的设计师而言&#xff0c;选择一款既便捷又高效的渲染工具&#xff0c;对于快速完成高质量的产品效果图至关重要。特别是对于初学者&#xff0c;工具的直观性和功能性是他们最为关注的焦点。 那…

【机器学习之旅】概念启程、步骤前行、分类掌握与实践落地

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;机器学习 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

【MySQL】4.MySQL日志管理与数据库的备份和恢复

备份的目的只要是为了灾难恢复&#xff0c;备份还可以测试应用&#xff0c;回滚数据&#xff0c;修改和查询历史数据&#xff0c;审计等 日志在备份、恢复中起着重要作用 一、数据库备份的重要性 在生产环境中&#xff0c;数据的安全性至关重要 任何数据丢失都可能产生严重的…

详解U-Net分割网络,提供详细代码技术细节及完整项目代码

一. 原始模型整体概述 U-Net网络是Ronneberger等人在2015年发表于计算机医学影像顶刊 MICCAI上的一篇论文&#xff0c;该论文首次提出了一种U型结构来进行图像的语义分割&#xff0c;论文的下载链接如下&#xff1a;U-Net: Convolutional Networks for Biomedical Image Segme…

高中数学:抽象函数难点突破(拔高)

例题1 只证明前3个小题&#xff0c;4,5比较简单&#xff0c;不给与证明 这里&#xff0c;第3小题&#xff0c;难度最高 例题2 证明单调性的方法&#xff1a; 1、观察图像法&#xff1a;前提有具体解析式&#xff0c;且能画出图像 2、导数法&#xff1a;高三才学&#xff0c;且…

python和Vue开发的RBAC用户角色权限管理系统

后端框架&#xff1a;python的FastAPI作为后端服务和python-jose作为JWT认证 前端框架&#xff1a;Vue3构建页面和Vue Router作为路由管理&#xff0c;Pinia作为数据存储&#xff0c;Vite作为打包工具 可以实现菜单控制和路由控制&#xff0c;页面里面有按钮权限控制&#xf…

信息化平台管理系统智能引擎,互联网企业转型升级的新篇章-亿发

企业管理系统一直在伴随着中国互联网企业的发展而不断进步。过去&#xff0c;企业管理主要依赖于传统的表格和图表记录&#xff0c;但随着互联网企业的崛起&#xff0c;他们开始尝试自己开发简易的管理系统以满足业务需求。随着企业规模和业务复杂度的增加&#xff0c;互联网企…

【分享贴】多项目并行,如何做好项目管理?

对于项目经理来说&#xff0c;多项目并行管理是工作中的常态&#xff0c;也是一大难点。当多个项目共同推进时&#xff0c;项目经理经常会出现手忙脚乱、四处救火的情形&#xff0c;例如&#xff1a; A.资源管理难&#xff1a;资源冲突、资源分配不合理会导致项目延期。 B.进度…

【STM32嵌入式系统设计与开发】——12IWDG(独立看门狗应用)

这里写目录标题 一、任务描述二、任务实施1、ActiveBeep工程文件夹创建2、函数编辑&#xff08;1&#xff09;主函数编辑&#xff08;2&#xff09;USART1初始化函数(usart1_init())&#xff08;3&#xff09;USART数据发送函数&#xff08; USART1_Send_Data&#xff08;&…

苹果App Store上架工具介绍

文章目录 摘要引言正文1. Xcode2. [appuploder](https://www.applicationloader.net/)3. [克魔助手](https://keymob.com/) 4.[ipa guard](https://www.ipaguard.com/)总结参考资料 摘要 苹果App Store作为iOS应用程序的主要分发渠道&#xff0c;上架应用程序需要遵守规定和通…

Hive3.0.0出库库表中timestamp字段读取为null

在利用sqoop1.99.7做数据迁移的时候&#xff0c;从mysql导出表格到hive建立对应的表格&#xff0c;字段中使用了timestamp类型&#xff0c;在读取数据的时候&#xff0c;发现数据为null。查找问题方法如下&#xff1a; 1、查询库表字段类型 命令&#xff1a;desc tablen…

00000基础搭建vue+flask前后端分离项目

我完全是参考的这个vue3flask前后端分离环境速建_flask vue3-CSDN博客 安装了node_js&#xff08;添加了环境变量&#xff09; 环境变量 把原来的镜像源换成了淘宝镜像源 npm config set registry https://registry.npmmirror.com/ 查看版本证明安装成功 npm - v 安装npm i…

正则表达式不会用?一篇教你快速搞懂 !

目录 前言一、基础字符二、一系列常用的字符&#xff1b;1、一些元字符&#xff08;Meta-characters&#xff09; 三、一些高级概念1、贪婪与懒惰匹配2、两个实例加深理解1.颜色值的匹配&#xff1a;RGBS值2.ipv4 地址匹配 四、正则表达式常用语法**1.Flags&#xff08;标志符或…

c语言中动态内存管理

说到内存&#xff0c;大家一定都知道。但是有一种函数可以实现动态内存管理&#xff0c;下面大家一起学习。 文章目录 一、为什么要有动态内存管理&#xff1f;二、malloc 和 free1.malloc2.free 三、calloc 和 realloc1.calloc2.realloc3.常见的动态内存的错误3.1对NULL指针的…

面试题 之 webpack

1.说说你对webpack理解&#xff1f;解决什么问题&#xff1f; Webpack 是实现前端项目的模块化&#xff0c;用于现代 JavaScript 应用程序的静态模块打包工具&#xff0c;被webpack 直接引用的资源打包进 bunde.js的资源&#xff0c;当webpack 处理应用程序时,它会在内部构建一…

理解CPU与执行指令原理

本文侧重介绍cpu的工作任务&#xff0c;与cpu执行指令的过程是怎么样的&#xff1f; 目录 1.理解CPU 1.1.CPU的功能 1.2.CPU的逻辑构成 2.认识指令 2.1.什么是指令 2.2.CPU执行指令的准备工作(重点) 3.指令的执行过程 前景知识&#xff1a; 什么是计算机 就是遵循冯诺依…

阿里云部署宝塔,设置了安全组还是打不开。

1.在安全组是开放正确的端口好。8888要开&#xff0c;但是不只是开放8888&#xff0c;举个例子&#xff0c;https://47.99.53.222:17677/49706cf7这个&#xff0c;要开放17677这个端口号。 2.安全组要挂载到实例上&#xff0c;从三个点的进入点击管理实例&#xff0c;加到对应的…

深入聊聊企业数字化转型这个事儿

01 什么是数字化&#xff1f; 聊数字化&#xff0c;就不得不聊聊信息化、智能化。佛性的说&#xff1a;信息化是数字化的前世&#xff0c;智能化是数字化的来生&#xff01;我习惯用一个结构化的图形来表示事物之间的关系&#xff0c;信息化、数字化、智能化的关系如下&#…

[flask] flask的基本介绍、flask快速搭建项目并运行

笔记 Flask Flask 本身相当于一个内核&#xff0c;其他几乎所有的功能都要用到扩展&#xff08;邮件扩展Flask-Mail&#xff0c;用户认证Flask-Login&#xff0c;数据库Flask-SQLAlchemy&#xff09;&#xff0c;都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入ORM、…