目录
权限管理
认证
授权
整体架构
认证
Authentication
SecurityContextHolder
springboot整合springsecurity
流程分析
默认⽤户⽣成
总结
基本上涉及到⽤户参与的系统都要进⾏权限管理,权限管理属于系统安全的范畴,权限管理实现对⽤户访问系统的控制,按照安全规则或者安全策略控制⽤户可以访问⽽且只能访问⾃⼰被授权的资源。权限管理包括⽤户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源⽤户⾸先经过身份认证,认证通过后⽤户具有该资源的访问权限⽅可访问。
身份认证,就是判断⼀个⽤户是否为合法⽤户的处理过程。最常⽤的简单身份认证⽅式是系统通过核对⽤户输⼊的⽤户名和⼝令,看其是否与系统中存储的该⽤户的⽤户名和⼝令⼀致,来判断⽤户身份是否正确。对于采⽤指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
授权,即访问控制,控制谁能访问哪些资源。主体进⾏身份认证后需要分配权限⽅可访问系统的资源,对于某些资源没有权限是⽆法访问的
解决⽅案
和其他领域不同,在 Java 企业级开发中,安全管理框架⾮常少,⽬前⽐较常⻅的就是:
ShiroShiro 本身是⼀个⽼牌的安全管理框架,有着众多的优点,例如轻量、简单、易于集成、可以在JavaSE环境中使⽤等。不过,在微服务时代,Shiro 就显得⼒不从⼼了,在微服务⾯前和扩展⽅⾯,⽆法充分展示⾃⼰的优势。开发者⾃定义也有很多公司选择⾃定义权限,即⾃⼰开发权限管理。但是⼀个系统的安全,不仅仅是登录和权限控制这么简单,我们还要考虑种各样可能存在的⽹络政击以及防彻策略,从这个⻆度来说,开发者⽩⼰实现安全管理也并⾮是⼀件容易的事情,只有⼤公司才有⾜够的⼈⼒物⼒去⽀持这件事情。
Spring SecuritySpring Security,作为spring 家族的⼀员,在和 Spring 家族的其他成员如 Spring Boot Spring Clond等进⾏整合时,具有其他框架⽆可⽐拟的优势,同时对 OAuth2 有着良好的⽀持,再加上Spring Cloud对 Spring Security的不断加持(如推出 Spring Cloud Security ),让 Spring Securiy 不知不觉中成为微服务项⽬的⾸选安全管理⽅案。
在的架构设计中,认证和授权 是分开的,⽆论使⽤什么样的认证⽅式。都不会影响授权,这是两个独⽴的存在,这种独⽴带来的好处之⼀,就是可以⾮常⽅便地整合⼀些外部的解决⽅案
AuthenticationManager
在Spring Security中认证是由AuthenticationManager接⼝来负责的,接⼝定义为:
AuthenticationManager 主要实现类为 ProviderManager,在 ProviderManager
中管理了众多 AuthenticationProvider 实例。在⼀次完整的认证流程中,Spring Security 允许存在多个 AuthenticationProvider ,⽤来实现多种认证⽅式,这些 AuthenticationProvider 都是由 ProviderManager 进⾏统⼀管理的。
getAuthorities 获取⽤户权限信息
getCredentials 获取⽤户凭证信息,⼀般指密码
getDetails 获取⽤户详细信息
getPrincipal 获取⽤户身份信息,⽤户名、⽤户对象等
isAuthenticated ⽤户是否认证成功
SecurityContextHolder ⽤来获取登录之后⽤户信息。Spring Security 会将登录⽤户数据保存在 Session 中。但是,为了使⽤⽅便,Spring Security在此基础上还做了⼀些改进,其中最主要的⼀个变化就是线程绑定。当⽤户登录成功后,Spring Security 会将登录成功的⽤户信息保存到 SecurityContextHolder 中。
SecurityContextHolder 中的数据保存默认是通过ThreadLocal 来实现的,使⽤ ThreadLocal 创建的变量只能被当前线程访问,不能被其他线程访问和修改,也就是⽤户数据和请求线程绑定在⼀起。当登录请求处理完毕后,Spring Security 会将 SecurityContextHolder 中的数据拿出来保存到 Session 中,同时将 SecurityContexHolder 中的数据清空。以后每当有请求到来时,Spring Security就会先从 Session 中取出⽤户登录数据,保存到 SecurityContextHolder 中,⽅便在该请求的后续处理过程中使⽤,同时在请求结束时将 SecurityContextHolder 中的数据拿出来保存到 Session 中,然后将 Security SecurityContextHolder 中的数据清空。这⼀策略⾮常⽅便⽤户在 Controller、Service 层以及任何代码中获取当前登录⽤户数据。
环境搭建
引入依赖
org.springframework.boot
spring-boot-starter-security
只需引入这个依赖,所有的接⼝就会⾃动保护起来!
springsecurity提供默认的登录页面
实现原理:
https:"docs.spring.io/spring-security/site/docs/5.5.4/reference/html
5/#servlet-architecture
虽然开发者只需要引⼊⼀个依赖,就可以让 Spring Security 对应⽤进⾏保护。 Spring Security ⼜是如何做到的呢?
在 Spring Security 中 认证、授权 等功能都是基于过滤器完成的
需要注意的是,默认过滤器并不是直接放在 Web 项⽬的原⽣过滤器链中,⽽是通过⼀geFlterChainProxy 来统⼀管理。 Spring Security 中的过滤器链通过FilterChainProxy 嵌⼊到 Web项⽬的原⽣过滤器链中。 FilterChainProxy 作为⼀ 个顶层的管理者,将统⼀管理Security Filter。FilterChainProxy 本身是通过Spring 框架提供的 DelegatingFilterProxy 整合到原⽣的过滤器链中。
Security Filters
那么在 Spring Security 中给我们提供那些过滤器 ? 默认情况下那些过滤器会被加载
呢?
过滤器 |
过滤器作⽤ |
默认是否 加载 |
ChannelProcessingFilter |
过滤请求协议 HTTP 、HTTPS |
NO |
WebAsyncManagerIntegrationFilter |
将 WebAsyncManger 与 SpringSecurity 上下⽂进 ⾏集成 |
YES |
SecurityContextPersistenceFilter |
在处理请求之前 ,将安全信息加载到 SecurityContextHolder 中 |
YES |
HeaderWriterFilter |
处理头信息加⼊响应中 |
YES |
CorsFilter |
处理跨域问题 |
NO |
CsrfFilter |
处理 CSRF 攻击 |
YES |
LogoutFilter |
处理注销登录 |
YES |
OAuth2AuthorizationRequestRedirectFilter |
处理 OAuth2 认证重定向 |
NO |
Saml2WebSsoAuthenticationRequestFilter |
处理 SAML 认证 |
NO |
X509AuthenticationFilter |
处理 X509 认证 |
NO |
AbstractPreAuthenticatedProcessingFilter |
处理预认证问题 |
NO |
CasAuthenticationFilter |
处理 CAS 单点登录 |
NO |
OAuth2LoginAuthenticationFilter |
处理 OAuth2 认证 |
NO |
Saml2WebSsoAuthenticationFilter |
处理 SAML 认证 |
NO |
UsernamePasswordAuthenticationFilter |
处理表单登录 |
YES |
OpenIDAuthenticationFilter |
处理 OpenID 认证 |
NO |
DefaultLoginPageGeneratingFilter |
配置默认登录⻚⾯ |
YES |
DefaultLogoutPageGeneratingFilter |
配置默认注销⻚⾯ |
YES |
ConcurrentSessionFilter |
处理 Session 有效期 |
NO |
DigestAuthenticationFilter |
处理 HTTP 摘要认证 |
NO |
BearerTokenAuthenticationFilter |
处理 OAuth2 认证的 Access Token |
NO |
BasicAuthenticationFilter |
处理 HttpBasic 登录 |
YES |
RequestCacheAwareFilter |
处理请求缓存 |
YES |
SecurityContextHolder<br 'AwareRequestFilter |
包装原始请求 |
YES |
JaasApiIntegrationFilter |
处理 JAAS 认证 |
NO |
RememberMeAuthenticationFilter |
处理 RememberMe 登录 |
NO |
AnonymousAuthenticationFilter |
配置匿名认证 |
YES |
OAuth2AuthorizationCodeGrantFilter |
处理OAuth2认证中授权码 |
NO |
SessionManagementFilter |
处理 session 并发问题 |
YES |
ExceptionTranslationFilter |
处理认证/授权中的异常 |
YES |
FilterSecurityInterceptor |
处理授权相关 |
YES |
SwitchUserFilter |
处理账户切换 |
NO |
可以看出, Spring Security 提供了 30 多个过滤器。默认情况下Spring Boot 在对 Spring Security 进⼊⾃动化配置时,会创建⼀个名为 SpringSecurityFilerChain 的过滤器,并注⼊到 Spring 容器中,这个过滤器将负责所有的安全管理,包括⽤户认证、 授权、重定向到登录⻚⾯等。具体可以参考WebSecurityConfiguration的源码 :
SpringBootWebSecurityConfiguration
这个类是 spring boot ⾃动配置类,通过这个源码得知,默认情况下对所有请求进⾏权限控制 :
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {
@Bean
@Order(SecurityProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity
http)
throws Exception {
http.authorizeRequests().anyRequest()
.authenticated().and().formLogin().and().httpBasic();
return http.build();
}
}
这就是为什么在引⼊ Spring Security 中没有任何配置情况下,请求会被拦截的原因!
条件⼀ classpath中存在 SecurityFilterChain.class,HttpSecurity.class
条件⼆ 没有⾃定义 WebSecurityConfigurerAdapter.class,SecurityFilterChain.class
class DefaultWebSecurityCondition extends AllNestedConditions {
DefaultWebSecurityCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnClass({ SecurityFilterChain.class,
HttpSecurity.class })
static class Classes {
}
@ConditionalOnMissingBean({ WebSecurityConfigurerAdapter.class,
SecurityFilterChain.class })
static class Beans {
}
}
默认情况下,条件都是满⾜的。 WebSecurityConfigurerAdapter 这个类极其重要, Spring Security 核⼼配置都在这个类中 :
如果要对 Spring Security 进⾏⾃定义配置,就要⾃定义这个类实例,通过覆盖类中⽅ 法达到修改默认配置的⽬的。
1. 请求 /hello 接⼝,在引⼊ spring security 之后会先经过⼀些列过滤器
2. 在请求到达 FilterSecurityInterceptor时,发现请求并未认证。请求拦截下来, 并抛出 AccessDeniedException 异常。
3. 抛出 AccessDeniedException 的异常会被 ExceptionTranslationFilter 捕 获,这个 Filter 中会调⽤ LoginUrlAuthenticationEntryPoint#commence ⽅法给客户端返回 302,要求客户端进⾏重定向到 /login ⻚⾯。
4. 客户端发送 /login 请求。
5. /login 请求会再次被拦截器中 DefaultLoginPageGeneratingFilter 拦截到, 并在拦截器中返回⽣成登录⻚⾯。
就是通过这种⽅式, Spring Security 默认过滤器中⽣成了登录⻚⾯,并返回!
1. 查看 SpringBootWebSecurityConfiguration#defaultSecurityFilterChain ⽅法表单登录
2. 处理登录为 FormLoginConfigurer 类中 调⽤UsernamePasswordAuthenticationFilter这个类实例
3. 查看类中 UsernamePasswordAuthenticationFilter的attempAuthentication ⽅法得知实际调⽤ AuthenticationManager 中 authenticate ⽅法
4. 调⽤ ProviderManager 类中⽅法 authenticate
5. 调⽤了 ProviderManager 实现类中AbstractUserDetailsAuthenticationProvider类中⽅法
6. 最终调⽤实现类 DaoAuthenticationProvider 类中⽅法⽐较
看到这⾥就知道默认实现是基于 InMemoryUserDetailsManager 这个类 ,也就是内存的 实现 !
UserDetailService
通过上面源码分析也能得知 UserDetailService 是顶层⽗接⼝,接⼝中loadUserByUserName ⽅法是⽤来在认证时进⾏⽤户名认证⽅法,默认实现使⽤是内存实 现,如果想要修改数据库实现我们只需要⾃定义 UserDetailService 实现,最终返回 UserDetails 实例即可
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException;
}
UserDetailServiceAutoConfigutation
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(
value = { AuthenticationManager.class,
AuthenticationProvider.class, UserDetailsService.class,
AuthenticationManagerResolver.class },
type = { "org.springframework.security.oauth2.jwt.JwtDecoder",
"org.springframework.security.oauth2.server.resource.introspection
.OpaqueTokenIntrospector",
"org.springframework.security.oauth2.client.registration.ClientReg
istrationRepository" })
public class UserDetailsServiceAutoConfiguration {
"....
@Bean
@Lazy
public InMemoryUserDetailsManager
inMemoryUserDetailsManager(SecurityProperties properties,
ObjectProvider passwordEncoder) {
SecurityProperties.User user = properties.getUser();
List roles = user.getRoles();
return new InMemoryUserDetailsManager(
User.withUsername(user.getName()).password(getOrDeducePassword(use
r, passwordEncoder.getIfAvailable()))
.roles(StringUtils.toStringArray(roles)).build());
}
}
结论
1. 从⾃动配置源码中得知当 classpath 下存在 AuthenticationManager 类
2. 当前项⽬中,系统没有提供 AuthenticationManager.class、AuthenticationProvider.class、UserDetailsService.class、AuthenticationManagerResolver.class实例
默认情况下都会满⾜,此时Spring Security会提供⼀个InMemoryUserDetailManager 实例
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {
private final User user = new User();
public User getUser() {
return this.user;
}
"....
public static class User {
private String name = "user";
private String password = UUID.randomUUID().toString();
private List roles = new ArrayList)();
private boolean passwordGenerated = true;
"get set (
}
}
这就是默认⽣成 user 以及 uuid 密码过程! 另外看明⽩源码之后,就知道只要在配置⽂
件中加⼊如下配置可以对内存中⽤户和密码进⾏覆盖。
spring.security.user.name=root
spring.security.user.password=root
spring.security.user.roles=admin,users
AuthenticationManager、ProviderManger、以及 AuthenticationProvider三者关系
WebSecurityConfigurerAdapter 扩展 Spring Security 所有默认配置
UserDetailService ⽤来修改默认认证的数据源信息