spring security源码分析心得
看了半天的文档及源码,终于理出了spring-security的一些总体思路,spring security主要分认证(authentication)和授权(authority)。
1.认证authentication
认证主要代码在spring-security-core下的包org.springframework.security.authentication下,主类:AuthenticationManager、AuthenticationProvider
其关系如下:
2.授权Authorization
授权也称Access Control,主要代码在spring-security-core下的包org.springframework.security.access下,主类:AccessDecisionManager、SecurityMetadataSource。它们的关系通过ConfigAttribute关联起来。
SecurityMetadataSource获取ConfigAttribute,方法:
Collection<ConfigAttribute> getAttributes(Object object)throws IllegalArgumentException;
AccessDecisionManager根据进行授权,方法:
void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,InsufficientAuthenticationException;
其实现类:AffirmativeBased的授权逻辑如下:
/**
* This concrete implementation simply polls all configured
* {@link AccessDecisionVoter}s and grants access if any
* <code>AccessDecisionVoter</code> voted affirmatively. Denies access only if there
* was a deny vote AND no affirmative votes.
* <p>
* If every <code>AccessDecisionVoter</code> abstained from voting, the decision will
* be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to
* false).
* </p>
*
* @param authentication the caller invoking the method
* @param object the secured object
* @param configAttributes the configuration attributes associated with the method
* being invoked
*
* @throws AccessDeniedException if access is denied
*/
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0; for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes); if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
} switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return; case AccessDecisionVoter.ACCESS_DENIED:
deny++; break; default:
break;
}
} if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
} // To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
从上文可以看出,真正的授权是通过AccessDecisionVoter来完成的。
3.认证和授权的集成AbstractSecurityInterceptor
AbstractSecurityInterceptor包含了四个instance及其get/set方法
private AccessDecisionManager accessDecisionManager;
private AfterInvocationManager afterInvocationManager;
private AuthenticationManager authenticationManager = new NoOpAuthenticationManager();
private RunAsManager runAsManager = new NullRunAsManager();
加一个抽象的方法:
/**
* Indicates the type of secure objects the subclass will be presenting to the
* abstract parent for processing. This is used to ensure collaborators wired to the
* {@code AbstractSecurityInterceptor} all support the indicated secure object class.
*
* @return the type of secure object the subclass provides services for
*/
public abstract Class<?> getSecureObjectClass();
AbstractSecurityInterceptor的实现类有两个:
3.1 FilterSecurityInterceptor
定义:
/**
* Performs security handling of HTTP resources via a filter implementation.
* <p>
* The <code>SecurityMetadataSource</code> required by this security interceptor is of
* type {@link FilterInvocationSecurityMetadataSource}.
* <p>
* Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
* </p>
*
* @author Ben Alex
* @author Rob Winch
*/
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {}
实现了标准的servlet的Filter接口,其逻辑如下:
/**
* Method that is actually called by the filter chain. Simply delegates to the
* {@link #invoke(FilterInvocation)} method.
*
* @param request the servlet request
* @param response the servlet response
* @param chain the filter chain
*
* @throws IOException if the filter chain fails
* @throws ServletException if the filter chain fails
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
最重要的实现invoke
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
} InterceptorStatusToken token = super.beforeInvocation(fi); try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
} super.afterInvocation(token, null);
}
}
3.2 MethodSecurityInterceptor
定义:
/**
* Provides security interception of AOP Alliance based method invocations.
* <p>
* The <code>SecurityMetadataSource</code> required by this security interceptor is of
* type {@link MethodSecurityMetadataSource}. This is shared with the AspectJ based
* security interceptor (<code>AspectJSecurityInterceptor</code>), since both work with
* Java <code>Method</code>s.
* <p>
* Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
*
* @author Ben Alex
* @author Rob Winch
*/
public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements
MethodInterceptor {}
其invoke方法如下:
/**
* This method should be used to enforce security on a <code>MethodInvocation</code>.
*
* @param mi The method being invoked which requires a security decision
*
* @return The returned value from the method invocation (possibly modified by the
* {@code AfterInvocationManager}).
*
* @throws Throwable if any error occurs
*/
public Object invoke(MethodInvocation mi) throws Throwable {
InterceptorStatusToken token = super.beforeInvocation(mi); Object result;
try {
result = mi.proceed();
}
finally {
super.finallyInvocation(token);
}
return super.afterInvocation(token, result);
}
4.Spring Security Java Config ---@EnableWebSecurity
将@EnableWebSecurity注解加到@Configuration下来获得spring securiy的安全性。
WebSecurityConfigurer定义的配置或者对WebSecurityConfigurerAdapter类的扩展类示例如下:
@Configuration
@EnableWebSecurity
public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
// Spring Security should completely ignore URLs starting with /resources/
.antMatchers("/resources/");
} @Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/public/").permitAll().anyRequest()
.hasRole("USER").and()
// Possibly more configuration ...
.formLogin() // enable form based log in
// set permitAll for all URLs associated with Form Login
.permitAll();
} @Override
protected void configure(AuthenticationManagerBuilder auth) {
auth
// enable in memory based authentication with a user named "user" and "admin"
.inMemoryAuthentication().withUser("user").password("password").roles("USER")
.and().withUser("admin").password("password").roles("USER", "ADMIN");
} // Possibly more overridden methods ...
}
4.1 WebSecurityConfigurer
WebSecurityConfigurer允许对WebSecurity进行定制化,在绝大部分情景下,开发者使用@EnableWebSecurity注解或者对WebSecurityConfigurerAdapter进行重写的方式来自动应用@EnableWebSecurity注解。定义如下:
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
SecurityConfigurer<Filter, T> { }
4.2 WebSecurityConfigurerAdapter
WebSecurityConfigurerAdapter提供了创建WebSecurityConfigurer实例的便利方法,它是一个基类。该类的实现通过重写方法来实现定制化。
它自动从SpringFactoriesLoader查找AbstractHttpConfigurer,从而让开发者可以扩展。为达到这个目的,必须创建一个AbstractHttpConfigurer的扩展类,然后在classpath路径下创建一个文件META-INF/spring.factories,示例如下:
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer
如果你有多个扩展类,可以使用逗号分隔,示例如下:
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer, sample.OtherThatExtendsAbstractHttpConfigurer
4.2.1 初始化
初始化分两个过程:获取HttpSecurity,配置FilterSecurityInterceptor到WebSecurity
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}
获取HttpSecurity的过程:
/**
* Creates the {@link HttpSecurity} or returns the current instance
*
* ] * @return the {@link HttpSecurity}
* @throws Exception
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
} DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
.postProcess(new DefaultAuthenticationEventPublisher());
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher); AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects(); http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
if (!disableDefaults) {
// @formatter:off
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<HttpSecurity>()).and()
.logout();
// @formatter:on
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader); for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
configure(http);
return http;
}
5. xml配置解析类SecurityNamespaceHandler
它的解析器有以下几种:
private void loadParsers() {
// Parsers
parsers.put(Elements.LDAP_PROVIDER, new LdapProviderBeanDefinitionParser());
parsers.put(Elements.LDAP_SERVER, new LdapServerBeanDefinitionParser());
parsers.put(Elements.LDAP_USER_SERVICE, new LdapUserServiceBeanDefinitionParser());
parsers.put(Elements.USER_SERVICE, new UserServiceBeanDefinitionParser());
parsers.put(Elements.JDBC_USER_SERVICE, new JdbcUserServiceBeanDefinitionParser());
parsers.put(Elements.AUTHENTICATION_PROVIDER,
new AuthenticationProviderBeanDefinitionParser());
parsers.put(Elements.GLOBAL_METHOD_SECURITY,
new GlobalMethodSecurityBeanDefinitionParser());
parsers.put(Elements.AUTHENTICATION_MANAGER,
new AuthenticationManagerBeanDefinitionParser());
parsers.put(Elements.METHOD_SECURITY_METADATA_SOURCE,
new MethodSecurityMetadataSourceBeanDefinitionParser()); // Only load the web-namespace parsers if the web classes are available
if (ClassUtils.isPresent(FILTER_CHAIN_PROXY_CLASSNAME, getClass()
.getClassLoader())) {
parsers.put(Elements.DEBUG, new DebugBeanDefinitionParser());
parsers.put(Elements.HTTP, new HttpSecurityBeanDefinitionParser());
parsers.put(Elements.HTTP_FIREWALL, new HttpFirewallBeanDefinitionParser());
parsers.put(Elements.FILTER_SECURITY_METADATA_SOURCE,
new FilterInvocationSecurityMetadataSourceParser());
parsers.put(Elements.FILTER_CHAIN, new FilterChainBeanDefinitionParser());
filterChainMapBDD = new FilterChainMapBeanDefinitionDecorator();
} if (ClassUtils.isPresent(MESSAGE_CLASSNAME, getClass().getClassLoader())) {
parsers.put(Elements.WEBSOCKET_MESSAGE_BROKER,
new WebSocketMessageBrokerSecurityBeanDefinitionParser());
}
}
6.小结
spring支持注解和xml配置两种方式,因此分析源码可以从xml配置及注解两方面入手,相互印证。
参考文献:
【1】https://spring.io/guides/topicals/spring-security-architecture/
【2】http://zzy.cincout.cn/2016/12/23/spring-security-2016-12-23-spring-security-01/
spring security源码分析心得的更多相关文章
- Spring Security 源码分析(四):Spring Social实现微信社交登录
社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯微博.淘宝.豆瓣.MSN.Google等社会化媒体账号登录该网站. 前言 ...
- Spring Security 源码分析 --- WebSecurity
概述 spring security 源码分析系列文章. 源码分析 我们想一下,我们使用 ss 框架的步骤是怎么样的. @Configuration @EnableWebSecurity @Enabl ...
- spring security源码分析之web包分析
Spring 是一个非常流行和成功的 Java 应用开发框架.Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案.一般来说,Web 应用的安全性包括 ...
- spring security源码分析之core包
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring I ...
- spring security源码分析之一springSecurityFilterChain
1. spring和spring security的集成,配置web.xml如下: <context-param> <param-name>contextConfigLocat ...
- [心得体会]spring事务源码分析
spring事务源码分析 1. 事务的初始化注册(从 @EnableTransactionManagement 开始) @Import(TransactionManagementConfigurati ...
- 精尽Spring MVC源码分析 - 寻找遗失的 web.xml
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring Boot源码分析 - Jar 包的启动实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- Spring mvc源码分析系列--Servlet的前世今生
Spring mvc源码分析系列--Servlet的前世今生 概述 上一篇文章Spring mvc源码分析系列--前言挖了坑,但是由于最近需求繁忙,一直没有时间填坑.今天暂且来填一个小坑,这篇文章我们 ...
随机推荐
- ztree实现根节点右击事件,显示添加删除
需求,右击树节点,出现编辑和删除的提示框 1:在setting 配置里面,给callback设置,右击事件onRightClick: 2:写一个函数onRightClick function onRi ...
- Failed to initialize connector [Connector[HTTP/1.1-443]]
Failed to initialize connector [Connector[HTTP/1.1-443]] 出现如上错误时,是因为443端口被占用, 所以tomcat的https协议无法使用, ...
- [Typescript] Build Method decorators in Typescript
To using decorate, we can modifiy tsconfig.json: { "compilerOptions": { ... "experime ...
- Struts2学习(三)上传下载
今天记录一下利用struts2实现上传下载,借此案例说明一下struts2的开发流程. 须要注意的是struts2版本号不同非常多地方的写法是不同的.本例使用struts2.3.15 .有差别的地方文 ...
- 迷宫求解_数据结构c语言版
#include <iostream> #include <string> #include <cstdio> #include <cstdlib> # ...
- ES6中object对象属性
//////es5中定义对象属性要么字面量.要么点.要么[],变量与空格在这些方法中没有得到好的支持 /////在es6中可以这么定义: let w='www'; let obj1={w};//obj ...
- Ubuntu18.04 更改GRUB引导菜单背景图片和默认启动项
一.更改GRUB引导菜单背景图片1.首先准备一张想要的照片,文件名是啥无所谓,只要格式是*.jpg *.JPG *.jpeg *.JPEG *.png *.PNG *.tga *.TGA都行,都能自动 ...
- CentOS6.9下ssh密钥登录配置步骤(免密码登录)和ssh-keygen 命令常用参数
密钥登录步骤(免密码登录)ssh登录提供两种认证方式:口令(密码)认证方式和密钥认证方式.其中口令(密码)认证方式是我们最常用的一种,出于安全方面的考虑,介绍密钥认证方式登录到linux/unix的方 ...
- 学习Go语言之模板方法模式
模板方法模式结构图如下.原理是过程实现不一样,但是执行的顺序是按照模板固定好了的.即简单理解为都有1,2,3步骤,但是每一步的实现交由具体实现类不同实现. 1.过程式编程 // 模板方法模式 pack ...
- vue中使用console.log无效
webpack开发环境下,在vue中使用console.log无效,一直以为webpack出了问题. 使用window.console.log()就能够顺利在浏览器控制台输出了. 以及 在axios请 ...