# spring-security-test-parent **Repository Path**: larryzeal/spring-security-test-parent ## Basic Information - **Project Name**: spring-security-test-parent - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2017-12-23 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Spring Security Study ### requirements 1. `JDK 8` 2. `MAVEN 3` 3. `Tomcat 8` ### architecture 结构上,采用了 maven 的多模块结构,由 parent pom 负责依赖的版本、maven plugin 的设置。 技术上,采用了 `Spring` + `JPA` + `HTML` (前后端分离)。 ### settings 分为通用配置和模块特有配置,前者由 _common_config_ 模块负责,后者位于每个模块中。 主要使用 `Java Config` 形式,仅 _t01_starter_test_ 引入了 `Namespace` 配置。 Spring Security的 `filter` 在 _common_config_ 模块的 [config/WebInitializer]()#___onStartp___ 中设置。 ## Spring Security核心 `AuthenticationManager` > [`AuthenticationProvider`], `AccessDecisionManager`, `Authentication`, `GrantedAuthority`, `AuthenticationException`, `AccessDeniedException` <--> `ExceptionTranslationFilter` `ExceptionTranslationFilter`会根据异常类型来调用不同的功能。 `session` -- 应该是http session吧?是!见[org.springframework.security.web.context.HttpSessionSecurityContextRepository]()# __readSecurityContextFromSession(..)__ 中的 httpSession.getAttribute(this.springSecurityContextKey) `SecurityContext` `SecurityContextHolder` `SecurityContextPersistenceFilter` > 在认证成功之前,SecurityContext不存在,SecurityContextHolder中是null。 > 在认证成功之后,SecurityContext存在,SecurityContextHolder中保存了SecurityContext (ThreadLocal)。 那么,既然是ThreadLocal的,又如何在线程切换(request + thread pool)后还能保持? 这就是`SecurityContextPersistenceFilter`的功能: 1. 每次request,都从session中获取SecurityContext,并存入SecurityContextHolder中。 2. 而在请求结束之后,又将SecurityContextHolder中的SecurityContext存入session, 3. 最后,清除SecurityContextHolder中的SecurityContext! ## Filter - by order 1. `ChannelProcessingFilter` 用来处理`channel`的,就是`http`或`https`请求,如果请求不对,就跳转到对的`channel`上! 2. `SecurityContextPersistenceFilter` 前面有提过。 3. `ConcurrentSessionFilter` 需要使用`SecurityContextHolder`,且更新`session`的最后更新时间,以及检查`session`有无过期 - 过期则调用`LogoutHandler`。 4. 认证处理机制,如`UsernamePasswordAuthenticationFilter`,`CasAuthenticationFilter`,`BasicAuthenticationFilter`,从而让`SecurityContextHolder`包含有效的`Authentication`。 5. `SecurityContextHolderAwareRequestFilter` 封装`HttpServletRequest`。 6. `JaasApiIntegrationFilter` -- 这是什么?jaas 7. `RememberMeAuthenticationFilter` 如果之前的认证处理机制没有更新 SecurityContextHolder,并且用户请求包含了一个 Remember-Me 对应的 cookie,那么一个对应的 Authentication 将会设给 SecurityContextHolder 8. `AnonymousAuthenticationFilter`,如果之前的认证机制都没有更新 SecurityContextHolder 拥有的 Authentication,那么一个 AnonymousAuthenticationToken 将会设给 SecurityContextHolder 9. `ExceptionTransactionFilter`,用于处理在 FilterChain 范围内抛出的 AccessDeniedException 和 AuthenticationException,并把它们转换为对应的 Http 错误码返回或者对应的页面。 10. `FilterSecurityInterceptor`,保护 Web URI,并且在访问被拒绝时抛出异常。 > 当我们在使用 NameSpace 时,Spring Security 是会自动为我们建立对应的 FilterChain 以及其中的 Filter。 但有时我们可能需要添加我们自己的 Filter 到 FilterChain,又或者是因为某些特性需要自己显示的定义 Spring Security 已经为我们提供好的 Filter,然后再把它们添加到 FilterChain。 使用 NameSpace 时添加 Filter到 FilterChain 是通过 http 元素下的 custom-filter 元素来定义的。 定义 custom-filter 时需要我们通过 ref 属性指定其对应关联的是哪个 Filter,此外还需要通过 position、before 或者 after 指定该 Filter 放置的位置。 Spring Security 对那些内置的 Filter 都指定了一个别名,同时指定了它们的位置。 需要注意的是被代理的 Filter 的初始化方法 init() 和销毁方法 destroy() 默认是不会被执行的。 通过设置 DelegatingFilterProxy 的 targetFilterLifecycle 属性为 true,可以使被代理 Filter 与 DelegatingFilterProxy 具有同样的生命周期。 Spring Security会保存之前的请求信息,这是由ExceptionTranslationFilter完成的,默认将request保存在session中! `ConfigAttribute`将通过注解`@Secured( {"ROLE_USER", "ROLE_ADMIN"} )`的形式定义在受保护的方法上,或者通过 `access` 属性定义在受保护的 `URL` 上。 那么,理一理逻辑。 方法上的注解不用多说,肯定是硬编码的。 那URL呢?以上我们看到的也都是硬编码的权限,这肯定不行,不灵活。 如何针对某个资源新增权限,并赋给某个用户? 当访问某个URL时,必定需要去加载相应的权限配置 - 或者启动时就加载好,但这样不适合动态的权限处理。 anyway,肯定需要对比用户的权限与URL的权限。AccessDecisionManager! 认证的时候,加载用户自己的角色、权限集合!-- 务必注意,是用户自己的authentication! 然后,`AccessDescisionManager`会将其与目标对象的`ConfigAttribute`集合进行匹配,看看是否有相应的权限。 问题来了,这个目标对象的 _`ConfigAttribute`集合_ 什么时候出来的??? 查看decide方法的使用,可以看到 [org.springframework.security.access.intercept.AbstractSecurityInterceptor]()# __beforeInvocation__ 中有调用, 而这里传入的对象是 __Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object);__。 OK了,继续看这个 __obtainSecurityMetadataSource__ 方法,有两个实现,类`MethodSecurityInterceptor`这个明显不是我们想要的, 类`FilterSecurityInterceptor`这个才是我们想要的! 返回的是`FilterInvocationSecurityMetadataSource securityMetadataSource`,哪里传入的呢?我们看到这是一个property,猜想应该是有一个bean注入的。 先看看这个类,原来是个接口,不用多说,继续查看其实现类,有两个:`DefaultFilterInvocationSecurityMetadataSource`和`ExpressionBasedFilterInvocationSecurityMetadataSource`。 顾名思义,一个是默认的,一个是基于SpEL的。 先看默认的,其方法 [org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource]()# __getAttributes__ 很有意思。 ```java public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { public Collection getAttributes(Object object) { final HttpServletRequest request = ((FilterInvocation) object).getRequest(); for (Map.Entry> entry : requestMap.entrySet()) { if (entry.getKey().matches(request)) { return entry.getValue(); } } return null; } // ...其他略 } ``` 这个requestMap也很有意思,看起来是将所有的资源(URL)及配置(ConfigAttribute)封装起来,如果不是Spring MVC的功能,就是Spring Security的功能。 看这个类的构造,有参构造,可以继续查找使用了,有三个,ChannelSecurityConfigurer、ExpressionBasedFilterInvocationSecurityMetadataSource、UrlAuthorizationConfigurer。 UrlAuthorizationConfigurer,继续追踪,传入的是REGISTRY.createRequestMap(),而REGISTRY来自构造方法: ```java public final class UrlAuthorizationConfigurer> extends AbstractInterceptUrlConfigurer, H> { private final StandardInterceptUrlRegistry REGISTRY; public UrlAuthorizationConfigurer(ApplicationContext context) { this.REGISTRY = new StandardInterceptUrlRegistry(context); } FilterInvocationSecurityMetadataSource createMetadataSource(H http) { return new DefaultFilterInvocationSecurityMetadataSource(REGISTRY.createRequestMap()); } //... 其他略 } ``` 继续查看 createRequestMap(),好吧,下面比较复杂,总之就是有地方传入url/configAttribute。 冏,刚反应过来,其实重写 __FilterInvocationSecurityMetadataSource.getAttributes(object)__ 就行了! ```java class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{ public Collection getAttributes(Object object) throws IllegalArgumentException{} public Collection getAllConfigAttributes(){} public boolean supports(Class clazz){} } ``` 重写完了,需要注入到FilterSecurityInterceptor或者其子类实例中。 最后 -- 别忘了默认情况下URL的权限都是在配置的时候硬编码的! ## summary 如果想硬编码权限,直接使用http即可。 如果想实现灵活设定权限,需要提供自己的 __FilterInvocationSecurityMetadataSource__,并注入到 __FilterSecurityInterceptor__ 中。 ## error http://blog.csdn.net/zhanghaoxutao/article/details/60469922