SpringSecurity源码流程与JWT应用
2021-01-23 13:52:27 21 举报
AI智能生成
SpringSecurity逐行源码流程与JWT应用
作者其他创作
大纲/内容
一些重要的Filter类
GenericFilterBean
OncePerRequestFilter
过滤链
ChannelProcessingFilter
处理https,没配置就没有?
WebAsyncManagerIntegrationFilter
将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成
SecurityContextPersistenceFilter
HttpSessionSecurityContextRepository#loadContext
如果是第一次请求,session没有相关信息,那么会创建一个新的SecurityContext
包装request、response
SecurityContextHolder.setContext(contextBeforeChainExecution);
finally
将上下文保存到HttpSessionSecurityContextRepository
清除Holder中的上下文
HeaderWriterFilter
CorsFilter
未配置就没有
CsrfFilter
对需要验证的请求验证是否包含csrf的token信息,如果不包含,则报错。这样攻击网站无法获取到token信息,则跨域提交的信息都无法通过过滤器的校验
LogoutFilter
根据request的请求方法和路径匹配判断当前是否为注销URL
默认匹配 POST /logout
CsrfLogoutHandler
SecurityContextLogoutHandler
使session失效
清除remember me
清除SecurityContextHolder的SecurityContext
LogoutSuccessEventPublishingLogoutHandler
通知注销事件
重定向,默认为/login?logout
...
UsernamePasswordAuthenticationFilter
AntPathRequestMatcher#matches
是
String username = obtainUsername(request);
String password = obtainPassword(request);
return this.getAuthenticationManager().authenticate(token);
遍历本Manager和父Manager的所有AuthenticationProvider如果有AuthenticationProvider支持处理当前类型的Authentication
AbstractUserDetailsAuthenticationProvider#authenticate(Authentication authentication)
try { UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); if (loadedUser == null) { throw new InternalAuthenticationServiceException } return loadedUser;\t\t}
preAuthenticationChecks.check(user);
!user.isAccountNonLocked()
!user.isEnabled()
!user.isAccountNonExpired()
if (authentication.getCredentials() == null)
postAuthenticationChecks.check(user);
if (!user.isCredentialsNonExpired())
转换成新的UsernamePasswordAuthenticationToken返回
如果验证成功
擦除所有地方的密码信息
发布AuthenticationSuccessEvent事件
return result;
如果验证失败
抛出AuthenticationException
清除SecurityContext
SecurityContextHolder.getContext().setAuthentication(authResult);
发布InteractiveAuthenticationSuccessEvent事件
重定向
不是
chain.doFilter
return
DefaultLoginPageGeneratingFilter
if (isLoginUrlRequest(request) || loginError || logoutSuccess)
硬编码html,response返回输出
response.getWriter().write(loginPageHtml);
DefaultLogoutPageGeneratingFilter
if (this.matcher.matches(request))
BasicAuthenticationFilter
ConcurrentSessionFilter
取出session
若过期,进入注销逻辑,return
没过期,更新session
没session就跳过
没配置就没有
处理HTTP请求中的BASIC authorization头部,把认证结果写入SecurityContextHolder
RequestCacheAwareFilter
从缓存中寻找是否已经有解析过的请求,若有,替换掉原生请求
否则,继续
SecurityContextHolderAwareRequestFilter
封装Request,丰富API
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
检测 SecurityContextHolder 中的SecurityContext的 Authentication是否为null,若是
生成一个AnonymousAuthenticationTokenkey随机生成,username为anonymous,权限只有ROLE_Anonymousauthenticated为true
否则跳过
SessionManagementFilter
ExceptionTranslationFilter
通过catch处理下一个Filter或应用逻辑产生的异常
AuthenticationException
sendStartAuthentication
AccessDeniedException
当前是匿名认证或者是 记住我
SecurityContextHolder.getContext().setAuthentication(null);
其他
FilterSecurityInterceptor
InterceptorStatusToken token = AbstractSecurityInterceptor#beforeInvocation
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()\t\t\t\t.getAttributes(object)
初步判断上下文是否有Authentication对象
没有就直接抛AuthenticationCredentialsNotFoundException
Authentication authenticated = authenticateIfRequired();
首先再次获取到authentication对象
Authentication authentication = SecurityContextHolder.getContext()\t\t\t\t.getAuthentication()
if (authentication.isAuthenticated())
是则直接返回authentication
authentication = authenticationManager.authenticate(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
for (AccessDecisionVoter voter : getDecisionVoters())
结果相加,判断是否通过
继续执行Servlet的过滤链
进入dispatcherServlet
AbstractSecurityInterceptor#finallyInvocation
AbstractSecurityInterceptor#afterInvocation
综合应用利用JWT实现前后端分离的认证与授权
部分源代码
思路分析
SpringSecurity自带的过滤器认证原理
请求登录
检验参数是否正确
如果正确,生成sessionId,放在Cookie:JSESSIONID给前端浏览器保存
请求资源
前端每次资源访问带着Cookie,SpringSecurity解析Cookie中的SessionId,在内存中比对判断是否之前登陆过
前后端分离情景下的分析
但是前后端分离存在跨域,前端需要通过ajax发送登录或者获取资源的请求,那么就需要特殊处理带上Cookie,存在XSS风险如果Cookie设置为HttpOnly,也存在XSRF风险
因此,选择在请求头放入Token,采用JWT认证方式,后端解析Token来进行认证与授权
实现JWT的要求
请求体中带有username和password
为了更安全,password可为加密过的
如果正确,运用JWT工具包生成Token,放在响应头给前端浏览器保存
前端使用工具,如VUEX,每个资源请求都在请求头带上Token
服务器读取请求头中的Token并解析,若成功,根据Token中的信息生成Authentication对象,放入SecurityContext
成功进入请求的接口,进行对应的逻辑,返回资源
结合SpringSecurity具体实现
我们实现的Filter
思路
首先明确,我们需要实现自己的Filter的方法因为默认内置的Filter都是操作Cookie/Session的,无法满足要求
认证流程根据SpringSecurity的方法来Filter委托Manager来具体认证Manager委托所有的Provider来认证每个Provider利用UserdetailsService的方法来具体实现认证。
由于内置的UsernamePasswordAuthenticationFilter中的Manager是通过Configurer类配置的我们的Filter可以通过继承很容易得到,另外再配置Manager、实现具体的UserdetailsService就行了
思路过程
-可能最佳的选择是继承UsernamePasswordAuthentication/AbstractAuthenticationProcessingFilter,因为功能最多,而且可以配合SpringSecurity自身的逻辑。比如异常处理机制。改造相关的方法即可。-也可以选择继承BasicAuthenticationFilter、OncePerRequestFilter,更加简单-最后通过http.addFilter()添加到容器即可。
不同的父类,具体操作方法会有较大不同!
在右边源码流程可以知道,添加我们自定义的Filter是在Manager初始化之后的所以我们在配置类中添加Filter的时候就可以为Filter指定当前容器内的Manger(调用WebSecurityConfigurerAdapter#authenticationManager方法)
所以唯一需要具体分情况编写的就是UserdetailsService/Provider
若只实现UserdetailsService,则会自动生成默认Provider包装
注入到Manager的方法
直接使用@Bean注入容器,会被自动检测到
使用http.userdetailsService()
若自定义Provider和UserdetailsService,还可以自定义缓存等其他配置
具体运作流程
JWTutils
引入io.jsonwebtoken:jjwt依赖
引入javax.xml.bind:jaxb-api
需要提供的方法
根据UserDetails生成Token
根据Token解析生成UserDetails
如果Token瞎写的或者过期会报异常
根据旧Token,刷新Token的过期时间,返回新的Token
一次请求
进入自定义的UsernamePasswordAuthenticationFilter
路径为登陆路径,如/login
否则进入验证登录的逻辑
调用authenticationManager.authenticate(Authentication)
若验证成功,返回生成的Token
路径为其他路径,请求资源
检测是否携带token
没有Token或者Token瞎写的或者Token过期,返回对应的提示信息
Token解析正确,进入Controller执行对应逻辑,返回资源和刷新后的Token
解决CORS跨域问题
三选n
http.cors()
#addCorsMappings
@CrossOrigin(allowCredentials = \"true\
配置CorsConfigurationSource的Bean
SpringBoot 2.3.0下的SpringSecurity-------------------------整合JWT应用更新于2021/1/22
服务器端权限控制
方法权限注解控制
JSR
@Rolesallowed
@secured
表达式
@EnableGlobalMethodSecurity
SPEL
@PreAuthorize
@PreAuthorize(\"hasRole('USER')\")
@PreAuthorize(\
@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()
beforeInit()
init();
调用所有的WebSecurityConfigurerAdapter#init(final WebSecurity web)
HttpSecurity http = getHttp();
AuthenticationManager authenticationManager = authenticationManager();--getAuthenticationManager();
WebSecurityConfigurerAdapter#configure(AuthenticationManagerBuilder auth)
注意!父类这里会默认设置disableLocalConfigureAuthenticationBldr属性为true,即关闭了手动调用过此方法操控AuthenticationManagerBuilder的功能,子类覆盖此方法即可打破此规则。所以SpringSecurity提供了两种配置方法:
authenticationConfiguration+authenticationBuilder采用此方法,只能使用@Bean来操控Manager Build过程
AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
将globalAuthConfigurers的三个configurer的Bean加入builder
authBuilder.apply(config);
authenticationManager = authBuilder.build();
添加InitializeAuthenticationProviderManagerConfigurer
添加InitializeUserDetailsManagerConfigurer
beforeConfigure
configure();
InitializeAuthenticationProviderManagerConfigurer
寻找容器内自定义的AuthenticationProvider类,有就添加到bulider
寻找容器内自定义的UserDetailsService类,有就处理后添加到bulider
容器内寻找PasswordEncoder
容器内寻找UserDetailsPasswordService
new出DaoAuthenticationProvider(),并应用上面三个属性
调用DaoAuthenticationProvider#afterPropertiesSet
return performBuild();
配置eraseCredentials
配置eventPublisher
调用AutowireBeanFactoryObjectPostProcessor#postProcess后置处理认证管理器
localConfigureAuthenticationBldr采用此方法,只能覆盖configure方法来操控Manager Build过程
return localConfigureAuthenticationBldr.build()
两种方法功能几乎一样,不过只能选其一。
authenticationBuilder.parentAuthenticationManager(authenticationManager);
持有引用
新建并把所有认证相关的类放入sharedObjects
ApplicationContext
ContentNegotiationStrategy
AuthenticationTrustResolver
对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);\t\t})将HttpSecurity对象作为SecurityFilterChainBuilder对象加入webSecurity
从HttpSecurity对象中获取一个postBuildAction的Runnable对象,添加到webSecurity
作用是建造完成后为WebSecurity添加FilterSecurityInterceptor对象
beforeConfigure();
调用所有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
ExpressionUrlAuthorizationConfigurer
HttpSecurity#build返回的是DefaultSecurityFilterChain对象,即所说的SpringSecurity过滤链对象,包括了所需要的所有Filter
自定义的ObjectProcessor在这里生效
new FilterChainProxy(securityFilterChains)
postBuildAction.run();
return filterChainProxy
WebSecurity#build返回的是FilterChainProxy对象
#webSecurityExpressionHandler()
SpringWebMvcImportSelector
WebMvcSecurityConfiguration
OAuth2ImportSelector
SecurityDataConfiguration
new DefaultAuthenticationEventPublisher()
SecurityFilterAutoConfiguration
new DelegatingFilterProxyRegistrationBean(“springSecurityFilterChain”)
UserDetailsServiceAutoConfiguration
注入一个InMemoryUserDetailsManager
读取SecurityProperties
提供一个随机生成的帐号密码
可在配置文件中手动配置
处理一次请求
FilterChainProxy#doFilter
防火墙包装request、response,并校验一部分属性
根据request路径选出一个match的SecurityFilterChain,取出它的过滤链
如果取出的SecurityFilterChain是之前配置过的ignore,则没有过滤链,直接进行下一个Servlet 的Filter
包装一层
VirtualFilterChain#doFilter
利用currentPosition计数,先执行SpringSecurity的过滤链
见左边过滤链
然后继续执行Servelet容器其他的Filter
SecurityContextHolder.clearContext();
0 条评论
回复 删除
下一页