Spring Security认证与授权
什么是Spring SecuritySpring 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
[*]在其中编写login()方法来实现登录逻辑
public interface ISysUserService extends IService<SysUser> {
/**
* @description: 登录
* @author: HYJ
* @date: 2025/4/15 0:01
* @param:
* @return: edu.ptu.springsecurity.common.AjaxResult
**/
AjaxResult login(SysUser user);
}LoginUser
[*]LoginUser实现UserDetails接口,重写其中的方法.将业务数据衔接到Spring Security的认证体系中
// 忽略未知的属性,避免序列化时出现异常@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
页:
[1]