初探 Spring Boot Starter Security:构建更安全的Spring Boot应用

引言

Spring Boot 作为 Java 生态系统下的热门框架,以其简洁和易上手著称。而在构建 Web 应用程序时,安全性始终是开发者必须重视的一个方面。Spring Boot Starter Security 为开发者提供了一个简单但功能强大的安全框架,使得实现身份验证和授权变得相对容易。

本文将带你深入了解如何使用 Spring Boot Starter Security 来构建一个安全的 Spring Boot 应用,包括基本配置、常见用例以及一些技巧和最佳实践。

目录

  1. 什么是 Spring Boot Starter Security?
  2. 初始设置
    • 添加依赖
    • 基本配置
  3. 基本概念
    • 认证与授权
    • Filter 和 SecurityContext
  4. 示例:创建一个简单的安全应用
    • 设定用户角色
    • 自定义登录页面
    • 基于角色的访问控制
  5. 高级配置
    • 自定义 UserDetailsService
    • 自定义 Security Configuration
    • 使用 JWT 进行身份验证
  6. 综合示例:构建一个完整的安全应用
    • 项目结构
    • 代码实现
    • 测试和验证
  7. 最佳实践与常见问题
    • 安全最佳实践
    • 常见问题及解决方案
  8. 结论

1. 什么是 Spring Boot Starter Security?

Spring Boot Starter Security 是一个简化的 Spring Security 集成包,使得我们可以非常容易地在 Spring Boot 应用中添加强大的安全功能。它提供了一套灵活的工具和配置,用于实现认证和授权,使得应用程序更加安全。

2. 初始设置

添加依赖

首先,我们需要在 pom.xml 文件中添加 Spring Boot Starter Security 的依赖:

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

基本配置

在添加依赖后,Spring Security 会自动为我们的应用添加一些默认的安全配置,例如 HTTP Basic Authentication(基于 HTTP 的基础身份验证)。这意味着,我们可以立即看到应用要求用户进行身份验证。

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

此时,运行应用后,您会看到 Spring Boot 自动生成了一个密码,并在控制台输出。

3. 基本概念

认证与授权

  • 认证(Authentication):验证用户的身份。
  • 授权(Authorization):确定用户是否有权访问某个资源。

Filter 和 SecurityContext

Spring Security 通过一系列的过滤器(Filters)来处理安全逻辑。这些过滤器会拦截每个请求,并应用相应的认证和授权逻辑。所有安全相关的信息都会被存储在 SecurityContext 中,从而使得后续的请求处理可以基于这些信息进行访问控制。

4. 示例:创建一个简单的安全应用

设定用户角色

我们可以通过创建一个配置类来设定用户角色:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("user").password(passwordEncoder().encode("password")).roles("USER").and().withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN");}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").antMatchers("/user/**").hasRole("USER").and().formLogin();}
}

在上面的配置中,我们创建了两个用户(user 和 admin),并且设置了不同的角色(USER 和 ADMIN)。此外,我们还定义了不同 URL 路径对应的访问权限。

自定义登录页面

我们可以自定义一个登录页面,以增强用户体验:

<!DOCTYPE html>
<html>
<head><title>Login Page</title>
</head>
<body><h2>Login</h2><form method="post" action="/login"><div><label>Username: </label><input type="text" name="username"></div><div><label>Password: </label><input type="password" name="password"></div><div><button type="submit">Login</button></div></form>
</body>
</html>

WebSecurityConfig 中,我们需要指定这个自定义登录页面:

