没有网站可以做备案吗,软文代发布,漳州做网站开发,耒阳市古雍网站建设店前言
在现代 Web 应用程序中#xff0c;安全性是至关重要的一个方面。Spring Security 作为一个功能强大且广泛使用的安全框架#xff0c;为 Java 应用程序提供了全面的安全解决方案。本文将深入介绍 Spring Security 的基本概念、核心功能以及如何在应用程序中使用它来实现…前言
在现代 Web 应用程序中安全性是至关重要的一个方面。Spring Security 作为一个功能强大且广泛使用的安全框架为 Java 应用程序提供了全面的安全解决方案。本文将深入介绍 Spring Security 的基本概念、核心功能以及如何在应用程序中使用它来实现认证和授权。
一、Spring Security
Spring Security 是一个基于 Spring 的安全性框架用于提供身份验证、授权、攻击防护等安全服务。它构建在 Spring 框架之上利用依赖注入和 AOP 等功能使得集成到现有的 Spring 应用程序中非常简单。
在开始深入了解 Spring Security 之前我们需要了解几个核心概念
Authentication认证验证用户的身份通常是通过用户名和密码进行。Authorization授权确定用户是否有权限执行特定操作或访问特定资源。Principal主体代表当前用户的抽象概念通常是一个实现了 UserDetails 接口的对象。Granted Authority授权权限表示用户具有的权限通常是角色或权限的集合。Access Control访问控制定义了哪些用户可以访问应用程序的哪些部分以及如何限制对资源的访问。
二、快速入门 添加 Spring Security 依赖项首先在 Spring Boot 项目中添加 Spring Security 的依赖项。 dependencies!-- Spring Boot Web Starter 依赖包含了开发 web 应用所需的所有基础依赖 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- Spring Boot Security Starter 依赖包含了开发安全应用所需的所有基础依赖 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId/dependency
/dependenciesdependencyManagementdependencies!-- Spring Boot Dependencies POM用于管理 Spring Boot 项目的所有依赖的版本 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-dependencies/artifactIdversion2.7.5/versiontypepom/typescopeimport/scope/dependency/dependencies
/dependencyManagement配置安全策略创建一个配置类来配置 Spring Security 的行为。 Configuration
EnableWebSecurity // 注解启用 Spring Security 的 web 安全支持
public class SecurityConfig {
}定义一个访问端点定义一个测试使用的访问端点 RestController
RequestMapping(/test)
public class TestController {RequestMapping(/hello)public String hello() {return hello spring security;}
}运行应用程序运行 Spring Boot 应用程序并尝试访问端点 localhost:8080/test/hello。 默认账号是 user密码如下图所示 输入默认账号、密码之后
三、核心功能
3.1 身份验证Authentication
Spring Security 提供多种身份验证机制包括基本认证、表单认证、OAuth、LDAP 等。支持自定义身份验证流程可以根据应用程序的需求进行定制。开发人员可以根据需要配置请求路径是否需要认证才能访问。例如
Configuration
EnableWebSecurity
public class SecurityConfig {Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests().requestMatchers(new AntPathRequestMatcher(/test/hello)).permitAll() // 允许所有用户访问 /test/hello 路径.anyRequest().authenticated(); // 表示所有其他的请求都需要经过认证return http.build();}
}3.2 授权Authorization
Spring Security 可以基于角色Role-Based Access Control和权限Permission-Based Access Control的访问控制。
Configuration
EnableWebSecurity
public class SecurityConfig {Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests()// 只有具有 add 权限的用户才能访问 /test/hello 路径.requestMatchers(new AntPathRequestMatcher(/test/hello)).hasAuthority(add);return http.build();}
}3.3 漏洞防护Protection Against Exploits
Spring Security 可以防范常见的攻击如跨站点请求伪造CSRF、点击劫持等。例如通过如下配置可开启 CSRF 保护
Configuration
EnableWebSecurity
public class SecurityConfig {Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {// 配置跨站请求伪造CSRF的保护http.csrf();return http.build();}
}四、高级功能
4.1 会话管理Session Management
Spring Security 提供了会话管理的功能包括会话超时、并发登录限制、会话固定攻击防护等功能。例如可以通过如下配置开启并发登录限制
Configuration
EnableWebSecurity
public class SecurityConfig {Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.formLogin() // 启用了表单登录.and().authorizeRequests().anyRequest().authenticated() // 所有请求都需要经过认证.and().sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 设置会话创建策略为无状态即 Spring Security 不会创建会话.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true); // 设置每个用户的最大并发会话数为 1并且当达到最大并发会话数时阻止新的登录请求return http.build();}
}4.2 密码编码Password Encoding
Spring Security 提供密码加密和验证机制确保用户密码的安全性。例如要使用 BCrypt 加密只需进行如下配置即可
Configuration
EnableWebSecurity
public class SecurityConfig {Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}4.3 基于注解的方法级安全控制
Spring Security 允许在方法上使用注解的方式进行访问控制。
Secured(ROLE_ADMIN)
public void secureMethod() {// 只有具有 ROLE_ADMIN 角色的用户可以访问
}PreAuthorize(hasRole(ADMIN))
public void preAuthorizeMethod() {// 在方法调用之前进行授权检查
}要使用注解的方式需要使用 EnableMethodSecurity 注解开启这一功能
Configuration
EnableWebSecurity
EnableMethodSecurity // 开启基于注解的方法级安全控制
public class SecurityConfig {}除此之外Spring Security 支持使用表达式语言 (SpEL) 来定义复杂的访问控制规则。
PreAuthorize(hasRole(ROLE_USER) and #id principal.id)
public void updateUser(Long id) {// 只有具有 ROLE_USER 角色且 id 等于当前用户的 principal.id 的用户可以访问
}4.4 事件监听
Spring Security 允许监听安全事件例如登录成功、失败、登出等以便记录日志或执行其他操作。
Component
public class AuthenticationEventListener implements ApplicationListenerAuthenticationSuccessEvent {Overridepublic void onApplicationEvent(AuthenticationSuccessEvent event) {// 处理认证成功事件}
}五、工作原理
Spring Security 的设计有那么亿点复杂我们通过分开描述其核心工作流程、认证工作流程、鉴权工作流程来阐述其工作原理。
5.1 核心工作流程
Spring Security 的核心工作流程如下
用户发送请求用户通过浏览器或客户端发送请求到应用程序。请求进入过滤器链代理FilterChainProxy所有请求首先进入 FilterChainProxy它是一个标准的 Servlet 过滤器 (javax.servlet.Filter)。FilterChainProxy 的作用是根据请求的路径URL匹配合适的 SecurityFilterChain。请求进入安全过滤器链SecurityFilterChain匹配到相应的 SecurityFilterChain 后请求进入该安全过滤器链。SecurityFilterChain 是由多个安全过滤器SecurityFilter组成的序列每个安全过滤器按顺序处理请求。执行安全过滤器SecurityFilter每个安全过滤器 (SecurityFilter) 负责执行特定的安全操作和策略例如身份认证、授权、会话管理等。调用认证管理器AuthenticationManagerAuthenticationManager 是 Spring Security 的核心接口之一负责处理认证请求。在认证过程中AuthenticationManager 通常会使用 AuthenticationProvider 来进行具体的认证操作。返回认证结果AuthenticationManager 返回认证结果给 SecurityFilter。返回过滤器链认证完成后SecurityFilter 可能会进行一些额外的安全处理并将请求继续传递给下一个安全过滤器或者返回给 SecurityFilterChain。返回过滤器链代理处理完所有安全过滤器后请求最终返回到 FilterChainProxy。返回响应FilterChainProxy 将最终的响应返回给用户完成整个请求-响应周期。 FilterChainProxy
作用FilterChainProxy 是 Spring Security 中的一个核心组件它负责管理 Spring Security 中的各种过滤器链。当一个 HTTP 请求到达应用程序时FilterChainProxy 会将该请求传递给一个或多个 SecurityFilterChain 实例进行处理。如果需要FilterChainProxy 还可以重定向请求或返回错误信息。设计目的FilterChainProxy 的设计目的是提供一个统一的入口点用于管理和协调 Spring Security 中的所有过滤器链。例子假设我们正在开发一个 Web 应用程序该应用程序有多个端点如 /public、/user 和 /admin。我们可能希望 /public 端点对所有人开放/user 端点只对已登录用户开放而 /admin 端点只对管理员开放。在这种情况下你可以使用 FilterChainProxy 来管理三个不同的过滤器链每个过滤器链负责一个特定的端点。
SecurityFilterChain
作用SecurityFilterChain 是一个顶层接口。SecurityFilterChain 和 Servlet 中的 FilterChain 一样同样维护了很多 Filter这些 Filter 由 Spring Security 提供每个 Filter 具有不同的职能。设计目的SecurityFilterChain 的设计目的是为了支持添加一个或多个 SecurityFilterChain每个SecurityFilterChain 负责不同的请求比如依据请求地址进行区分这样可以为不同的请求设置不同的认证规则。例子继续上面的例子我们可能会为 /public 端点创建一个 SecurityFilterChain该过滤器链包含一个检查请求是否为 GET 的过滤器。对于 /user 和 /admin 端点我们可能会创建包含身份验证过滤器的 SecurityFilterChain该过滤器检查用户是否已登录并根据用户的角色用户或管理员授予相应的权限。
SecurityFilter
作用SecurityFilter 是 Spring Security 的过滤器每个过滤器负责处理特定的安全任务。当请求到达应用程序时它会依次通过过滤器链中的每个过滤器直到到达目标资源。在过滤器链中每个过滤器都可以对请求进行拦截、修改或执行其他操作以确保应用程序的安全性。设计目的SecurityFilter 的设计目的是为了提高 Web 应用程序的安全性、可维护性和可扩展性。例子在上述 SecurityFilterChain 中我们可能会使用多个 SecurityFilter。例如一个 SecurityFilter 可能会检查请求是否为 GET另一个 SecurityFilter 可能会检查用户是否已登录还有一个 SecurityFilter 可能会根据用户的角色授予相应的权限。
AuthenticationManager
作用AuthenticationManager 是 Spring Security 中的认证管理器用来对登录请求进行处理。当处理用户的登录请求时例如在使用表单登录时AuthenticationManager 的 authenticate 方法会被调用来处理请求。设计目的AuthenticationManager 这个接口的设计目的是对用户的未授信凭据进行认证认证通过则返回授信状态的凭据否则将抛出认证异常 AuthenticationException。例子当用户尝试登录时在登录表单中输入用户名和密码这些凭据将被传递给 AuthenticationManager。AuthenticationManager 会检查这些凭据是否有效。如果凭据有效AuthenticationManager 将创建一个已认证的 Authentication 对象该对象包含用户的详细信息和授权。如果凭据无效AuthenticationManager 将抛出一个异常。 5.2 认证工作流程
认证是验证用户身份的过程通常通过用户名和密码、数字证书或生物特征等手段进行。通过 Spring Security 的核心工作流程我们可以知道Spring Security 具体的认证工作是交由 AuthenticationManager 执行的。
AuthenticationManager 的认证流程如下
用户提交凭证用户向系统提交用户名和密码等凭证信息。认证管理器 (AuthenticationManager)接收到凭证后AuthenticationManager 负责进行认证。认证提供者 (AuthenticationProvider)AuthenticationManager 调用认证方法该方法会委托给配置的 AuthenticationProvider认证提供者。用户详情服务 (UserDetailsService)认证提供者通过调用 UserDetailsService 加载用户的详细信息通常是根据用户名加载用户对象。用户详情 (UserDetails)UserDetailsService 返回一个实现了 UserDetails 接口的用户详情对象其中包含了用户的详细信息和权限。创建认证信息 (Authentication)认证提供者使用 UserDetails 对象创建一个 Authentication 对象表示成功的认证。安全上下文持有者 (SecurityContextHolder)创建的 Authentication 对象被存储到 SecurityContextHolder 中以便后续的访问控制和安全操作使用。返回认证结果最终认证结果以 Authentication 对象的形式返回给用户表示用户已经成功通过认证。 Spring Security 的认证流程看似蛮复杂的。其实它的认证流程和我们常规的认证方式是类似的。
AuthenticationManager不进行具体的认证处理负责管理多个 AuthenticatonProvider具体的认证交由能够处理当前认证的 AuthenticationProvider。AuthenticationProvider进行具体认证通过调用 UserDetailService 从数据库或者其他地方加载用户信息。之后通过与用户提交的凭证信息进行匹配若成功则生成 Authentication 对象表示认证成功反之返回认证失败信息。UserDetailsService它的主要作用是根据用户名加载用户的详细信息。加载的具体逻辑一般由开发人员实现。UserDetails用于表示用户的基本身份和授权信息代表一个用户。Authentication用于表示用户在系统中的身份认证信息。具体来说Authentication 接口主要用于封装认证过程中的关键信息如认证的主体Principal、凭证Credentials、授权信息Authorities等。SecurityContextHolder主要用于存储和访问当前用户的 Authentication 对象即表示当前用户身份认证信息的实例。 5.3 鉴权工作流程
鉴权是在确认用户身份后决定用户是否有权访问特定资源或执行特定操作的过程。Spring Security 鉴权流程通常是在认证完成之后即生成了 Authentication 对象之后进行的。其具体流程如下
AuthorizationFilter 授权过滤器通过认证的请求被送到 AuthorizationFilter它是 Spring Security 中的一个过滤器负责处理所有的请求并开启鉴权过程。获取 Authentication 认证信息通过 SecurityContextHolder 从当前的安全上下文中获取用户的认证信息 (Authentication)这包括用户的身份凭证和权限信息。调用 AccessDecisionManager 访问决策管理器AuthorizationFilter 调用 AccessDecisionManager 进行实际的访问决策。AccessDecisionManager 是一个核心组件负责确定是否允许用户访问请求的资源或操作。AccessDecisionManager 可能会使用多个 AccessDecisionVoter 进行投票。AccessDecisionVoter 是决策的实际执行者根据用户的认证信息和访问请求投票是否允许访问资源或执行操作。返回投票结果每个 AccessDecisionVoter 根据自身的逻辑判断是否允许访问。投票结果将汇总给 AccessDecisionManager。返回访问决策结果AccessDecisionManager 将所有 AccessDecisionVoter 的投票结果综合起来最终决定是否允许用户访问请求的资源或操作。返回响应AuthorizationFilter 将处理的结果返回给用户响应用户的请求这可能包括成功的访问授权或者拒绝访问的信息。 其实鉴权的逻辑还是比较简单的只是流程比较多可以概括为
首先AuthorizationFilter 获取登录用户的认证信息Authentication然后AuthorizationFilter 调用 AccessDecisionManager 判断权限
六、前后端分离
在之前的快速入门中我们发现 Spring Security 默认情况下是采用前后端不分离的方式进行认证而现在我们的项目一般都是前后端分离的方式即 前端通过 RESTful API 与后端进行通信后端负责处理认证和授权而前端则通过获取后端返回的 JWTJSON Web Token来管理用户的身份验证和授权状态。要实现这一需求我们可以参考如下步骤 添加依赖首先在 pom.xml 文件中添加 Spring Security 和 JWT 的依赖 dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId/dependency!-- JWT 依赖 --dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt/artifactIdversion0.9.1/version !-- 根据需要选择合适的版本 --/dependency
/dependenciesdependencyManagementdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-dependencies/artifactIdversion2.7.5/versiontypepom/typescopeimport/scope/dependency/dependencies
/dependencyManagement创建用户服务实现类创建一个实现 UserDetailsService 接口的服务类用于从数据库加载用户信息并将其返回给 Spring Security 进行认证和授权。 Service
public class UserDetailsServiceImpl implements UserDetailsService {// 模拟从数据库中查找用户Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 检查传入的用户名是否为zsif (!zs.equals(username)) {// 如果用户名不是zs返回 null表示没有找到对应的用户return null;}// 创建并返回一个 UserDetails 对象表示用户的详细信息return User.builder().username(zs) // 设置用户名为zs.password($2a$16$RBoXNEqVxxtZ5l1QrJaMPub32Z8Q/e01tIG1Irs9ThxfXgeWxV1jq) // 设置加密后的密码.authorities(add) // 设置用户的权限为add.build();}
}创建 JWT 工具类 创建一个 JWT 工具类来生成和验证 JWT。 Component
public class JwtUtil {Value(${jwt.secret})private String secret;Value(${jwt.expiration})private long expiration;// 从令牌中提取用户名public String extractUsername(String token) {return extractClaim(token, Claims::getSubject);}// 从令牌中提取过期时间public Date extractExpiration(String token) {return extractClaim(token, Claims::getExpiration);}// 提取令牌中的声明public T T extractClaim(String token, FunctionClaims, T claimsResolver) {final Claims claims extractAllClaims(token);return claimsResolver.apply(claims);}// 解析令牌private Claims extractAllClaims(String token) {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}// 验证令牌是否过期private Boolean isTokenExpired(String token) {return extractExpiration(token).before(new Date());}// 生成令牌public String generateToken(UserDetails userDetails) {return Jwts.builder().setSubject(userDetails.getUsername()).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() expiration * 1000)).signWith(SignatureAlgorithm.HS256, secret).compact();}// 验证令牌public Boolean validateToken(String token, UserDetails userDetails) {final String username extractUsername(token);return (username.equals(userDetails.getUsername()) !isTokenExpired(token));}
}配置 JWT 相关属性在 application.properties 或 application.yml 中配置 JWT 的密钥和过期时间 jwt: # 配置JWT相关的属性secret: secretKey # 用于签名和验证JWT令牌的密钥expiration: 86400 # JWT令牌的有效期以秒为单位。这里设置为86400秒即24小时创建 JWT 认证过滤器创建一个 JWT 认证过滤器来拦截每个请求并验证 JWT。 // 这个类继承自 OncePerRequestFilter确保在每次请求时只调用一次过滤器
Component
public class JwtRequestFilter extends OncePerRequestFilter {// 注入JwtUtil工具类Resourceprivate JwtUtil jwtUtil;// 注入UserDetailsServiceImpl类用于加载用户详细信息Resourceprivate UserDetailsServiceImpl userDetailsService;// 重写OncePerRequestFilter的doFilterInternal方法用于处理每个HTTP请求Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {// 从请求头中获取Authorization信息final String authorizationHeader request.getHeader(Authorization);// 初始化用户名和JWT令牌变量String username null;String jwt null;// 检查Authorization头是否以Bearer 开头if (authorizationHeader ! null authorizationHeader.startsWith(Bearer )) {// 提取JWT令牌去掉Bearer 部分jwt authorizationHeader.substring(7);// 使用jwtUtil从令牌中提取用户名username jwtUtil.extractUsername(jwt);}// 如果用户名存在且当前没有已认证的用户if (username ! null SecurityContextHolder.getContext().getAuthentication() null) {// 加载用户详细信息UserDetails userDetails this.userDetailsService.loadUserByUsername(username);// 验证JWT令牌if (jwtUtil.validateToken(jwt, userDetails)) {// 创建UsernamePasswordAuthenticationToken对象包含用户详细信息和权限UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());// 将认证信息设置到SecurityContextHolder中SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);}}// 继续过滤链处理下一个过滤器或目标资源chain.doFilter(request, response);}
}配置 Spring Security开启 Spring Security 验证配置 SecurityFilterChain。 Configuration
EnableWebSecurity
public class SecurityConfig {// 注入JwtRequestFilter对象用于处理JWT认证Resourceprivate JwtRequestFilter jwtRequestFilter;Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.csrf().disable() // 禁用CSRF保护.authorizeRequests() // 配置请求授权.antMatchers(/login).permitAll() // 对于/login路径允许所有请求无需认证.anyRequest().authenticated() // 对于所有其他请求需要认证.and()// 在UsernamePasswordAuthenticationFilter之前添加JwtRequestFilter.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);return http.build();}Beanpublic AuthenticationManager authenticationManager(UserDetailsService userDetailsService,PasswordEncoder passwordEncoder) {// 创建一个DaoAuthenticationProviderDaoAuthenticationProvider authenticationProvider new DaoAuthenticationProvider();// 设置UserDetailsServiceauthenticationProvider.setUserDetailsService(userDetailsService);// 设置PasswordEncoderauthenticationProvider.setPasswordEncoder(passwordEncoder);// 返回包含这个认证提供者的ProviderManagerreturn new ProviderManager(authenticationProvider);}Beanpublic BCryptPasswordEncoder passwordEncoder() {// 创建一个强度为16的BCryptPasswordEncoderreturn new BCryptPasswordEncoder(16);}
}创建认证接口和控制器创建一个认证控制器来处理用户登录请求并返回 JWT 给前端。 RestController
public class AuthController {Resourceprivate AuthenticationManager authenticationManager;Resourceprivate JwtUtil jwtUtil;Resourceprivate UserDetailsServiceImpl userDetailsService;PostMapping(/login)public ResponseEntityString createAuthenticationToken(RequestBody LoginRequest loginRequest) {// 创建一个未认证的UsernamePasswordAuthenticationToken对象UsernamePasswordAuthenticationToken authenticationToken UsernamePasswordAuthenticationToken.unauthenticated(loginRequest.getUsername(), loginRequest.getPassword());// 调用AuthenticationManager的authenticate方法进行用户认证authenticationManager.authenticate(authenticationToken);// 加载用户详细信息UserDetails userDetails userDetailsService.loadUserByUsername(loginRequest.getUsername());// 生成JWT令牌String jwt jwtUtil.generateToken(userDetails);// 返回包含JWT令牌的响应return ResponseEntity.ok(jwt);}
}测试效果 首先访问 localhost:8080/login 获取到 token 然后使用 token 访问 localhost:8080/test/hello
七、小结
Spring Security 提供了强大而灵活的安全解决方案可以轻松集成到 Spring 应用程序中。但是我们不难发现 Spring Security 设计得确实复杂了那么亿点点 (¬‿¬)。Spring Security 的使用门槛虽然较高但是如果明白了它的原理便可以无缝与 Spring 结合使用在日常的开发中可以极大的提高开发效率增强应用的安全性。
推荐阅读
深入探究 Spring Boot Starter从概念到实践深入理解 Java 中的 volatile 关键字OAuth 2.0现代应用程序的授权标准Spring 三级缓存深入了解 MyBatis 插件定制化你的持久层框架