SpringSecurity源码流程与JWT应用
2021-01-23 13:52:27 21 举报
AI智能生成
SpringSecurity逐行源码流程与JWT应用
作者其他创作
大纲/内容
一些重要的Filter类
GenericFilterBean
OncePerRequestFilter
过滤链
ChannelProcessingFilter
处理https,没配置就没有?
WebAsyncManagerIntegrationFilter
将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成
SecurityContextPersistenceFilter
new HttpRequestResponseHolder(request,response)
HttpSessionSecurityContextRepository#loadContext
从request中获取session,从Session中取出已认证用户的信息保存在SecurityContext中,提高效率,
避免每一次请求都要解析用户认证信息,方便接下来的filter直接获取当前的用户信息
避免每一次请求都要解析用户认证信息,方便接下来的filter直接获取当前的用户信息
如果是第一次请求,session没有相关信息,那么会创建一个新的SecurityContext
包装request、response
SecurityContextHolder.setContext(contextBeforeChainExecution);
finally
将上下文保存到HttpSessionSecurityContextRepository
清除Holder中的上下文
HeaderWriterFilter
往该请求的Header中添加相应的信息,在http标签内部使用security:headers来控制
CorsFilter
未配置就没有
CsrfFilter
对需要验证的请求验证是否包含csrf的token信息,如果不包含,则报错。
这样攻击网站无法获取到token信息,则跨域提交的信息都无法通过过滤器的校验
这样攻击网站无法获取到token信息,则跨域提交的信息都无法通过过滤器的校验
LogoutFilter
根据request的请求方法和路径匹配判断当前是否为注销URL
默认匹配 POST /logout
this.handler.logout(request, response, auth);
CsrfLogoutHandler
SecurityContextLogoutHandler
使session失效
清除remember me
清除SecurityContextHolder的SecurityContext
LogoutSuccessEventPublishingLogoutHandler
通知注销事件
SimpleUrlLogoutSuccessHandler#onLogoutSuccess(request, response, auth);
重定向,默认为/login?logout
...
UsernamePasswordAuthenticationFilter
AntPathRequestMatcher#matches
是
try {
Authentication authResult = UsernamePasswordAuthenticationFilter#attemptAuthentication(request, response);
if (authResult == null) {
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (AuthenticationException failed){
unsuccessfulAuthentication(request, response, failed);
return;
}
Authentication authResult = UsernamePasswordAuthenticationFilter#attemptAuthentication(request, response);
if (authResult == null) {
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (AuthenticationException failed){
unsuccessfulAuthentication(request, response, failed);
return;
}
String username = obtainUsername(request);
String password = obtainPassword(request);
new UsernamePasswordAuthenticationToken(username, password)
setDetails(request, token);
return this.getAuthenticationManager().authenticate(token);
遍历本Manager和父Manager的所有AuthenticationProvider
如果有AuthenticationProvider支持处理当前类型的Authentication
如果有AuthenticationProvider支持处理当前类型的Authentication
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
AbstractUserDetailsAuthenticationProvider#authenticate(Authentication authentication)
try {
user = retrieveUser(username,authentication);
}catch (UsernameNotFoundException notFound){
throw new BadCredentialsException(xx)
}
user = retrieveUser(username,authentication);
}catch (UsernameNotFoundException notFound){
throw new BadCredentialsException(xx)
}
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException
}
return loadedUser;
}
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException
}
return loadedUser;
}
preAuthenticationChecks.check(user);
!user.isAccountNonLocked()
!user.isEnabled()
!user.isAccountNonExpired()
additionalAuthenticationChecks(user,authentication)
if (authentication.getCredentials() == null)
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword()))
postAuthenticationChecks.check(user);
if (!user.isCredentialsNonExpired())
转换成新的UsernamePasswordAuthenticationToken返回
如果验证成功
擦除所有地方的密码信息
发布AuthenticationSuccessEvent事件
return result;
如果验证失败
抛出AuthenticationException
catch (AuthenticationException failed){
unsuccessfulAuthentication(request, response, failed);
return;
}
unsuccessfulAuthentication(request, response, failed);
return;
}
清除SecurityContext
rememberMeServices.loginFail(request, response);
failureHandler.onAuthenticationFailure(request, response, failed);
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authResult);
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
发布InteractiveAuthenticationSuccessEvent事件
successHandler.onAuthenticationSuccess(request, response, authResult);
重定向
不是
chain.doFilter
return
...
DefaultLoginPageGeneratingFilter
if (isLoginUrlRequest(request) || loginError || logoutSuccess)
硬编码html,response返回输出
String loginPageHtml = generateLoginPageHtml(request, loginError,
logoutSuccess);
logoutSuccess);
response.getWriter().write(loginPageHtml);
return
DefaultLogoutPageGeneratingFilter
if (this.matcher.matches(request))
硬编码html,response返回输出
renderLogout(request, response);
BasicAuthenticationFilter
ConcurrentSessionFilter
取出session
若过期,进入注销逻辑,return
没过期,更新session
没session就跳过
...
BasicAuthenticationFilter
没配置就没有
处理HTTP请求中的BASIC authorization头部,把认证结果写入SecurityContextHolder
RequestCacheAwareFilter
从缓存中寻找是否已经有解析过的请求,若有,替换掉原生请求
否则,继续
SecurityContextHolderAwareRequestFilter
封装Request,丰富API
chain.doFilter(this.requestFactory.create((HttpServletRequest) req,
(HttpServletResponse) res), res)
(HttpServletResponse) res), res)
...
RememberMeAuthenticationFilter
当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息,
如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统
如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统
AnonymousAuthenticationFilter
检测 SecurityContextHolder 中的SecurityContext的 Authentication是否为null,若是
生成一个AnonymousAuthenticationToken
key随机生成,username为anonymous,权限只有ROLE_Anonymous
authenticated为true
key随机生成,username为anonymous,权限只有ROLE_Anonymous
authenticated为true
否则跳过
...
SessionManagementFilter
sessionAuthenticationStrategy.onAuthentication(authentication,
request, response)
request, response)
securityContextRepository.saveContext(SecurityContextHolder.getContext(),
request, response);
request, response);
ExceptionTranslationFilter
直接chain.doFilter(request, response)
通过catch处理下一个Filter或应用逻辑产生的异常
AuthenticationException
sendStartAuthentication
AccessDeniedException
当前是匿名认证或者是 记住我
sendStartAuthentication(request,response,chain,new InsufficientAuthenticationException(xx))
SecurityContextHolder.getContext().setAuthentication(null);
requestCache.saveRequest(request, respo
authenticationEntryPoint.commence(request, response, reason);
其他
accessDeniedHandler.handle(request, response,(AccessDeniedException) exception);
FilterSecurityInterceptor
InterceptorStatusToken token = AbstractSecurityInterceptor#beforeInvocation
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object)
.getAttributes(object)
初步判断上下文是否有Authentication对象
没有就直接抛AuthenticationCredentialsNotFoundException
Authentication authenticated = authenticateIfRequired();
首先再次获取到authentication对象
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication()
.getAuthentication()
if (authentication.isAuthenticated())
是则直接返回authentication
authentication = authenticationManager.authenticate(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
try {
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException){
// 发布AuthorizationFailureEvent事件
throw accessDeniedException; // 再次抛出
}
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException){
// 发布AuthorizationFailureEvent事件
throw accessDeniedException; // 再次抛出
}
for (AccessDecisionVoter voter : getDecisionVoters())
int result = voter.vote(authentication, object, configAttributes);
结果相加,判断是否通过
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,attributes);
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,attributes, object)
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
继续执行Servlet的过滤链
进入dispatcherServlet
AbstractSecurityInterceptor#finallyInvocation
AbstractSecurityInterceptor#afterInvocation
综合应用
利用JWT实现前后端分离的认证与授权
利用JWT实现前后端分离的认证与授权
思路分析
SpringSecurity自带的过滤器认证原理
请求登录
检验参数是否正确
如果正确,生成sessionId,放在Cookie:JSESSIONID给前端浏览器保存
请求资源
前端每次资源访问带着Cookie,SpringSecurity解析Cookie中的SessionId,在内存中比对判断是否之前登陆过
如果比对成功,会拿出之前解析过的SecurityContext,带有已经解析过的认证信息,之后检验的时候就会直接放行,不需要再检验
前后端分离情景下的分析
但是前后端分离存在跨域,前端需要通过ajax发送登录或者获取资源的请求,那么就需要特殊处理带上Cookie,存在XSS风险
如果Cookie设置为HttpOnly,也存在XSRF风险
如果Cookie设置为HttpOnly,也存在XSRF风险
因此,选择在请求头放入Token,采用JWT认证方式,后端解析Token来进行认证与授权
实现JWT的要求
请求登录
请求体中带有username和password
为了更安全,password可为加密过的
如果正确,运用JWT工具包生成Token,放在响应头给前端浏览器保存
请求资源
前端使用工具,如VUEX,每个资源请求都在请求头带上Token
服务器读取请求头中的Token并解析,若成功,根据Token中的信息生成Authentication对象,放入SecurityContext
成功进入请求的接口,进行对应的逻辑,返回资源
结合SpringSecurity具体实现
我们实现的Filter
结合SpringSecurity具体实现
思路
首先明确,我们需要实现自己的Filter的方法
因为默认内置的Filter都是操作Cookie/Session的,无法满足要求
因为默认内置的Filter都是操作Cookie/Session的,无法满足要求
认证流程根据SpringSecurity的方法来
Filter委托Manager来具体认证
Manager委托所有的Provider来认证
每个Provider利用UserdetailsService的方法来具体实现认证。
Filter委托Manager来具体认证
Manager委托所有的Provider来认证
每个Provider利用UserdetailsService的方法来具体实现认证。
由于内置的UsernamePasswordAuthenticationFilter中的Manager是通过Configurer类配置的
我们的Filter可以通过继承很容易得到,另外再配置Manager、实现具体的UserdetailsService就行了
我们的Filter可以通过继承很容易得到,另外再配置Manager、实现具体的UserdetailsService就行了
思路过程
-可能最佳的选择是继承UsernamePasswordAuthentication/AbstractAuthenticationProcessingFilter,
因为功能最多,而且可以配合SpringSecurity自身的逻辑。
比如异常处理机制。改造相关的方法即可。
-也可以选择继承BasicAuthenticationFilter、OncePerRequestFilter,更加简单
-最后通过http.addFilter()添加到容器即可。
因为功能最多,而且可以配合SpringSecurity自身的逻辑。
比如异常处理机制。改造相关的方法即可。
-也可以选择继承BasicAuthenticationFilter、OncePerRequestFilter,更加简单
-最后通过http.addFilter()添加到容器即可。
不同的父类,具体操作方法会有较大不同!
在右边源码流程可以知道,添加我们自定义的Filter是在Manager初始化之后的
所以我们在配置类中添加Filter的时候就可以为Filter指定当前容器内的Manger(调用WebSecurityConfigurerAdapter#authenticationManager方法)
所以我们在配置类中添加Filter的时候就可以为Filter指定当前容器内的Manger(调用WebSecurityConfigurerAdapter#authenticationManager方法)
所以唯一需要具体分情况编写的就是UserdetailsService/Provider
若只实现UserdetailsService,则会自动生成默认Provider包装
注入到Manager的方法
直接使用@Bean注入容器,会被自动检测到
使用http.userdetailsService()
若自定义Provider和UserdetailsService,还可以自定义缓存等其他配置
注入到Manager的方法
直接使用@Bean注入容器,会被自动检测到
具体运作流程
JWTutils
引入io.jsonwebtoken:jjwt依赖
引入javax.xml.bind:jaxb-api
需要提供的方法
根据UserDetails生成Token
根据Token解析生成UserDetails
如果Token瞎写的或者过期会报异常
根据旧Token,刷新Token的过期时间,返回新的Token
一次请求
进入自定义的UsernamePasswordAuthenticationFilter
路径为登陆路径,如/login
否则进入验证登录的逻辑
从requestbody里提取username,password
通过new UsernamePasswordAuthenticationToken
(username, password)获建造一个Authentication
(username, password)获建造一个Authentication
调用authenticationManager.authenticate(Authentication)
若验证成功,返回生成的Token
路径为其他路径,请求资源
检测是否携带token
没有Token或者Token瞎写的或者Token过期,返回对应的提示信息
Token解析正确,进入Controller执行对应逻辑,返回资源和刷新后的Token
解决CORS跨域问题
http.cors()
#addCorsMappings
@CrossOrigin(allowCredentials = "true", allowedHeaders = "*")
配置CorsConfigurationSource的Bean
服务器端权限控制
方法权限注解控制
JSR
@Rolesallowed
@secured
表达式
@EnableGlobalMethodSecurity
SPEL
@PreAuthorize
@PreAuthorize("hasRole('USER')")
@PreAuthorize("hasPermission(#contact, 'admin')")
@PreAuthorize("#c.name == authentication.name")
@P
@Param
@PreAuthorize(“authentication.principal.username.equals(#username)”)
@PostAuthorize
@PostAuthorize("returnObject.id%2==0")
源码流程
自动配置类初始化
SecurityAutoConfiguration
@import
SpringBootWebSecurityConfiguration
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
如果有手动的配置类则跳过
负责未自定义的情况下提供一个默认的WebSecurityConfigurerAdapter,配置了基本信息、基本账户
WebSecurityEnablerConfiguration
@EnableWebSecurity
@EnableGlobalAuthentication
@Import(AuthenticationConfiguration.class)
@Import(ObjectPostProcessorConfiguration.class)
AutowireBeanFactoryObjectPostProcessor
postProcess(T object)
对object调用bean工厂的initializeBean方法
回调Aware接口
回调afterPropertiesSet
调用postProcessBeforeInitialization
对object调用bean工厂的autowireBean方法
populateBean
AuthenticationManagerBuilder
实现类为DefaultPasswordEncoderAuthenticationManagerBuilder
初始化3个认证管理器的configurerBean类,如果是(非直接手动操控authManagerBuilder)的配置,后面会取来用
EnableGlobalAuthenticationAutowiredConfigurer
使得优先初始化@EnableGlobalAuthentication的的BEAN
InitializeAuthenticationProviderBeanManagerConfigurer
InitializeUserDetailsBeanManagerConfigurer
作用:在之后检测上下文中是否有自定义的UserDetailsService,如果有,进行自动化配置
@Import
WebSecurityConfiguration
#setFilterChainProxySecurityConfigurer()
使用objectPostProcessor加工new出WebSecurity对象
读取上下文中所有webSecurityConfigurer类,排序
将所有webSecurityConfigurer加入webSecurity
#springSecurityFilterChain()
return webSecurity.build()
--doBuild()
--doBuild()
beforeInit()
init();
调用所有的WebSecurityConfigurerAdapter#init(final WebSecurity web)
HttpSecurity http = getHttp();
AuthenticationManager authenticationManager = authenticationManager();
--getAuthenticationManager();
--getAuthenticationManager();
WebSecurityConfigurerAdapter#configure(AuthenticationManagerBuilder auth)
注意!父类这里会默认设置disableLocalConfigureAuthenticationBldr属性为true,
即关闭了手动调用过此方法操控AuthenticationManagerBuilder的功能,子类覆盖此方法即可打破此规则。
所以SpringSecurity提供了两种配置方法:
即关闭了手动调用过此方法操控AuthenticationManagerBuilder的功能,子类覆盖此方法即可打破此规则。
所以SpringSecurity提供了两种配置方法:
authenticationConfiguration+authenticationBuilder
采用此方法,只能使用@Bean来操控Manager Build过程
采用此方法,只能使用@Bean来操控Manager Build过程
AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
将globalAuthConfigurers的三个configurer的Bean加入builder
authBuilder.apply(config);
authenticationManager = authBuilder.build();
beforeInit()
init();
添加InitializeAuthenticationProviderManagerConfigurer
添加InitializeUserDetailsManagerConfigurer
beforeConfigure
configure();
InitializeAuthenticationProviderManagerConfigurer
寻找容器内自定义的AuthenticationProvider类,有就添加到bulider
InitializeUserDetailsBeanManagerConfigurer
寻找容器内自定义的UserDetailsService类,有就处理后添加到bulider
容器内寻找PasswordEncoder
容器内寻找UserDetailsPasswordService
new出DaoAuthenticationProvider(),并应用上面三个属性
调用DaoAuthenticationProvider#afterPropertiesSet
return performBuild();
new ProviderManager(authenticationProviders,parentAuthenticationManager:null)
配置eraseCredentials
配置eventPublisher
调用AutowireBeanFactoryObjectPostProcessor#postProcess后置处理认证管理器
localConfigureAuthenticationBldr
采用此方法,只能覆盖configure方法来操控Manager Build过程
采用此方法,只能覆盖configure方法来操控Manager Build过程
return localConfigureAuthenticationBldr.build()
两种方法功能几乎一样,不过只能选其一。
authenticationBuilder.parentAuthenticationManager(authenticationManager);
持有引用
新建并把所有认证相关的类放入sharedObjects
UserDetailsServiceDelegator(Arrays.asList(localConfigureAuthenticationBldr, globalAuthBuilder)
ApplicationContext
ContentNegotiationStrategy
AuthenticationTrustResolver
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,sharedObjects)
对http对象应用默认配置
链式配置的原理就是给http(HttpSecurity)对象添加了各种configurer对象
从META-INF/spring.factories获取所有AbstractHttpConfigurer并加入HttpSecurity
configure(HttpSecurity http)
web
.addSecurityFilterChainBuilder(http)
.postBuildAction(() -> {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
})
将HttpSecurity对象作为SecurityFilterChainBuilder对象加入webSecurity
.addSecurityFilterChainBuilder(http)
.postBuildAction(() -> {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
})
将HttpSecurity对象作为SecurityFilterChainBuilder对象加入webSecurity
从HttpSecurity对象中获取一个postBuildAction的Runnable对象,添加到webSecurity
作用是建造完成后为WebSecurity添加FilterSecurityInterceptor对象
beforeConfigure();
configure();
调用所有configurer的configurer.configure(WebSecurity web);
可调用web#ignoring()方法忽略对一些url的验证
可开启调试模式
return performBuild();
List securityFilterChains = new ArrayList<>(chainSize)
对于所有的ignoredRequests,各新建一条DefaultSecurityFilterChain
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
对于所有的securityFilterChainBuilder,开始构造,结果放入securityFilterChains
securityFilterChains.add(securityFilterChainBuilder.build());
#init
SessionManagementConfigurer
RequestCacheConfigurer
AnonymousConfigurer
添加了一个AnonymousAuthenticationProvider
FormLoginConfigurer
#beforeConfigure
建造AuthenticationManager
#configure
ExceptionHandlingConfigurer
ExceptionTranslationFilter
ExpressionUrlAuthorizationConfigurer
FilterSecurityInterceptor
FormLoginConfigurer
HttpSecurity#build返回的是DefaultSecurityFilterChain对象,即所说的SpringSecurity过滤链对象,包括了所需要的所有Filter
return new DefaultSecurityFilterChain(requestMatcher, filters);
自定义的ObjectProcessor在这里生效
new FilterChainProxy(securityFilterChains)
postBuildAction.run();
return filterChainProxy
WebSecurity#build返回的是FilterChainProxy对象
#webSecurityExpressionHandler()
SpringWebMvcImportSelector
WebMvcSecurityConfiguration
OAuth2ImportSelector
SecurityDataConfiguration
new DefaultAuthenticationEventPublisher()
SecurityFilterAutoConfiguration
new DelegatingFilterProxyRegistrationBean(“springSecurityFilterChain”)
new DelegatingFilterProxy(this.targetBeanName, getWebApplicationContext()
UserDetailsServiceAutoConfiguration
注入一个InMemoryUserDetailsManager
读取SecurityProperties
提供一个随机生成的帐号密码
可在配置文件中手动配置
处理一次请求
FilterChainProxy#doFilter
doFilterInternal(request, response, chain);
防火墙包装request、response,并校验一部分属性
根据request路径选出一个match的SecurityFilterChain,取出它的过滤链
如果取出的SecurityFilterChain是之前配置过的ignore,则没有过滤链,直接进行下一个Servlet 的Filter
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
包装一层
VirtualFilterChain#doFilter
利用currentPosition计数,先执行SpringSecurity的过滤链
见左边过滤链
然后继续执行Servelet容器其他的Filter
SecurityContextHolder.clearContext();
0 条评论
下一页
为你推荐
查看更多