@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").antMatchers("/user/**").hasRole("USER").and().formLogin().loginPage("/login").permitAll();
}

基于角色的访问控制

上述配置已经体现了基于角色的基本访问控制。我们规定了 /admin/** 路径只能由拥有 ADMIN 角色的用户访问,而 /user/** 路径只能由拥有 USER 角色的用户访问。

5. 高级配置

自定义 UserDetailsService

有时候,我们需要从数据库加载用户信息。我们可以通过实现 UserDetailsService 接口来自定义加载用户的逻辑:

@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserRepository userRepository;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username);if (user == null) {throw new UsernameNotFoundException("User not found.");}return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles()));}
}

自定义 Security Configuration

除了基本配置外,有些时候我们需要更灵活的配置。例如,我们可以完全覆盖默认的 Spring Security 配置:

@Configuration
@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate CustomUserDetailsService userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().logout().permitAll();}
}

使用 JWT 进行身份验证

JWT(JSON Web Token)是一种更加轻便的授权机制,我们可以采用它来替代 Session Cookie 进行身份验证。实现 JWT 需要进行以下几步:

  1. 添加 jwt 相关的依赖;
  2. 创建 token 提供者;
  3. 创建过滤器来验证 token ;
添加 JWT 依赖

pom.xml 中添加以下依赖:

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
创建 TokenProvider
@Component
public class TokenProvider {private final String jwtSecret = "yourSecretKey";private final long jwtExpirationMs = 3600000;public String generateToken(Authentication authentication) {String username = authentication.getName();Date now = new Date();Date expiryDate = new Date(now.getTime() + jwtExpirationMs);return Jwts.builder().setSubject(username).setIssuedAt(now).setExpiration(expiryDate).signWith(SignatureAlgorithm.HS512, jwtSecret).compact();}public String getUsernameFromToken(String token) {return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();}public boolean validateToken(String authToken) {try {Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);return true;} catch (SignatureException | MalformedJwtException | ExpiredJwtException | UnsupportedJwtException | IllegalArgumentException e) {e.printStackTrace();}return false;}
}
创建 JWT 过滤器
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {@Autowiredprivate TokenProvider tokenProvider;@Autowiredprivate CustomUserDetailsService customUserDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {try {String jwt = getJwtFromRequest(request);if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {String username = tokenProvider.getUsernameFromToken(jwt);UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authentication);}} catch (Exception ex) {logger.error("Could not set user authentication in security context", ex);}filterChain.doFilter(request, response);}private String getJwtFromRequest(HttpServletRequest request) {String bearerToken = request.getHeader("Authorization");if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {return bearerToken.substring(7);}return null;}
}
调整 Security Configuration
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable().authorizeRequests().antMatchers("/login", "/signup").permitAll().anyRequest().authenticated();http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);}
}

6. 综合示例:构建一个完整的安全应用

接下里,我们将创建一个功能更全的示例应用,结合之前介绍的各种配置,实现用户注册、登录、基于角色的访问控制和 JWT 身份验证。

项目结构

src└── main├── java│    └── com.example.security│         ├── controller│         ├── model│         ├── repository│         ├── security│         ├── service│         └── SecurityApplication.java└── resources├── templates└── application.yml

代码实现

模型类
@Entity
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;private String password;private String roles;  // e.g., "USER, ADMIN"// getters and setters
}
Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {User findByUsername(String username);
}
UserDetailsService 实现
@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserRepository userRepository;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username);if (user == null) {throw new UsernameNotFoundException("User not found.");}return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles()));}
}
安全配置
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;@Autowiredprivate CustomUserDetailsService customUserDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(customUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable().authorizeRequests().antMatchers("/login", "/signup").permitAll().anyRequest().authenticated();http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);}
}
控制器
@RestController
public class AuthController {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate CustomUserDetailsService userDetailsService;@Autowiredprivate TokenProvider tokenProvider;@PostMapping("/login")public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest loginRequest) {Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),loginRequest.getPassword()));SecurityContextHolder.getContext().setAuthentication(authentication);String jwt = tokenProvider.generateToken(authentication);return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));}@PostMapping("/signup")public ResponseEntity<?> registerUser(@RequestBody SignUpRequest signUpRequest) {if(userRepository.existsByUsername(signUpRequest.getUsername())) {return new ResponseEntity<>(new ApiResponse(false, "Username is already taken!"), HttpStatus.BAD_REQUEST);}// Creating user's accountUser user = new User();user.setUsername(signUpRequest.getUsername());user.setPassword(passwordEncoder.encode(signUpRequest.getPassword()));user.setRoles("USER");userRepository.save(user);return ResponseEntity.ok(new ApiResponse(true, "User registered successfully"));}
}

测试和验证

我们已经完成了一个简单但是功能齐全的 Spring Boot 安全应用。可以通过以下步骤进行测试和验证:

  1. 启动应用
  2. 通过 /signup 端点进行用户注册
  3. 通过 /login 端点进行用户登录,并获取 JWT token
  4. 使用获取的 JWT token 访问其他受保护的端点

7. 最佳实践和常见问题

安全最佳实践

  • 使用强加密算法:如 BCryptPasswordEncoder 对密码进行加密存储。
  • 避免硬编码密码或密钥:将敏感信息存储在安全的配置文件或环境变量中。
  • 启用 CSRF 保护:对于需要借助表单提交的应用保持 CSRF 保护。
  • 定期更新依赖:检查依赖库的安全更新,避免使用有已知漏洞的库。
  • 输入验证:在用户输入点进行严格的输入验证,防止XSS和SQL注入等攻击。

常见问题及解决方案

问题1:为什么自定义登录页面不显示?

解决方案:确保在 WebSecurityConfig 中设置了 .loginPage("/login").permitAll(); 并且路径正确。

问题2:身份验证失败,显示 “Bad credentials”。

解决方案:确认用户名和密码是否正确,以及整体加密方式一致。

问题3:为什么 JWT 从请求中提取失败?

解决方案:确认请求头格式是否正确,Authorization: Bearer <token>,并且确保 JWT 过滤器在安全配置中正确添加。

结论

Spring Boot Starter Security 为开发者提供了丰富且灵活的安全配置选项,使得安全性实现变得相对简单。在本文中,我们探讨了基本概念和常见用例,并通过构建一个完整的示例应用,展示了其强大的功能。希望这些内容能帮助你在构建安全的 Spring Boot 应用时游刃有余。

通过对 Spring Boot Starter Security 的深入了解和实践,我们不仅增强了应用的安全性,还为用户提供了更为可靠的使用体验。继续学习和实践,你将在开发和维护安全应用的道路上走得更远。

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

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

相关文章

从动态代理角度简单理解Spring AOP

1. 概述 动态代理 是指在运行时&#xff0c;动态地创建目标类的代理对象&#xff0c;并对其中特定的方法进行拦截或增强的技术。这种技术主要用于在不修改目标类代码的情况下&#xff0c;增强目标类的功能。 在Java中&#xff0c;动态代理主要基于Java的反射机制和接口来实现…

gdc2024:Raytracing in Snowdrop技术实现与性能优化策略

在今年的GDC&#xff08;游戏开发者大会&#xff09;的Advanced Graphics Summit上&#xff0c;关于Snowdrop引擎中光线追踪技术的讨论引起了广泛关注。 一、光线追踪全局照明的实现细节 屏幕空间追踪&#xff1a; 屏幕空间追踪从相机出发&#xff0c;对屏幕上的每个像素点生成…

DDL—表—数据类型—字符串类型相关语法

&#xff08;1&#xff09;表格可视化 普通字符串 类型大小描述CHAR0~255 bytes定长字符串&#xff0c;其表示即使你存储一个字符&#xff0c;它也会占用你括号里个数的字符的空间&#xff0c;因为未占用的字符的其它空间会用空格进行补位。需要再后面跟一个参数&#xff1a;…

harmony 鸿蒙ArkUI动画/交互事件开发常见问题(ArkTS)

ArkUI动画/交互事件开发常见问题(ArkTS) 焦点事件onBlur/onFocus回调无法触发(API 9) 问题现象 焦点事件onBlur/onFocus回调无法触发 解决措施 焦点事件默认情况下需要外接键盘的Tab键&#xff0c;或方向键触发&#xff0c;点击触发焦点事件需要添加焦点控制属性focusOnTo…

Linux 监控USB硬盘插拔事件并自动挂载和卸载

定义udev规则来监控USB插拔事件。 一、在/etc/udev/rules.d目录下随意创建一个规则文件&#xff0c;例&#xff1a;99-usb-mount.rules KERNEL"sd[b-z]?",SUBSYSTEM"block",RUN"/usr/local/src/mountusb.sh %k $env{ACTION}"二、创建 /usr/lo…

基于arm64架构国产操作系统|Linux下的RTMP|RTSP低延时直播播放器开发探究

技术背景 2014年4月8日起&#xff0c;美国微软公司停止了对Windows XP SP3操作系统提供服务支持&#xff0c;这引起了社会和广大用户的广泛关注和对信息安全的担忧。而2020年对Windows7服务支持的终止再一次推动了国产系统的发展。工信部对此表示&#xff0c;将继续加大力度&a…

C++ 红黑树

目录 1.红黑树的概念 2.红黑树的性质 3.红黑树节点的定义 4.红黑树的插入操作 5.数据测试 1.红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上各个…

C++基础与深度解析 | 泛型算法 | bind | Lambda表达式

文章目录 一、泛型算法1.泛型算法的分类2.迭代器分类 二、bind与lambda表达式1.bind2.lambda表达式 三、泛型算法的改进--ranges(c20) 一、泛型算法 C中的泛型算法是标准模板库&#xff08;STL&#xff09;的一部分&#xff08;这里重点讨论 C 标准库中定义的算法&#xff0c;而…

【vue-cli搭建vue项目的过程2.x】

vue-cli搭建vue项目 vue-cli搭建vue项目安装node安装vue-cli脚手架并创建项目安装 Ant Design Vue或element-ui(笔者使用Ant-design-vue组件&#xff0c;并全局引入)开发安装三方库包1、Package.json文件---引入如下package.json文件执行npm i或npm install命令即可下载如下依赖…

数据结构~~链式二叉树

目录 一、基本概念 链式存储概念 二、链式二叉树的结构 链式二叉树结构 构建链式二叉树 二叉树的遍历 二叉树节点和高度等 二叉树销毁 三、链式二叉树的练习 相同的树 对称二叉树 另外一颗子树 二叉树前序遍历 二叉树遍历 四、完整代码 Tree.h Tree.c 五、总结 一…

Linux服务升级:Predixy 升级代理 Redis-cluster 集群

目录 一、实验 1.环境 2. 启动Redis服务 3.Predixy 升级代理 Redis-cluster 集群 二、问题 1. Predixy进行set操作报错 2.如何创建脚本启动predixy 3.Redis代理对比 一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 系统版本节点软件IP备注CentOS7.9Redis…

Springboot开发 -- Postman 调试类型详解

引言 在 Spring Boot 应用开发过程中&#xff0c;接口测试是必不可少的一环。Postman 作为一款强大的 API 开发和测试工具&#xff0c;可以帮助开发者轻松构建、测试和管理 HTTP 请求。本文将为大家介绍如何在 Spring Boot 开发中使用 Postman 进行接口测试。 一、准备工作 安…

C/C++|malloc分配内存详解

看本节前&#xff0c;希望读者有linux内存分布的基本概念&#xff0c;可以阅读这篇文章&#xff1a; 进程虚拟地址空间和函数调用栈 在本节中希望读者可以一口气阅读完所有内容。 本博客内容全部来自小林coding&#xff1a;malloc 是如何分配内存的&#xff1f; 这里仅为笔记记…

Python-图片旋转360,保存对应图片

#Author &#xff1a;susocool #Creattime:2024/5/25 #FileName:turn360 #Description: 会旋转指定的图像文件360度&#xff0c;并将每个旋转后的图像保存到指定目录&#xff0c;文件名以旋转角度命名。 from PIL import Imagedef rotate_and_save(image_path, output_dir) :# …

Linux/Ubuntu 中安装 ZeroTier,实现内网穿透,2分钟搞定

相信很多人都有远程连接家中设备的需求&#xff0c;如远程连接家中的NAS、Windows等服务&#xff0c;所以会涉及到一个内网穿透工具的使用&#xff0c;如果没有公网IP的情况下&#xff0c;推荐大家使用ZeroTier&#xff0c;这是一款强大的内网穿透工具。 mac和windows版的操作…

Nginx-狂神说

Nginx概述 公司产品出现瓶颈&#xff1f; 我们公司项目刚刚上线的时候&#xff0c;并发量小&#xff0c;用户使用的少&#xff0c;所以在低并发的情况下&#xff0c;一个jar包启动应用就够了&#xff0c;然后内部tomcat返回内容给用户。 但是慢慢的&#xff0c;使用我们平台…

HTTP 各版本差异

http1.0 它的特点是每次请球和响应完毕后都会销毁TCP 连接。同时规走前一个响应完成后才发送下一个请求。这样做有两个问题&#xff1a; 无法复用连接了。 每次请求都要创建新的TCP连接&#xff0c;完成三次握手和四次挥手。网络利用率低 队头阻塞 如果前一个请求被某种原因阻…

K8S认证|CKA题库+答案| 13. sidecar 代理容器日志

目录 13、使用 sidecar 代理容器日志 CKA v1.29.0模拟系统免费下载试用&#xff1a; 题目&#xff1a; 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、生成yaml文件 3&#xff09;、官网找模板 4&#xff09;、编辑yaml文件 5&#xff09;、应用yaml…

车载电子电器架构 —— 智能座舱技术

车载电子电器架构 —— 智能座舱技术 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何消耗你的…

qt multiple definition of 报错解决

qt编译报了很多错&#xff0c; multiple definition of xxx 原来一维设计文件ui 的问题 后来发现是pro中头文件和cpp文件重写了&#xff0c;导致重复编译报的错 解决方法&#xff1a;把重复的头文件和cpp文件删了就可以了。