Spring Boot整合Spring Security

Spring Boot 专栏:Spring Boot 从零单排
Spring Cloud 专栏:Spring Cloud 从零单排
GitHub:SpringBootDemo
Gitee:SpringBootDemo

Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块的默认技术选型,仅需引入spring-boot-starter-security模块,进行少量配置,即可实现强大的Web安全控制。

Spring Security的两个主要目标是认证授权(访问控制)

官方文档:https://docs.spring.io/spring-security/site/docs/5.2.0.RELEASE/reference/htmlsingle/

0 开发环境

  • JDK:1.8
  • Spring Boot:2.7.18

Spring Boot 版本升级为2.7.18,专栏中其他Spring Boot相关环境同步升级

1 引入依赖

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

2 测试

2.1 新建Controller

@RestController
@RequestMapping("user")
public class UserController {@GetMapping(value = "query")public String query() {return "用户查询成功";}
}

2.2 测试

启动服务,浏览器访问 127.0.0.1:8090/user/query,页面自动跳转到授权登录页

在这里插入图片描述

默认用户名为user,控制台上会打印默认密码,默认密码每次启动服务都会刷新

在这里插入图片描述

登录成功后,就可以正常访问了

在这里插入图片描述

3 自定义密码

该部分会使用到Spring Security的几个关键类,如下:

  • WebSecurityConfigurerAdapter 自定义Security策略
  • AuthenticationManagerBuilder 自定义认证策略
  • @EnableWebSecurity 开启WebSecurity模式

3.1 通过application.yml 配置

spring:security:user:name: adminpassword: 123456

3.2 自定义配置类配置

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码加密BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();//配置用户名、密码,该配置方式下,用户名和密码保存在内存中auth.inMemoryAuthentication()//密码加密方式.passwordEncoder(new BCryptPasswordEncoder()).withUser("admin").password(passwordEncoder.encode("123456")).roles("admin");}
}

3.3 自定义实现类配置

3.3.1 编写UserDetailsService实现类

这里我们就直接固定写死用户名和密码,实际生产中可以从数据库中获取

@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {//设置角色,角色的概念后续介绍List<GrantedAuthority> roles = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");return new User("admin", new BCryptPasswordEncoder().encode("123456"), roles);}
}

3.3.2 编写配置类

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsServiceImpl userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//使用UserDetailsServiceImpl 查询用户名、密码auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());}
}

分别测试,都通过

4 用户认证和授权/基于角色和权限的访问控制

实际生产中,需要根据用户角色的权限来控制可访问的页面、可执行的操作等

4.1 新建4个页面

level-1.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1 style="color: red">这是用户等级1可访问的页面</h1>
</body>
</html>

level-2.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1 style="color: green">这是用户等级2可访问的页面</h1>
</body>
</html>

level-3.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1 style="color: blue">这是用户等级3可访问的页面</h1>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<a href="level-1.html">等级1</a><br><br>
<a href="level-2.html">等级2</a><br><br>
<a href="level-3.html">等级3</a>
</body>
</html>

4.2 修改配置类

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {//请求授权的规则//开启认证http.authorizeRequests()//首页所有人可访问.antMatchers("/").permitAll()//功能页对应角色或权限才能访问//hasRole 为角色授权,表示用户拥有指定角色//hasAuthority 为权限授权,表示用户拥有指定权限.antMatchers("/level-1.html").hasRole("level1").antMatchers("/level-2.html").hasRole("level2").antMatchers("/level-3.html").hasAuthority("level3");//开启登录,无权限时进入登录页面http.formLogin();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//密码加密BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();//配置用户名、密码,该配置方式下,用户名和密码保存在内存中auth.inMemoryAuthentication()//密码加密方式.passwordEncoder(new BCryptPasswordEncoder()).withUser("admin").password(passwordEncoder.encode("123456")).roles("admin").and().withUser("admin1").password(passwordEncoder.encode("123456")).roles("level1").and().withUser("admin2").password(passwordEncoder.encode("123456")).roles("level2").and().withUser("admin3").password(passwordEncoder.encode("123456")).authorities("level3").and().withUser("admin0").password(passwordEncoder.encode("123456")).authorities("ROLE_level1", "ROLE_level2", "level3");}
}

