Spring Security-全面详解(学习总结---从入门到深化)(下)

简介: 我们也可以自定义退出成功处理器,在退出后清理一些数据

Spring Security认证_退出成功处理器

我们也可以自定义退出成功处理器,在退出后清理一些数据,写法 如下:

1、自定义退出成功处理器

public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
        System.out.println("清除一些数据...");
        response.sendRedirect("/login.html");
   }
}

2、配置退出成功处理器

// 退出登录配置
http.logout()
   .logoutUrl("/logout") // 退出登录路径
    //               .logoutSuccessUrl("/login.html") // 退出登录后跳转的路径
   .clearAuthentication(true) //清除认证状态,默认为true
   .invalidateHttpSession(true) // 销毁HttpSession对象,默认为 true
   .logoutSuccessHandler(new MyLogoutSuccessHandler()); //自定义退出成功处
理器

Spring Security认证_Remember Me

Spring Security中Remember Me为“记住我”功能,即下次访问系统 时无需重新登录。当使用“记住我”功能登录后,Spring Security会 生成一个令牌,令牌一方面保存到数据库中,另一方面生成一个叫 remember-me 的Cookie保存到客户端。之后客户端访问项目时自动携 带令牌,不登录即可完成认证。

2345_image_file_copy_55.jpg

1、编写“记住我”配置类

@Configuration
public class RememberMeConfig {
    @Autowired
    private DataSource dataSource;
    // 令牌Repository
    @Bean
    public PersistentTokenRepository getPersistentTokenRepository() {
        // 为Spring Security自带的令牌控制器设置数据源
        JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl = new JdbcTokenRepositoryImpl();
        jdbcTokenRepositoryImpl.setDataSource(dataSource);
        //自动建表,第一次启动时需要,第二次启动时注释掉
        //       jdbcTokenRepositoryImpl.setCreateTableOnStartup(true);
        return jdbcTokenRepositoryImpl;
   }
}

2、修改Security配置类

// 记住我配置
http.rememberMe()
       .userDetailsService(userDetailsService)//登录逻辑交给哪个对象
       .tokenRepository(repository) //持久层对象
       .tokenValiditySeconds(30); //保存时间,单位:秒

3、在登录页面添加“记住我”复选框

<form class="form" action="/login" method="post">
    <input type="text" placeholder="用户名" name="username">
    <input type="password" placeholder="密码" name="password">
    <input type="checkbox" name="remember-me" value="true"/>记住我</br>
    <button type="submit">登录</button>
</form>

Spring Security授权_RBAC

2345_image_file_copy_56.jpg

授权即认证通过后,系统给用户赋予一定的权限,用户只能根据权 限访问系统中的某些资源。RBAC是业界普遍采用的授权方式,它有 两种解释:

Role-Based Access Control

基于角色的访问控制,即按角色进行授权。比如在企业管理系统 中,主体角色为总经理可以查询企业运营报表。逻辑为:

if(主体.hasRole("总经理角色")){
 查询运营报表
}

如果查询企业运营报表的角色变化为总经理和股东,此时就需要修 改判断逻辑代码:

if(主体.hasRole("总经理角色") || 主体.hasRole("股东角色")){
     查询运营报表
}

此时我们可以发现,当需要修改角色的权限时就需要修改授权的相 关代码,系统可扩展性差。

Resource-Based Access Control

基于资源的访问控制,即按资源(或权限)进行授权。比如在企业 管理系统中,用户必须 具有查询报表权限才可以查询企业运营报 表。逻辑为:

if(主体.hasPermission("查询报表权限")){
   查询运营报表
}

这样在系统设计时就已经定义好查询报表的权限标识,即使查询报 表所需要的角色变化为总经理和股东也不需要修改授权代码,系统 可扩展性强。该授权方式更加常用。

Spring Security授权_权限表设计

2345_image_file_copy_57.jpg

用户和权限的关系为多对多,即用户拥有多个权限,权限也属于多 个用户,所以建表方式如下:

2345_image_file_copy_58.jpg

这种方式需要指定用户有哪些权限,如:张三有查询工资的权限, 即在用户权限中间表中添加一条数据,分别记录张三和查询工资权 限ID。但在系统中权限数量可能非常庞大,如果一条一条添加维护 数据较为繁琐。所以我们通常的做法是再加一张角色表:

