首先 请求进入 FilterChainProxy 这个类
FilterChainProxy.java
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
List<Filter> filters = getFilters(fi.getRequestUrl());
if (filters == null || filters.size() == 0) {
if (logger.isDebugEnabled()) {
logger.debug(fi.getRequestUrl() + filters == null ? " has no matching filters"
: " has an empty filter list");
}
chain.doFilter(request, response);
return;
}
VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi,
filters);
virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());
}
public List<Filter> getFilters(String url) {
if (stripQueryStringFromUrls) { // String query string - see SEC-953
int firstQuestionMarkIndex = url.indexOf("?");
if (firstQuestionMarkIndex != -1) {
url = url.substring(0, firstQuestionMarkIndex);
}
}
for (Map.Entry<Object, List<Filter>> entry : filterChainMap.entrySet()) {
Object path = entry.getKey();
if (matcher.requiresLowerCaseUrl()) {
url = url.toLowerCase();
if (logger.isDebugEnabled()) {
logger.debug("Converted URL to lowercase, from: '" + url
+ "'; to: '" + url + "'");
}
}
boolean matched = matcher.pathMatchesUrl(path, url);
if (logger.isDebugEnabled()) {
logger.debug("Candidate is: '" + url + "'; pattern is " + path
+ "; matched=" + matched);
}
if (matched) {
return entry.getValue();
}
}
return null;
}
可以看出, FilterInvocation 是见 request 和 response ,chain 只是进行了封装, 然后根据 url 来判断这个请求是否需要进行拦截, 这里 getFilter() 方法是查询的 intercepter-url 中配置的 内容。(这里具体的内容在下面)
接下来就是执行所有的List
VirtualFilterChain.java 这是 FilterChainProxy 的内部类
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (currentPosition == additionalFilters.size()) {
if (logger.isDebugEnabled()) {
logger.debug(fi.getRequestUrl()
+ " reached end of additional filter chain; proceeding with original chain");
}
fi.getChain().doFilter(request, response);
} else {
currentPosition++;
Filter nextFilter = additionalFilters.get(currentPosition - 1);
if (logger.isDebugEnabled()) {
logger.debug(fi.getRequestUrl() + " at position "
+ currentPosition + " of " + additionalFilters.size()
+ " in additional filter chain; firing Filter: '"
+ nextFilter + "'");
}
nextFilter.doFilter(request, response, this);
}
}
下面先按顺序分析各Filter的作用 (security默认添加的filterChain,共11个 还有大概4,5个没有涉及到,以后涉及到再进行添加)
1.org.springframework.security.web.context.SecurityContextPersistenceFilter
(2.0中是这个HttpSessionContextIntegrationFilter)
从这个类所在的包路径 context,大致知道这个类 只处理 上下文
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) { // ensure that filter is only applied once per request
chain.doFilter(request, response);
return;
}
final boolean debug = logger.isDebugEnabled();
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
logger.debug("Eagerly created session: " + session.getId());
}
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(
request, response);
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
} finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder
.getContext(); // Crucial removal of SecurityContextHolder contents - do this before anything else.
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
从代码看: 在一次request中只执行一次,并生成 SecurityContext(从session中读取,如果session中没有就创建一个新的),注册到 SecurityContextHolder中,当请求执行完后,清除该SecurityContext 和request中的 filter_applied 属性。在源码中类注释提到: 这个类 一次请求中只能执行一次,并且它应该在 任何认证过程之前 执行。
============================华丽丽的分割线===========================
2,org.springframework.security.web.authentication.logout.LogoutFilter
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (requiresLogout(request, response)) {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
if (logger.isDebugEnabled()) {
logger.debug("Logging out user '" + auth
+ "' and transferring to logout destination");
}
for (LogoutHandler handler : handlers) {
handler.logout(request, response, auth);
}
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
/**
*
* Allow subclasses to modify when a logout should take place. * * @param
* request the request * @param response the response * * @return true if
* logout should occur, false otherwise
*/
protected boolean requiresLogout(HttpServletRequest request,
HttpServletResponse response) {
String uri = request.getRequestURI();
int pathParamIndex = uri.indexOf(';');
if (pathParamIndex > 0) { // strip everything from the first semi-colon
uri = uri.substring(0, pathParamIndex);
}
int queryParamIndex = uri.indexOf('?');
if (queryParamIndex > 0) { // strip everything from the first question
// mark
uri = uri.substring(0, queryParamIndex);
}
if ("".equals(request.getContextPath())) {
return uri.endsWith(filterProcessesUrl);
}
return uri.endsWith(request.getContextPath() + filterProcessesUrl);
}
这个处理比较简单, 只是检查是否为 登出地址,是的话就退出然后返回,不是的话就进行下一个filter。
这个判断是否为登录地址我感觉很不正常,他判断是否以 logout_url 结尾,直接判断是否相等才对啊。
而且他根据 contextpath 是否为“” ,其实不用的,直接判断 contextPath+logout_url 即可。
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter .java
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
authResult = attemptAuthentication(request, response);
if (authResult == null) {// return immediately as subclass has
// indicated that it hasn't completed
// authentication
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
} catch (AuthenticationException failed) { // Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
} // Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, authResult);
}
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: "
+ request.getMethod());
}
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password); // Place the last username attempted into
// HttpSession for views
HttpSession session = request.getSession(false);
if (session != null || getAllowSessionCreation()) {
request.getSession().setAttribute(
SPRING_SECURITY_LAST_USERNAME_KEY,
TextEscapeUtils.escapeEntities(username));
} // Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
public Authentication doAuthentication(Authentication authentication)
throws AuthenticationException {
Class toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
} catch (AccountStatusException e) { // SEC-546: Avoid polling
// additional providers if
// auth failure is due to
// invalid account status
eventPublisher.publishAuthenticationFailure(e, authentication);
throw e;
} catch (AuthenticationException e) {
lastException = e;
}
}
if (result == null && parent != null) { // Allow the parent to try.
try {
result = parent.authenticate(authentication);
} catch (ProviderNotFoundException e) { // ignore as we will throw
// below if no other
// exception occurred prior
// to calling parent and the
// parent
// may throw ProviderNotFound even though a provider in the
// child already handled the request
} catch (AuthenticationException e) {
lastException = e;
}
}
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) { // Authentication
// is
// complete.
// Remove
// credentials
// and other
// secret
// data from
// authentication
((CredentialsContainer) result).eraseCredentials();
}
eventPublisher.publishAuthenticationSuccess(result);
return result;
} // Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
eventPublisher.publishAuthenticationFailure(lastException,
authentication);
throw lastException;
}
过程: 判断是否 为登录地址,是则进行认证,否则 继续下一个filter
认证过程: 取得 username,password, 调用 AuthenticationManager.authenticate(){
然后调用 所有的AuthenticationProvider 进行认证,有一个认证通过即可通过。在AuthenticationProvider中调用 配置的 UserDetailsService 的 loadUserByUserame() 得到 UserDetails, 当第一次从数据库取得后,会将UserDetails保存到 Cache中,这给权限分配的 及时性带来了困难,不过它专门提供了一个filter来进行 热部署权限
}
还有一点,这个filter中判断 "j_spring_security_check"这个地址也是以 endWith来匹配的,感觉不对。
回家了,晚上继续
===========================华丽丽的分割线=================================
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
org.springframework.security.web.authentication.www.BasicAuthenticationFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.session.SessionManagementFilter
org.springframework.security.web.access.ExceptionTranslationFilter
org.springframework.security.web.access.intercept.FilterSecurityInterceptor