**hasRole()hasAuthority()**用法是类似的,只不过hasRole()方法会给自定义的角色名前加上 ROLE_ 前缀

在这里插入图片描述

因此在自定义用户时,如果使用**authorities()给用户设置角色时,需要自行添加上ROLE_**前缀。

roles()authorities()设置的角色或权限,最终都存放在authorities参数中,且这两个方法会互相覆盖彼此的值。

在这里插入图片描述

4.3 测试

浏览器访问

在这里插入图片描述

依次点击等级1、等级2、等级3,均自动跳转到授权登录页面,登录对应权限的用户后,可成功访问。

其中,登录admin用户,无法访问任何页面,登录admin0,可访问所有页面

在这里插入图片描述

登录权限不匹配的用户,拒绝访问

在这里插入图片描述

4.4 使用UserDetailsService类实现

UserDetailsServiceImpl

@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {List<GrantedAuthority> roles;if ("admin1".equals(s)) {roles = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_level1");} else if ("admin2".equals(s)) {roles = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_level2");} else if ("admin3".equals(s)) {roles = AuthorityUtils.commaSeparatedStringToAuthorityList("level3");} else if ("admin0".equals(s)) {roles = AuthorityUtils.createAuthorityList("ROLE_level1", "ROLE_level2", "level3");} else {roles = AuthorityUtils.createAuthorityList("admin");}return new User(s, new BCryptPasswordEncoder().encode("123456"), roles);}
}

SecurityConfig

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserDetailsServiceImpl userDetailsService;@Overrideprotected void configure(HttpSecurity http) throws Exception {//请求授权的规则//开启认证http.authorizeRequests()//首页所有人可访问.antMatchers("/").permitAll()//功能页对应角色或权限才能访问//hasRole 为角色授权,表示用户拥有指定角色//hasAuthority 为权限授权,表示用户拥有指定权限.antMatchers("/level-1.html").hasRole("level1").antMatchers("/level-2.html").hasRole("level2").antMatchers("/level-3.html").hasAuthority("level3");//开启登录,无权限时进入登录页面http.formLogin();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//使用UserDetailsServiceImpl 查询用户名、密码auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());}
}

浏览器访问,测试,通过

5 常用注解

可控制用户认证访问接口

5.1 @Secured()

校验用户具有某个角色,才可访问接口

需在启动类开启注解

@EnableGlobalMethodSecurity(securedEnabled = true)

然后在接口方法上配置注解

@RestController
@RequestMapping("user")
public class UserController {@GetMapping(value = "query")@Secured("ROLE_level1")public String query() {return "用户查询成功";}@GetMapping(value = "update")@Secured({"ROLE_level1", "ROLE_level2"})public String update() {return "用户更新成功";}
}

5.2 @PreAuthorize()

在进入方法前校验用户具有某个权限或角色

需在启动类开启注解

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

然后在接口方法上配置注解

@RestController
@RequestMapping("user")
public class UserController {@GetMapping(value = "delete")@PreAuthorize("hasAnyAuthority('ROLE_level1','level3')")public String delete() {return "用户删除成功";}
}

5.3 @PostAuthorize()

在进入方法后校验用户具有某个权限或角色

需在启动类开启注解

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

然后在接口方法上配置注解

@RestController
@RequestMapping("user")
public class UserController {@GetMapping(value = "delete")@PostAuthorize("hasAnyAuthority('ROLE_level1','level3')")public String delete() {return "用户删除成功";}
}

5.4 @PostFilter()

校验权限后对数据进行过滤,只返回满足条件的数据

新建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserModel {private String username;private String password;
}

然后在方法上加上注解

@RestController
@RequestMapping("user")
public class UserController {@GetMapping(value = "queryList")@PreAuthorize("hasAnyAuthority('ROLE_level1','level3')")@PostFilter("filterObject.username == 'test'")public List<UserModel> queryList() {List<UserModel> userList = new ArrayList<>();userList.add(new UserModel("test", "qwerty"));userList.add(new UserModel("test2", "asdfgh"));userList.add(new UserModel("test3", "zxcvbn"));return userList;}
}

