vi设计品牌图片,深圳网站的优化公司,app开发需要多久,wordpress 发布html5 博客主页: 南来_北往
系列专栏#xff1a;Spring Boot实战 前言
Spring Security 6.0是一个功能强大且可扩展的身份验证和访问控制框架#xff0c;它用于保护基于Java的应用程序。其主要目标是提供一个全面的安全解决方案#xff0c;包括身份验证、授权、防止跨站请… 博客主页: 南来_北往
系列专栏Spring Boot实战 前言
Spring Security 6.0是一个功能强大且可扩展的身份验证和访问控制框架它用于保护基于Java的应用程序。其主要目标是提供一个全面的安全解决方案包括身份验证、授权、防止跨站请求伪造CSRF等功能。
身份验证Authentication
身份验证是确认用户身份的过程。Spring Security提供了多种身份验证机制如表单登录、HTTP基本身份验证、OAuth2等。在Spring Security中AuthenticationManager负责处理身份验证逻辑。当用户提供凭据如用户名和密码时AuthenticationManager将创建一个Authentication对象其中包含有关用户的信息。
授权Authorization
授权是确定用户可以访问哪些资源的过程。在Spring Security中AccessDecisionManager负责处理授权逻辑。它根据用户的角色和权限来确定是否允许用户访问特定资源。AccessDecisionManager使用投票策略来决定是否允许访问。每个投票者可以根据其配置投赞成票、反对票或弃权票。如果赞成票多于反对票则允许访问。
过滤器链Filter Chain
Spring Security使用一系列过滤器来处理请求。这些过滤器按照特定的顺序组成一个过滤器链。每个过滤器都负责处理特定的任务如处理CSS和JavaScript资源、处理CORS、处理会话管理等。当请求进入应用程序时过滤器链中的过滤器将按顺序处理请求。如果某个过滤器决定终止请求例如因为用户未经身份验证则后续过滤器将不会执行。
安全上下文Security Context
安全上下文是一个包含有关当前用户和其权限的对象。在Spring Security中SecurityContextHolder负责存储和管理安全上下文。当用户通过身份验证时Authentication对象将被存储在SecurityContextHolder中。这使得应用程序可以在任何地方访问用户的凭据和权限信息。
CSRF保护
跨站请求伪造CSRF是一种攻击攻击者试图利用已登录用户的凭据来执行恶意操作。为了防止CSRF攻击Spring Security提供了一个CsrfFilter它会自动为每个表单添加一个隐藏的CSRF令牌。当表单提交时CsrfFilter将验证令牌是否有效。如果令牌无效或不存在请求将被拒绝。
这只是Spring Security 6.0实现原理的一个简要概述。要深入了解Spring Security的各个方面建议您查阅官方文档和相关教程。
Java Web应用的Security实现基本思路
Java Web应用的Security实现基本思路主要包括以下几个方面
身份验证Authentication确保用户的身份合法通常使用用户名和密码进行验证。授权Authorization确定用户具有哪些权限以便控制用户可以访问的资源和执行的操作。防止跨站请求伪造CSRF确保用户提交的请求是合法的避免恶意网站利用用户在其他网站的认证状态发起攻击。输入验证Input Validation对用户输入的数据进行验证防止恶意数据注入攻击。错误处理正确处理异常情况避免泄露敏感信息。敏感数据保护对敏感数据进行加密存储和传输防止数据泄露。
以下是一个简单的Java Web应用Security实现代码示例
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers(/admin/**).hasRole(ADMIN).antMatchers(/user/**).hasAnyRole(USER, ADMIN).anyRequest().authenticated().and().formLogin().loginPage(/login).permitAll().and().logout().permitAll();}
}在这个示例中我们使用了Spring Security框架来实现Web应用的安全功能。首先我们通过EnableWebSecurity注解启用了Web安全配置。然后我们继承了WebSecurityConfigurerAdapter类并重写了configure方法来配置安全规则。
在configure方法中我们使用authorizeRequests方法定义了不同URL路径的访问权限。例如只有具有ADMIN角色的用户才能访问/admin/**路径而具有USER或ADMIN角色的用户都可以访问/user/**路径。其他所有请求都需要用户进行身份验证。
我们还配置了表单登录和注销功能分别对应于/login页面和允许所有用户访问的注销操作。
Spring Security框架的基本架构和原理
Spring Security是一个功能强大且可扩展的身份验证和访问控制框架它提供了一种简单的方式来保护基于Java的应用程序。以下是Spring Security的基本架构和原理 身份验证AuthenticationSpring Security通过AuthenticationManager接口来处理身份验证。这个接口负责从用户提交的凭据中获取认证信息并将其封装成一个Authentication对象。常见的认证方式包括用户名密码认证、OAuth2认证等。 授权Authorization一旦用户被认证Spring Security会使用AccessDecisionManager来决定用户是否有权访问特定的资源。AccessDecisionManager会根据用户的权限和请求的资源来判断是否允许访问。 过滤器链Filter ChainSpring Security使用一系列过滤器来处理HTTP请求。这些过滤器按照顺序执行每个过滤器都负责一个特定的安全功能如身份验证、授权、防止跨站请求伪造CSRF等。 安全上下文Security ContextSpring Security使用SecurityContextHolder来存储当前用户的安全上下文信息。这个上下文包含了用户的认证信息、权限等信息可以在应用程序的任何位置访问。 配置ConfigurationSpring Security的配置通常通过继承WebSecurityConfigurerAdapter类并重写其方法来实现。例如可以配置登录页面、注销行为、URL访问规则等。
下面是一个简单的Spring Security配置示例
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;Configuration
EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 配置内存中的用户存储auth.inMemoryAuthentication().withUser(user).password({noop}password).roles(USER).and().withUser(admin).password({noop}password).roles(ADMIN);}Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers(/admin/**).hasRole(ADMIN).antMatchers(/user/**).hasAnyRole(USER, ADMIN).anyRequest().authenticated().and().formLogin().loginPage(/login).permitAll().and().logout().permitAll();}
}在这个示例中我们配置了两个内存中的用户user和admin分别具有不同的角色。我们还定义了URL访问规则要求访问/admin/**路径的用户必须具有ADMIN角色而访问/user/**路径的用户必须具有USER或ADMIN角色。最后我们配置了表单登录和注销功能。
Authentication身份认证
身份认证有很多种方式大致可以分为以下4类 标准的账号密码认证这是很多网站都支持的方式也是大家最熟悉的认证模式 调用第三方服务或内部其它API进行认证当服务自身无法直接获取用户的密码时需要借助第三方服务或者内部API进行认证 基于Token的认证这是API服务一般使用的认知方式通过令牌来进行身份验证 OAuth2或其它OpenID认证这种方式广泛用于允许用户使用其它平台的身份信息进行登录例如微信登录Google登录等。
Spring Security支持大部分的认证方式但不同的认证方式需要配置不同的Bean及其依赖Bean否则很容易遇到各种异常和空指针。
本文重点讨论标准的账号密码认证方式。
如果你使用的是Spring Boot那么Spring Boot Starter Security默认就配置了Form表单和Basic认证方式其配置代码如下所示
Configuration(proxyBeanMethods false)
ConditionalOnWebApplication(type Type.SERVLET)
class SpringBootWebSecurityConfiguration {Configuration(proxyBeanMethods false)ConditionalOnDefaultWebSecuritystatic class SecurityFilterChainConfiguration {BeanOrder(SecurityProperties.BASIC_AUTH_ORDER)SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((requests) - requests.anyRequest().authenticated()); // 所有URL都需要认证用户http.formLogin(withDefaults()); // 支持form表单认证默认配置提供了自动生成的登录和注销页面http.httpBasic(withDefaults()); // 支持HTTP Basic Authenticationreturn http.build();}}// ...其它配置...
}
为了讨论方便我们用下面的配置覆盖Spring Boot默认的配置只支持Form表单认证方式讨论它具体是如何实现的。
Configuration()
public class MySecurityConfig {BeanSecurityFilterChain mySecurityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((requests) - requests.anyRequest().authenticated()); // (1)http.formLogin(withDefaults()); // (2)return http.build();}
} authorizeHttpRequests方法用于配置每个请求的权限控制这里要求所有请求都要通过认证后才能访问。实际上这个方法配置的更多是鉴权相关的内容跟身份认证的关联较小它本质上是增加了一个AuthorizationFilter用于鉴权具体细节在鉴权部分会详细说明。 http.formLogin方法提供了Form表单认证的方式withDefaults方法是Form表单认证的默认配置。这段配置的作用就是增加了用于账号密码认证的UsernamePasswordAuthenticationFilter以及自动生成登录页面和注销页面的DefaultLogoutPageGeneratingFilter和DefaultLogoutPageGeneratingFilter共3个Security Filter。值得注意的是登录页面和注销页面这两个Filter是配合DefaultLoginPageConfigurer配置一起注册的。如果你通过formLogin.loginPage提供了自定义的登录页面那么这两个Filter就不会被注册。
在本节中我们主要讨论身份认证的实现因此接下来将详细探究Form表单认证方式中UsernamePasswordAuthenticationFilter的实现。
AbstractAuthenticationProcessingFilter
对于Filter我们重点分析它的doFilter方法的源码。实际上它继承了抽象类AbstractAuthenticationProcessingFilter而这个抽象类的doFilter是一个模板方法定义了整个认证流程。其核心流程非常简单伪代码如下
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {// 首先判断该请求是否是认证请求或者登录请求if (!requiresAuthentication(request, response)) { // (1)chain.doFilter(request, response);return;}try {Authentication authenticationResult attemptAuthentication(request, response); // (2) 实际认证逻辑// 认证成功successfulAuthentication(request, response, chain, authenticationResult); // (3)}catch (AuthenticationException ex) {// 认证失败unsuccessfulAuthentication(request, response, ex); // (4)}
} 首先requiresAuthentication方法用于判断当前请求是否为认证请求或者登录请求例如通常是POST /login。只有在登录认证的情况下才需要通过这个Filter attempAuthentication方法是实际的认证逻辑这是一个抽象方法具体的逻辑由子类重写实现。它的规范行为是如果认证成功应该返回认证结果Authentication否则以抛出异常AuthenticationException的方式表示认证失败 successfulAuthentication认证成功后该方法会将Authentication对象放到Security Context中这是非常关键的一步后续需要认证结果的时候都是从Security Context获取的比如鉴权Filter。此外该方法还会处理其它一些相关功能比如RememberMe事件发布最后再调用AuthenticationSuccessHandler unsuccessfulAuthentication 在认证失败后它会清空Security Context调用RememberMe相关服务和AuthenticationFailureHandler来处理认证失败后的回调逻辑比如跳转到错误页面。 Authentication模型
在这里我们涉及到了一个非常重要的数据模型——Authentication它是一个接口类型它既是对认证结果的一个抽象表示同时也是对认证请求的一个抽象通常也被称为认证Token。它的方法都比较抽象定义如下
public interface Authentication extends Principal, Serializable {// 当前认证用户拥有的权限列表Collection? extends GrantedAuthority getAuthorities();// 用户的一个身份标识通常就是用户名Object getPrincipal();// 可用于证明用户身份的一个凭证通常就是用户密码Object getCredentials();// 当前用户是否认证通过boolean isAuthenticated();// 更新用户的认证状态void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;// 获取附加的详情信息比如原始的Http请求体等。Object getDetails();
}
具体的Authentication实现一般都命名为XXXToken大部分都继承自抽象类AbstractAuthenticationToken比如表示标准的用户名密码认证结果的UsernamePasswordAuthenticationToken表示匿名登录用户认证结果的AnonymousAuthenticationToken等等你也可以完全实现自己的Authentication。
attempAuthentication方法
接下来我们看下UsernamePasswordAuthenticationFilter的认证具体实现方法attempAuthentication它的源码如下
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {// 默认只支持POST请求if (this.postOnly !request.getMethod().equals(POST)) {throw new AuthenticationServiceException(Authentication method not supported: request.getMethod());}// 从form表单获取用户名和密码String username obtainUsername(request);username (username ! null) ? username.trim() : ;String password obtainPassword(request);password (password ! null) ? password : ;// 构建一个用于认证的请求UsernamePasswordAuthenticationToken authRequest UsernamePasswordAuthenticationToken.unauthenticated(username,password);// 附加详细信息比如请求体有些认证方式需要除了用户名密码外更多的信息setDetails(request, authRequest);// 委托给AuthenticationManager做具体的认证return this.getAuthenticationManager().authenticate(authRequest);
} 这个方法非常简单它主要进行一些前置校验工作从请求体中获取用户名和密码并构建认证请求对象。然后剩余的认证工作都是委托给AuthenticationManager接口来完成的该接口的定义如下
public interface AuthenticationManager {Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
AuthenticationManager和AuthenticationProvider
AuthenticationManager接口只有一个方法它的入参和出参都是Authentication对象。通常情况下入参提供了必要的认证信息例如用户名和密码。而在认证成功后该方法会返回认证结果并附加认证状态用户拥有的权限列表等信息。如果认证失败它会抛出AuthenticationException异常类的子类其中包括DisabledExceptionLockedException和BadCredentialsException等账号相关的异常。
AuthenticationManager接口定义了Spring Security的认证行为。你可以提供自定义的实现Spring Security也提供了一个通用的实现类ProviderManager。ProviderManager将具体的认证工作委托给一系列的AuthenticationProvider。
每个AuthenticationProvider对应不同的认证方式。比如最常见的用户名密码的认证实现是DaoAuthenticationProvider而JwtAuthenticationProvider提供了JWT Token的认证。你可以通过添加不同的AuthenticationProvider的方式在同一个服务内支持多种类型的认证方式比如需要调用其它API检验密码的情况就需要自定义AuthenticationProvider。
此外ProviderManager还可以配置父级AuthenticationManager当这个ProviderManager的所有AuthenticationProvider都不支持所需的认证方式时它会继续委托给父级的AuthenticationManager而该父级通常也是一个ProviderManager类型。
UserDetailsService和PasswordEncoder
DaoAuthenticationProvider是最常用的认证实现之一它通过UserDetailsService和PasswordEncoder来验证用户名和密码。
UserDetailsService的作用是查找用户信息UserDetails这些信息包括用户密码状态权限列表等。用户信息可以存储在内存数据库或者其它任何地方。Spring Security默认的配置是内存存储对应的UserDetailsService实现是InMemoryUserDetailsManager而数据库存储则对应JdbcUserDetailsManager。
从UserDetailsService获取到用户密码后需要通过PasswordEncoder来验证密码的正确性。因为密码一般都不应该以明文形式存储实际存储的是按一定规则编码后的文本Spring Security支持多种编码方式例如bcryptargon2scryptpbkdf2等。你可以配置PasswordEncoder Bean来选择不同的编码方式。都是请注意内置的编码方式默认对编码后的文本有一个格式要求就是必须有类似{bcrypt}的前缀来表示编码方式。
总结
本文重点分析了Spring Security的源码和架构帮助读者理解其实现原理。由于篇幅有限本文只覆盖了身份认证和鉴权模块的核心逻辑很多特性没有涉及包括Session管理Remember Me服务异常分支和错误处理等等不过有了上述的基础知识读者完全可以自己分析源码并深入理解这些特性。