2345_image_file_copy_59.jpg

用户角色,角色权限都是多对多关系,即一个用户拥有多个角色, 一个角色属于多个用户;一个角色拥有多个权限,一个权限属于多 个角色。这种方式需要指定用户有哪些角色,而角色又有哪些权限。

如:张三拥有总经理的角色,而总经理拥有查询工资、查询报表的 权限,这样张三就拥有了查询工资、查询报表的权限。这样管理用 户时只需管理少量角色,而管理角色时也只需要管理少量权限即可。接下来我们创建五张表:

CREATE TABLE `users`  (`uid` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`uid`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3
  CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `users` VALUES (1, 'baizhan','$2a$10$Eqv9PRMl6bPt5BiwgPr2eucgyl.E.xLENt4b
vfDvv7DyS5AVPT.U6', '13812345678');
CREATE TABLE `role`  (
  `rid` int(11) NOT NULL AUTO_INCREMENT,
  `roleName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `roleDesc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`rid`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4
CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `role` VALUES (1,'总经理','管理整个公司');
INSERT INTO `role` VALUES (2,'股东','参与公司决策');
INSERT INTO `role` VALUES (3,'财务','管理公司资产');
CREATE TABLE `permission`  (
  `pid` int(11) NOT NULL AUTO_INCREMENT,
  `permissionName` varchar(255) CHARACTER
SET utf8 COLLATE utf8_general_ci NULL
DEFAULT NULL,
  `url` varchar(255) CHARACTER SET utf8
COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`pid`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4
CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `permission` VALUES (1,'查询报表', '/reportform/find');
INSERT INTO `permission` VALUES (2,'查询工资', '/salary/find');
INSERT INTO `permission` VALUES (3,'查询税务', '/tax/find');
CREATE TABLE `users_role`  (
  `uid` int(255) NOT NULL,
  `rid` int(11) NOT NULL,
  PRIMARY KEY (`uid`, `rid`) USING BTREE,
  INDEX `rid`(`rid`) USING BTREE,
  CONSTRAINT `users_role_ibfk_1` FOREIGN KEY(`uid`) REFERENCES `users` (`uid`) ON DELETE
RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT `users_role_ibfk_2` FOREIGN KEY(`rid`) REFERENCES `role` (`rid`) ON DELETE
RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8
COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `users_role` VALUES (1, 2);
INSERT INTO `users_role` VALUES (1, 3);
CREATE TABLE `role_permission`  (
  `rid` int(11) NOT NULL,
  `pid` int(11) NOT NULL,
   PRIMARY KEY (`rid`, `pid`) USING BTREE,
   INDEX `pid`(`pid`) USING BTREE,
   CONSTRAINT `role_permission_ibfk_1` FOREIGN KEY (`rid`) REFERENCES `role`
(`rid`) ON DELETE RESTRICT ON UPDATE
RESTRICT,
  CONSTRAINT `role_permission_ibfk_2`
FOREIGN KEY (`pid`) REFERENCES `permission`
(`pid`) ON DELETE RESTRICT ON UPDATE
RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8
COLLATE = utf8_general_ci ROW_FORMAT =
Dynamic;
INSERT INTO `role_permission` VALUES (1, 1);
INSERT INTO `role_permission` VALUES (2, 1);
INSERT INTO `role_permission` VALUES (1, 2);
INSERT INTO `role_permission` VALUES (3, 2);
INSERT INTO `role_permission` VALUES (1, 3);
INSERT INTO `role_permission` VALUES (2, 3);

Spring Security授权_编写查询权限方法

2345_image_file_copy_60.jpg

在认证后进行授权需要根据用户id查询到用户的权限,写法如下:

1、编写用户、角色、权限实体类

// 不要命名为User,避免和Spring Security提供的User混淆
@Data
public class Users {
    private Integer uid;
    private String username;
    private String password;
    private String phone;
}
// 角色
@Data
public class Role {
    private String rid;
    private String roleName;
    private String roleDesc;
}
// 权限
@Data
public class Permission {
    private String pid;
    private String permissionName;
    private String url;
}

2、编写UserMapper接口

// 根据用户名查询权限
List<Permission> findPermissionByUsername(String username);

3、在resources目录中编写UsersMapper的映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itbaizhan.myspringsecurity.mapper.UsersMapper">
<select id="findPermissionByUsername" parameterType="string" resultType="com.itbaizhan.myspringsecurity.domain.Permission">
       SELECT DISTINCT permission.pid,permission.permissionName,permission.url FROM
       users
       LEFT JOIN users_role on users.uid = users_role.uid
       LEFT JOIN role on users_role.rid = role.rid
       LEFT JOIN role_permission on role.rid = role_permission.rid
       LEFT JOIN permission on role_permission.pid = permission.pid
       where username = #{username}
    </select>
</mapper>

4、测试方法

@SpringBootTest
public class UsersMapperTest {
    @Autowired
    private UsersMapper usersMapper;
    @Test
    public void testFindPermissionByUsername(){
        List<Permission> baizhan = usersMapper.findPermissionByUsername("baizhan");
        baizhan.forEach(System.out::println);
   }
}

5、修改认证逻辑,认证成功后给用户授权

// 自定义认证逻辑
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    // 1.构造查询条件
    QueryWrapper<Users> wrapper = new QueryWrapper<Users>().eq("username",username);
    // 2.查询用户
    Users users = userMapper.selectOne(wrapper);
    if (users == null){
        return null;
   }
    // 3.查询用户权限
    List<Permission> permissions = userMapper.findPermissionByUsername(username);
    // 4.将自定义权限集合转为Security的权限类型集合
    List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
    for (Permission permission : permissions) {
        grantedAuthorities.add(new SimpleGrantedAuthority(permission.getUrl()));
   }
    // 5.封装为UserDetails对象
    UserDetails userDetails = User.withUsername(users.getUsername())
       .password(users.getPassword())
       .authorities(grantedAuthorities)
       .build();
    // 6.返回封装好的UserDetails对象
       return userDetails;
}

Spring Security授权_配置类设置访问控制

2345_image_file_copy_61.jpg

在给用户授权后,我们就可以给系统中的资源设置访问控制,即拥 有什么权限才能访问什么资源。

1、编写控制器类,添加控制器方法资源

@RestController
public class MyController {
    @GetMapping("/reportform/find")
    public String findReportForm() {
        return "查询报表";
   }
    @GetMapping("/salary/find")
    public String findSalary() {
        return "查询工资";
   }
    @GetMapping("/staff/find")
    public String findStaff() {
        return "查询员工";
   }
}

2、修改Security配置类

// 权限拦截配置
http.authorizeRequests()
       .antMatchers("/login.html").permitAll() //表示任何权限都可以访问
       .antMatchers("/reportform/find").hasAnyAuthority("/reportform/find") // 给资源配置需要的权限
       .antMatchers("/salary/find").hasAnyAuthority("/salary/find")
       .antMatchers("/staff/find").hasAnyAuthority("/staff/find")
       .anyRequest().authenticated();  //表示任何请求都需要认证后才能访问

3、测试访问资源,由于没有权限被拦截访问时会抛出403异常

Spring Security授权_自定义访问控制逻辑

如果资源数量很多,一条条配置需要的权限效率较低。我们可以自 定义访问控制逻辑,即访问资源时判断用户是否具有名为该资源 URL的权限。

1、自定义访问控制逻辑

@Service
public class MyAuthorizationService {
    // 自定义访问控制逻辑,返回值为是否可以访问资源
    public boolean hasPermission(HttpServletRequest request,Authentication authentication){
        // 获取会话中的登录用户
        Object principal = authentication.getPrincipal();
        if (principal instanceof UserDetails){
            // 获取登录用户的权限
            Collection<? extends GrantedAuthority> authorities = ((UserDetails)principal).getAuthorities();
            // 获取请求的URL路径
            String uri = request.getRequestURI();
            // 将URL路径封装为权限对象
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(uri);
            // 判断用户的权限集合是否包含请求的URL权限对象
            return authorities.contains(authority);
       }
        return false;
   }
}

2、在配置文件中使用自定义访问控制逻辑

// 权限拦截配置
http.authorizeRequests()
               .antMatchers("/login.html").permitAll() //表示任何权限都可以访问
                // 任何请求都使用自定义访问控制逻辑
               .anyRequest().access("@myAuthorizationService.hasPermission(request,authentication)"
);

Spring Security授权_注解设置访问控制

2345_image_file_copy_62.jpg

除了配置类,在SpringSecurity中提供了一些访问控制的注解。这 些注解默认都是不可用的,需要开启后使用。

@Secured

该注解是基于角色的权限控制,要求UserDetails中的权限名必须以 ROLE_ 开头。

1、在配置类开启注解使用

@SpringBootApplication
@MapperScan("com.itbaizhan.mysecurity.mapper")
@EnableGlobalMethodSecurity(securedEnabled=true)
public class MysecurityApplication {
    public static void main(String[] args)
   {
      SpringApplication.run(MysecurityApplication.class, args);
   }
}

2、在控制器方法上添加注解

@Secured("ROLE_reportform")
@GetMapping("/reportform/find")
public String findReportForm() {
    return "查询报表";
}

@PreAuthorize

该注解可以在方法执行前判断用户是否具有权限

1、在配置类开启注解使用

@SpringBootApplication
@MapperScan("com.itbaizhan.mysecurity.mapper")
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MysecurityApplication {
    public static void main(String[] args)
    {
      SpringApplication.run(MysecurityApplication.class, args);
   }
}

2、在控制器方法上添加注解

@PreAuthorize("hasAnyAuthority('/reportform/find')")
@GetMapping("/reportform/find")
public String findReportForm() {
    return "查询报表";
}

Spring Security授权_在前端进行访问控制

2345_image_file_copy_63.jpg

SpringSecurity可以在一些视图技术中进行控制显示效果。例如 Thymeleaf中,只有登录用户拥有某些权限才会展示一些菜单。

1、在pom中引入Spring Security和Thymeleaf的整合依赖

<!--Spring Security整合Thymeleaf-->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

2、在Thymeleaf中使用Security标签,控制前端的显示内容

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
    xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title>主页面</title>
</head>
<body>
<h1>主页面</h1>
<ul>
    <li sec:authorize="hasAnyAuthority('/reportform/find')">
      <a href="/reportform/find">查询报表</a></li>
    <li sec:authorize="hasAnyAuthority('/salary/find')">
      <a href="/salary/find">查询工资</a></li>
    <li sec:authorize="hasAnyAuthority('/staff/find')">
      <a href="/staff/find">查询员工</a>
    </li>
</ul>
     <a href="/logout">退出登录</a>
</body>
</html>

3、这样面对不同权限的用户,前端可以显示不同的菜单

Spring Security授权_403处理方案

2345_image_file_copy_64.jpg

使用Spring Security时经常会看见403(无权限),这样的页面很 不友好,我们可以自定义403异常处理方案:

1、编写权限不足页面 noPermission.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>权限不足</title>
</head>
<body>
  <h1>您的权限不足,请联系管理员!</h1>
</body>
</html>

2、编写权限不足处理类

public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
      response.sendRedirect("/noPermission.html");
   }
}

3、在Spring Security配置文件中配置异常处理

//异常处理
http.exceptionHandling().
                accessDeniedHandler(new MyAccessDeniedHandler());


相关实践学习
消息队列+Serverless+Tablestore:实现高弹性的电商订单系统
基于消息队列以及函数计算,快速部署一个高弹性的商品订单系统,能够应对抢购场景下的高并发情况。
云安全基础课 - 访问控制概述
课程大纲 课程目标和内容介绍视频时长 访问控制概述视频时长 身份标识和认证技术视频时长 授权机制视频时长 访问控制的常见攻击视频时长
目录
打赏
0
3
0
0
62
分享
相关文章
什么是JWT?如何使用Spring Boot Security实现它?
什么是JWT?如何使用Spring Boot Security实现它?
1286 5
Spring AI 实战|Spring AI入门之DeepSeek调用
本文介绍了Spring AI框架如何帮助Java开发者轻松集成和使用大模型API。文章从Spring AI的初探开始,探讨了其核心能力及应用场景,包括手动与自动发起请求、流式响应实现打字机效果,以及兼容不同AI服务(如DeepSeek、通义千问)的方法。同时,还详细讲解了如何在生产环境中添加监控以优化性能和成本管理。通过Spring AI,开发者可以简化大模型调用流程,降低复杂度,为企业智能应用开发提供强大支持。最后,文章展望了Spring AI在未来AI时代的重要作用,鼓励开发者积极拥抱这一技术变革。
792 71
Spring AI 实战|Spring AI入门之DeepSeek调用
微服务——SpringBoot使用归纳——为什么学习Spring Boot
本文主要探讨为什么学习Spring Boot。从Spring官方定位来看,Spring Boot旨在快速启动和运行项目,简化配置与编码。其优点包括:1) 良好的基因,继承了Spring框架的优点;2) 简化编码,通过starter依赖减少手动配置;3) 简化配置,采用Java Config方式替代繁琐的XML配置;4) 简化部署,内嵌Tomcat支持一键式启动;5) 简化监控,提供运行期性能参数获取功能。此外,从未来发展趋势看,微服务架构逐渐成为主流,而Spring Boot作为官方推荐技术,与Spring Cloud配合使用,将成为未来发展的重要方向。
103 0
微服务——SpringBoot使用归纳——为什么学习Spring Boot
Spring Security 实战指南:从入门到精通
本文详细介绍了Spring Security在Java Web项目中的应用,涵盖登录、权限控制与安全防护等功能。通过Filter Chain过滤器链实现请求拦截与认证授权,核心组件包括AuthenticationProvider和UserDetailsService,负责用户信息加载与密码验证。文章还解析了项目结构,如SecurityConfig配置类、User实体类及自定义登录逻辑,并探讨了Method-Level Security、CSRF防护、Remember-Me等进阶功能。最后总结了Spring Security的核心机制与常见配置,帮助开发者构建健壮的安全系统。
206 0
【Spring】【事务】初学者直呼学会了的Spring事务入门
本文深入解析了Spring事务的核心概念与使用方法。Spring事务是一种数据库事务管理机制,通过确保操作的原子性、一致性、隔离性和持久性(ACID),维护数据完整性。文章详细讲解了声明式事务(@Transactional注解)和编程式事务(TransactionTemplate、PlatformTransactionManager)的区别与用法,并探讨了事务传播行为(如REQUIRED、REQUIRES_NEW等)及隔离级别(如READ_COMMITTED、REPEATABLE_READ)。
114 1
Java 21 与 Spring Boot 3.2 微服务开发从入门到精通实操指南
《Java 21与Spring Boot 3.2微服务开发实践》摘要: 本文基于Java 21和Spring Boot 3.2最新特性,通过完整代码示例展示了微服务开发全流程。主要内容包括:1) 使用Spring Initializr初始化项目,集成Web、JPA、H2等组件;2) 配置虚拟线程支持高并发;3) 采用记录类优化DTO设计;4) 实现JPA Repository与Stream API数据访问;5) 服务层整合虚拟线程异步处理和结构化并发;6) 构建RESTful API并使用Springdoc生成文档。文中特别演示了虚拟线程配置(@Async)和StructuredTaskSco
110 0
|
3月前
|
Spring框架的学习与应用
总的来说,Spring框架是Java开发中的一把强大的工具。通过理解其核心概念,通过实践来学习和掌握,你可以充分利用Spring框架的强大功能,提高你的开发效率和代码质量。
104 20
|
3月前
|
Spring Security 入门与详解
Spring Security 是 Spring 框架中的核心安全模块,提供认证、授权及防护功能。本文详解其核心概念,包括认证(Authentication)、授权(Authorization)和过滤器链(Security Filter Chain)。同时,通过代码示例介绍基本配置,如 PasswordEncoder、UserDetailsService 和自定义登录页面等。最后总结常见问题与解决方法,助你快速掌握 Spring Security 的使用与优化。
506 0
Spring Security 入门
Spring Security 是 Spring 框架中的安全模块,提供强大的认证和授权功能,支持防止常见攻击(如 CSRF 和会话固定攻击)。它通过过滤器链拦截请求,核心概念包括认证、授权和自定义过滤器。配置方面,涉及密码加密、用户信息服务、认证提供者及过滤器链设置。示例代码展示了如何配置登录、注销、CSRF防护等。常见问题包括循环重定向、静态资源被拦截和登录失败未返回错误信息,解决方法需确保路径正确和添加错误提示逻辑。
402 2
Spring Security 入门
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问