测试,权限验证通过后

在这里插入图片描述

5.5 @PreFilter()

校验权限后对数据进行过滤,只有满足条件的数据才能传入接口方法中

@RestController
@RequestMapping("user")
public class UserController {@PostMapping(value = "queryUser")@PreAuthorize("hasAnyAuthority('ROLE_level1','level3')")@PreFilter("filterObject.username == 'test2'")public List<UserModel> queryUser(@RequestBody List<UserModel> userModels) {return userModels;}
}

测试,权限验证通过后

在这里插入图片描述

6 记住我

配置类中开启记住我

    @Overrideprotected void configure(HttpSecurity http) throws Exception {//请求授权的规则//开启认证http.authorizeRequests()//首页所有人可访问.antMatchers("/").permitAll()//功能页对应角色或权限才能访问//hasRole 为角色授权,表示用户拥有指定角色//hasAuthority 为权限授权,表示用户拥有指定权限.antMatchers("/level-1.html").hasRole("level1").antMatchers("/level-2.html").hasRole("level2").antMatchers("/level-3.html").hasAuthority("level3");//开启登录,无权限时进入登录页面http.formLogin();//记住我http.rememberMe();}

启动服务,访问页面,登录页面增加了记住我选择框

在这里插入图片描述

登录成功后,cookie中已保存用户信息,默认时间为2周

在这里插入图片描述

7 注销

7.1 配置类中开启注销

    @Overrideprotected void configure(HttpSecurity http) throws Exception {//请求授权的规则//开启认证http.authorizeRequests()//首页所有人可访问.antMatchers("/").permitAll()//功能页对应角色或权限才能访问//hasRole 为角色授权,表示用户拥有指定角色//hasAuthority 为权限授权,表示用户拥有指定权限.antMatchers("/level-1.html").hasRole("level1").antMatchers("/level-2.html").hasRole("level2").antMatchers("/level-3.html").hasAuthority("level3");//开启登录,无权限时进入登录页面http.formLogin();//记住我http.rememberMe();//开启注销,注销成功后回首页http.logout().logoutSuccessUrl("/");}

7.2 level-* 页面增加注销按钮

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h1 style="color: red">这是用户等级1可访问的页面</h1>
<br><br>
<a href="/logout">注销</a>
</body>
</html>

其他两个页面做相同修改

启动服务,登录成功后点击注销按钮,注销成功,返回首页,访问页面需再次登录

8 自定义登录页

8.1 新建登录页login.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><meta charset="UTF-8"><title>Login Page</title><style>body {font-family: Arial, sans-serif;background-color: #f5f5f5;}.container {width: 300px;margin: auto;padding: 40px;border: 1px solid #ccc;background-color: white;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);}h2 {text-align: center;}label {display: block;margin-bottom: 10px;}input[type="text"],input[type="password"] {width: 100%;padding: 6px;border: 1px solid #ccc;outline: none;}button {width: 100%;padding: 10px;color: white;background-color: #4CAF50;cursor: pointer;border: none;outline: none;}button:hover {opacity: 0.9;}</style>
</head>
<body>
<div class="container"><h2>登录</h2><form action="/login" method="post"><label for="username">用户名</label><input type="text" id="username" name="username"><br><br><label for="password">密码</label><input type="password" id="password" name="password"><br><br><input type="checkbox" name="remember-me" title="记住我">记住我<br><br><button type="submit">登 录</button></form>
</div>
</body>
</html>

8.2 配置类中开启自定义登录页

    @Overrideprotected void configure(HttpSecurity http) throws Exception {//请求授权的规则//开启认证http.authorizeRequests()//首页所有人可访问.antMatchers("/").permitAll()//功能页对应角色或权限才能访问//hasRole 为角色授权,表示用户拥有指定角色//hasAuthority 为权限授权,表示用户拥有指定权限.antMatchers("/level-1.html").hasRole("level1").antMatchers("/level-2.html").hasRole("level2").antMatchers("/level-3.html").hasAuthority("level3");//开启登录,无权限时进入登录页面//自定义登录页http.formLogin().loginPage("/login.html").loginProcessingUrl("/login");//关闭csrf防护http.csrf().disable();//记住我http.rememberMe();//开启注销,注销成功后回首页http.logout().logoutSuccessUrl("/");}

这里,自定义登录,默认用户参数是username,默认密码参数是password,默认记住我参数是remember-me,如果需要自定义登录表单的参数,做如下修改

        //开启登录,无权限时进入登录页面http.formLogin().loginPage("/login.html").loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password");//关闭csrf防护http.csrf().disable();//记住我http.rememberMe().rememberMeParameter("remember-me");

启动服务,访问地址,跳转到自定义登录页

在这里插入图片描述

9 自定义403页面

9.1 新建403.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
权限不足,无法访问
</body>
</html>

9.2 配置类中开启自定义403页面

    @Overrideprotected void configure(HttpSecurity http) throws Exception {//...其他代码...//自定义403页面http.exceptionHandling().accessDeniedPage("/403.html");}

启动服务,浏览器访问,登录无权限用户后提示

在这里插入图片描述

至此,Spring Boot整合Spring Security实现用户认证和授权基本用法已讲解完毕,且测试通过。

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

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

相关文章

Netty学习——源码篇6 Pipeline设计原理

1 Pipeline设计原理 在Netty中每个Channel都有且仅有一个ChannelPipeline与之对应&#xff0c;它们的组成关系如下图&#xff1a; 通过上图可以看到&#xff0c;一个Channel包含了一个ChannelPipeline&#xff0c;而ChannelPipeline中又维护了一个由ChannelHandlerContext组成的…

【笔记】深入理解JVM机制

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 JVM 运⾏流程图 JVM 中内存区域划分 方法区 / 元数据区 堆 栈 程序计数器 本地方法栈 内存区域总结 JVM 中类加载过程 …

C++ STL- list 的使用以及练习

目录 0.引言 1. list 介绍 2. list 使用 2.1 构造函数 2.2 list iterator 的使用 3 list capacity 4. list element access 5. list modifiers 6. list 迭代器失效 7. list 与vector 对vector 8. OJ 题讲解 删除链表的倒数第 N 个节点&#xff1a; 0.引言 …

Jetson AGX ORIN 配置 FGVC-PIM 神经网络(包含 arm64 下面 torch 和 torchvision 配置内容)

Jetson AGX ORIN 配置 FGVC-PIM 神经网络 文章目录 Jetson AGX ORIN 配置 FGVC-PIM 神经网络配置 ORIN 环境创建 FGVC-PIM 虚拟环境安装 PyTorch安装 torchvision安装其他依赖包 配置 ORIN 环境 首先先配置 ORIN 的环境&#xff0c;可以参考这个链接&#xff1a; Jetson AGX …

Go第三方框架--gin框架(一)

序言 Gin框架作为go语言使用最多的web框架&#xff0c;以其快速的响应速度和对复杂http路由配置的支持受到程序员和媛们的喜爱&#xff0c;几乎统治了web市场。但作为一名合格的程序员&#xff0c;要知其然更要知其所以然&#xff0c;不然八股文背的也没有啥意思。本着这个原则…

JavaScript中的Lexical Environment

概要 本文主要介绍JavaScript中的一个重要概念Lexical Environment&#xff0c;它可以帮助我们解释我们为什么可以通过嵌套方法&#xff0c;共享数据&#xff0c;以及为什么可以在函数中定义一个和全局变量同名的变量&#xff0c;并且不会影响到全局变量。 基本分析 基本概念…

如何使用Python进行网络安全与密码学【第149篇—密码学】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 用Python进行网络安全与密码学&#xff1a;技术实践指南 随着互联网的普及&#xff0c;网络…

几个常用的AI工具

人工智能大模型的出现对人类社会产生了深远的影响&#xff0c;这些影响既包括积极的方面&#xff0c;也包括一些潜在的挑战: 1. **提高效率**&#xff1a;AI大模型能够快速处理大量数据&#xff0c;提高工作效率&#xff0c;尤其在数据分析、自然语言处理等领域。 2. **辅助决…

面向对象【枚举类】

文章目录 枚举类定义枚举类enum 方式定义的要求和特点 enum 中常用方法实现接口的枚举类 枚举类 枚举类是一种特殊的类&#xff0c;它用于定义一组固定数量的常量。枚举类在实际开发中非常有用&#xff0c;因为它们可以增加代码的可读性和可维护性。本文将介绍Java枚举类的定义…

Java基于微信小程序的校园请假系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#…

【Java程序设计】【C00370】基于(JavaWeb)Springboot的公司进存销管理系统(有论文)

TOC 博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;博客中有上百套程序可供参考&#xff0c;欢迎共同交流学习。 项目简介 项目获取 &#x1f345;文末点击卡片…

视频记录历史播放位置效果

简介 每次打开页面视频从上一次的播放位置开始播放 利用lodash库做节流 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-sca…

UI界面设计是什么?一篇文章带你全面了解

伴随着因特网的飞速发展&#xff0c;很多与因特网相关的职位都衍生出来了&#xff0c;UI 界面设计师是因特网的核心职位之一。UI 界面设计已经渗透到我们生活的各个方面&#xff0c;包括网站、应用程序或其它数字平台上的按钮、菜单布局、配色方案和排版等。很多人认为 UI 界面…

具有徊滞特性的欠压锁定功能的B3842/43/44是专为脱线和Dc-Dc开关电源应用设计的

B3842/43/44是专为脱线和Dc-Dc开关电源应用设计的恒频电流型Pwd控制器内部包含温度补偿精密基准、供精密占空比调节用的可调振荡器、高增益混放大器、电流传感比较器和适合作功率MOST驱动用的大电流推挽输出颇以及单周期徊滞式限流欠压锁定、死区可调、单脉冲计数拴锁等保护电路…

【Java程序设计】【C00369】基于(JavaWeb)Springboot的笔记记录分享平台(有论文)

[TOC]() 博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;博客中有上百套程序可供参考&#xff0c;欢迎共同交流学习。 项目简介 项目获取 &#x1f345;文末点击…

BSV区块链的应用开发前景——通过标准化来促进创新

​​发表时间&#xff1a;2024年3月5日 近年来区块链领域的发展日新月异&#xff0c;各种全新的技术和方法论正在迅猛涌现。在这个瞬息万变的环境之中&#xff0c;标准化不仅仅会为开发者们带来便利&#xff0c;同时也促进了应用之间的互操作性&#xff0c;并且推动着生态系统的…

SAP ABAP-BOPF基础训练-01简介与架构

1. 介绍-Introduction ① BOPF是什么&#xff1f;BOPF(the Business Object Processing Framework)&#xff1a;业务对象处理框架 提供了一种增量和模块化的方法&#xff0c;以符合企业面向服务体系结构(eSOA)的方式实现业务对象&#xff1b; 部分平台基础层&#xff0c;软件组…

UI设计师必备软件:2024年趋势解读!

设计的两个关键方面是用户界面 (UI) 和用户体验 (UX)&#xff0c;UI设计侧重于人们如何与产品互动的审美元素&#xff0c;UX设计更侧重于人们如何使用产品&#xff0c;无论你的重点是什么 UX 还是 UI&#xff0c;或者你是否试图将两者结合起来&#xff0c;你需要高质量的UI设计…

【C++】share_ptr详解

一、share_ptr 的简单使用 1.1、基本用法 从较浅的层面看&#xff0c;智能指针是利用了一种叫做RAII&#xff08;资源获取即初始化&#xff09;的技术对普通的指针进行封装&#xff0c;这使得智能指针实质是一个对象&#xff0c;行为表现的却像一个指针。 智能指针的作用是防…

【MySQL数据库】数据类型和简单的增删改查

目录 数据库 MySQL的常用数据类型 1.数值类型&#xff1a; 2.字符串类型 3.日期类型 MySQL简单的增删改查 1.插入数据&#xff1a; 2.查询数据&#xff1a; 3.修改语句&#xff1a; 4.删除语句&#xff1a; 数据库 平时我们使用的操作系统都把数据存储在文件中&#…