什么是Spring Security
Spring Security是基于Spring框架,提供了一套Web应用安全性框架.专门为Java应用提供用户认证(Authentication)和用户授权(Authorization),支持单体应用到微服务的全场景安全防护
认证(Authentication)
- 验证某个用户是否为系统中的合法主体,即确认该用户是否可以访问此系统.认证一般需要用户提供用户名与密码,提供校验用户名与密码完成认证过程
- 简单来说→认证是判断该用户是否能登录;
- 关键要素:1.Principal:用户主体(如用户名)2.Credentials:验证凭证(如用户密码)3.Authorities:用户权限集合
授权(Authorization)
- 是指某个用户是否有权限执行某个操作.在同一系统中,不同用户所具有的权限是不同的.如对某一文件,有些用户只能读不能修改,而有些用户既可读也可以修改.某个角色都有一系列的权限
- 简单来说→授权是判断该用户是否有权限去做特定的操作;
- 关键要素:1.角色(Role):用户分组标识2.权限(Permission):具体操作权限
优势和缺点
- 优势
- 深度与Spring整合:无缝支持Spring Boot、Spring MVC、Spring Data等框架
- 企业级安全方案:支持OAuth2,SAML,LDAP,JWT等协议,满足复杂安全需求
- 旧版本无法脱离Web环境
- 新版本对框架进行分层提取,分为核心板块和Web板块,
- 缺点
- 性能开销:默认开启CSRF,Session管理等特性,对高性能场景需手动优化
- 配置复杂度高:默认配置覆盖大量安全规则,需要显式覆盖才能简化
与Shiro对比
对比维度Spring SecurityShiro生态整合深度集成 Spring 技术栈需手动整合Spring,对非 Spring 项目更友好微服务支持天然支持 Spring Cloud Security需自行实现分布式会话和权限管理典型场景企业级应用,微服务架构,需要 OAuth2 的 SaaS 系统中小型 Web 应用,移动端后台,快速开发项目
- 一般来说常见的安全管理技术栈组合是这样:
- SSM+Shiro
- Spring Boot/Spring Cloud+Spring Security
Spring Security实现原理
对Web资源最好的保护是Filter,对方法调用的最好方法是AOP
- Spring Security进行认证和权限检验时就是通过一系列的Filter来进行拦截
- 如图所示:一个请求要访问到后端,就要从左到右经过这些过滤器.其中绿色的过滤器是负责认证的过滤器,蓝色部分是负责异常处理的过滤器,橙色是负责权限校验的拦截器.
- 对于我们而言,只需关注UserNamePasswordAuthenticationFilter**->负责登入认证和FilterSecurityInterceptor->负责授权**
对于Spring Security,掌握了过滤器和组件就完全掌握了Spring Security.其使用方法就是对过滤器和组件进行扩展
Spring Security入门
- 添加Spring Security相关依赖
- <dependency>
- <groupId>org.springframework.boot</groupId>
- spring-boot-starter-security</artifactId>
- </dependency>
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- jjwt-api</artifactId>
- <version>0.12.6</version>
- </dependency>
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- jjwt-impl</artifactId>
- <version>0.12.6</version>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- jjwt-jackson</artifactId>
- <version>0.12.6</version>
- <scope>runtime</scope>
- </dependency>
-
- <dependency>
- <groupId>com.auth0</groupId>
- java-jwt</artifactId>
- <version>3.10.3</version>
- </dependency>
-
-
- <dependency>
- <groupId>com.alibaba.fastjson2</groupId>
- fastjson2</artifactId>
- <version>2.0.1</version>
- </dependency>
复制代码
编写UserController进行测试
启动项目,访问localhost:8080进行测试,其会自动跳转到localhost:8080/login登入页面
- 默认的用户名:user
- 其密码会在项目启动时打印在控制台
- 输入正确的用户名和密码时,即可成功访问UserController中的get方法→说明Spring Security保护生效
- 当然在实际开发中,这种默认配置是不存在的,我们需要扩展这些组件
用户认证
用户认证的流程
核心接口:
- Authentication接口,表示当前访问系统的用户,封装了用户的信息,其实现类为UsernamePasswordAuthenticationToken
- AuthenticationManager接口,其定义了Authentication的方法
- UserDetailsService接口,加载用户特定数据的核心接口,其中定义了一个根据用户名查询用户信息的方法
- UserDetails接口,提供核心用户信息.将UserDetailsService中获取的信息封装为UserDetails对象返回.并将其封装至Authentication对象中
UserDetails中的基本方法:
用于认证的核心组件:
对于系统来说,同一时间会有多个用户正在使用,那么如何确认哪个用户正在请求登录接口是登录认证的核心目的.Spring Security提出了:当前登录用户/当前认证用户,Spring Security中使用Authentication来存储认证信息,表示当前用户
在Spring boot中使用安全上下文SecurityContext来获取Authentication,SecurityContext交有SecurityContextHolder来管理,使用以下方法即可获取Authentication
- Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
复制代码
- Spring Security三个认证核心组件为:
- Authentication:存储认证信息的上下文,代表当前用户
- SecurityContext:上下文对象,用来获取Authentication
- SecurityContextHolder:上下文管理对象,用来获取SecurityContext
认证逻辑
- AuthenticationManager是Spring Security用于执行身份验证的组件,其authenticate方法可以完成认证.Spring Security默认的认证方法是在UsernamePasswordAuthenticationFilter这个过滤器中进行认证的
关键代码如下:- // 创建用户认证令牌,使用用户名和密码作为凭证
- UsernamePasswordAuthenticationToken token =
- new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
- // 通过认证管理器执行Spring Security认证流程,返回包含用户详情的认证对象
- Authentication authenticate = authenticationManager.authenticate(token);
复制代码 加密器PasswordEncoder
- passwordEncoder在Spring Security中用于处理密码加密存储和验证.
- 它可以负责将用户提交的明文密码转换为不可逆的加密字符串(如BCrypt算法),之后便将密码存储到数据库中
- 在用户登录时,验证用户输入的明文密码是否与存储的加密密码一致
若需要自定义加密方法,我们可以编写自定义加密器CustomPasswordEncoder
- public class CustomPasswordEncoder implements PasswordEncoder {
- // 自定义密码加密方式,使用MD5加密算法
- @Override
- public boolean matches(CharSequence rawPassword, String encodedPassword) {
- return Arrays.toString(DigestUtils.md5Digest(rawPassword.toString().getBytes())).equals(encodedPassword);
- }
- // 自定义密码加密方式,使用MD5加密算法
- @Override
- public String encode(CharSequence rawPassword) {
- return Arrays.toString(DigestUtils.md5Digest(rawPassword.toString().getBytes()));
- }
- }
复制代码并在SecurityConfig中注册新的加密器
- @Configuration
- @EnableWebSecurity
- public class SecurityConfig extends WebSecurityConfiguration {
- // 密码加密器
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new CustomPasswordEncoder();
- }
- }
复制代码直接使用BCryptPasswordEncoder加密器
- @Configuration
- @EnableWebSecurity
- public class SecurityConfig extends WebSecurityConfiguration {
- // 密码加密器
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
- }
复制代码 自定义登录接口
- 首先要重写SecuritySpring中的方法,可以自己写使用@Bean注册,也可以重写WebSecurityConfigurerAdapter接口的方法
由于Spring Security会对每一个接口都会进行认证,有些接口需要放行,直接让用户访问,我们就得在config()中进行放行
- 接着需要把AuthenticationManager注入容器→因为要使用其的authenticate方法进行验证
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
- /**
- * 配置密码编码器
- *
- * @return
- */
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new CustomPasswordEncoder();
- }
- /**
- * 配置HTTP安全设置
- *
- * @param http
- * @throws Exception
- */
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- // 禁用CSRF保护(常用于API场景)
- .csrf().disable()
- // 配置会话管理为无状态(不创建和使用HTTP Session)
- .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- .and()
- // 配置请求授权规则
- .authorizeRequests()
- // 允许匿名访问登录端点
- .antMatchers("/user/login").permitAll()
- // 所有其他请求需要认证
- .anyRequest().authenticated();
- }
- /**
- * 暴露AuthenticationManager
- *
- * @return
- * @throws Exception
- */
- @Bean
- @Override
- protected AuthenticationManager authenticationManager() throws Exception {
- return super.authenticationManager();
- }
-
- }
复制代码 <ul>IUserService
- public interface ISysUserService extends IService<SysUser> {
- /**
- * @description: 登录
- * @author: HYJ
- * @date: 2025/4/15 0:01
- * @param: [user]
- * @return: edu.ptu.springsecurity.common.AjaxResult
- **/
- AjaxResult login(SysUser user);
- }
复制代码 LoginUser
- LoginUser实现UserDetails接口,重写其中的方法.将业务数据衔接到Spring Security的认证体系中
[code]// 忽略未知的属性,避免序列化时出现异常@JsonIgnoreProperties(ignoreUnknown = true)public class LoginUser implements UserDetails { // 用户信息 private SysUser user; /** * @description: 账号是否未过期 * @author: HYJ * @date: 2025/4/15 0:02 * @param: [] * @return: boolean **/ @Override public boolean isEnabled() { return true; } /** * @description: 密码是否未过期 * @author: HYJ * @date: 2025/4/15 0:02 * @param: [] * @return: boolean **/ @Override public boolean isCredentialsNonExpired() { return true; } /** * @description: 账号是否未锁定 * @author: HYJ * @date: 2025/4/15 0:02 * @param: [] * @return: boolean **/ @Override public boolean isAccountNonLocked() { return true; } /** * @description: 账号是否未过期 * @author: HYJ * @date: 2025/4/15 0:02 * @param: [] * @return: boolean **/ @Override public boolean isAccountNonExpired() { return true; } /** * @description: 获取用户名 * @author: HYJ * @date: 2025/4/15 0:02 * @param: [] * @return: java.lang.String **/ @Override public String getUsername() { return user.getUsername(); } /** * @description: 获取密码 * @author: HYJ * @date: 2025/4/15 0:02 * @param: [] * @return: java.lang.String **/ @Override public String getPassword() { return user.getPassword(); } /** * @description: 获取权限 * @author: HYJ * @date: 2025/4/15 0:02 * @param: [] * @return: java.util.Collection |