利用断点走了一遍spring-security源码的核心部分,下面根据自己的理解对源码做了一些解释,过滤器开头的标号是运行时默认配置调用的顺序,理解了原理,我们可以通过继承和实现接口的方式扩展过滤器,权限验证器,数据查询器,投票器等等……
1.SecurityContextPersistenceFilter 从HttpSession中获取SecurityContext上下文
2.logoutFilter 如果访问地址为/j_spring_security_logout,LogoutFilter将注销用户
3.AbstractAuthenticationProcessingFilter 权限管理器如果访问地址为/j_spring_security_check则选择对应的数据查询器来获取存储的用户相关信息
4.BasicAuthenticationFilter
5.RequestCacheAwareFilter
6.SecurityContextHolderAwareRequestFilter
7.RememberMeAuthenticationFilter 如果当前SecurityContextHolder中没有用户对象,则通过cookie查找
8.AnonymousAuthenticationFilter 如果当前SecurityContextHolder中没有用户对象,则创建匿名对象
9.SessionManagementFilter 检查session是否超时
10.ExceptionTranslationFilter 调用FilterSecurityInterceptor,AbstractSecurityInterceptor使用投票器进行权限判断
11.SwitchUserFilter 用户切换高权限用户向低权限用户切换
//从HttpSession中获取SecurityContext上下文
public class SecurityContextPersistenceFilter extends GenericFilterBean {
private SecurityContextRepository repo = new HttpSessionSecurityContextRepository();
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
//代码略.....................
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);//获得security上下文
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);
}
}
//代码略.....................
}
//如果访问地址为/j_spring_security_logout,LogoutFilter将注销用户
public class LogoutFilter extends GenericFilterBean {
private String filterProcessesUrl = "/j_spring_security_logout";
//代码略.....................
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
//判断如果访问地址为/j_spring_security_logout则执行注销,否则跳过
if (requiresLogout(request, response)) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
for (LogoutHandler handler : handlers) {
handler.logout(request, response, auth);
}
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
//代码略.....................
}
//权限管理器如果访问地址为/j_spring_security_check则选择对应的数据查询器来获取存储的用户相关信息
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements
ApplicationEventPublisherAware, MessageSourceAware {
//代码略.....................
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
//判断如果访问地址为/j_spring_security_check则跳过进行权限获取和判断,否则执行AnonymousAuthenticationFilter
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response); //调用下一个过滤器BasicAuthenticationFilter
return;
}
Authentication authResult;
try {
//执行UsernamePasswordAuthenticationFilter类中的方法
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);
}
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult); //是否存入cookie
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);//跳转到目标页面
}
//代码略.....................
}
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
//代码略.....................
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);
username = username.trim();
//封装成token对象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
//代码略.....................
//调用AbstractAuthenticationManager类中的方法
return this.getAuthenticationManager().authenticate(authRequest);
}
//代码略.....................
}
public abstract class AbstractAuthenticationManager implements AuthenticationManager {
//代码略.....................
public final Authentication authenticate(Authentication authRequest) throws AuthenticationException {
try {
return doAuthentication(authRequest);//调用ProviderManager类中的方法
} catch (AuthenticationException e) {
e.setAuthentication(authRequest);
if (clearExtraInformation) {
e.clearExtraInformation();
}
throw e;
}
}
//代码略.....................
}
//权限认证管理器
public class ProviderManager extends AbstractAuthenticationManager implements MessageSourceAware, InitializingBean {
private List providers = Collections.emptyList();
private AuthenticationManager parent;
//代码略.....................
public Authentication doAuthentication(Authentication authentication) throws AuthenticationException {
Class extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
try {
//调用AbstractUserDetailsAuthenticationProvider类中的方法
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;
}
eventPublisher.publishAuthenticationFailure(lastException, authentication);
throw lastException;
}
//代码略.....................
}
//权限查询器
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean,
MessageSourceAware {
//代码略.....................
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username); //从Ehcache实现的缓存里取userDetail对象
if (user == null) {
cacheWasUsed = false;
try {
//调用DaoAuthenticationProvider类中的方法
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
} catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
throw notFound;
}
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
preAuthenticationChecks.check(user); //检查用户是否有效
//通过页面传入的用户名、密码和数据库中取出的信息对比验证
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
} catch (AuthenticationException exception) {
if (cacheWasUsed) {
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
} else {
throw exception;
}
}
postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user); //将用户对象放入缓存
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
//代码略.....................
}
//数据库查询器
class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider{
private UserDetailsService userDetailsService;
//代码略.....................
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser;
try {
//获取用户信息,可以通过实现UserDetailsService接口或继承JdbcDaoImpl类来自定义内部实现
loadedUser = this.getUserDetailsService().loadUserByUsername(username);//调用自定义类UserDetailsServiceImpl的方法
}
catch (DataAccessException repositoryProblem) {
throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
}
if (loadedUser == null) {
throw new AuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
Object salt = null;
if (this.saltSource != null) {
salt = this.saltSource.getSalt(userDetails);
}
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"),
includeDetailsObject ? userDetails : null);
}
String presentedPassword = authentication.getCredentials().toString();
//判断密码是否一致
if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"),
includeDetailsObject ? userDetails : null);
}
}
//代码略.....................
}
//自定义类实现查询接口
public class UserDetailsServiceImpl extends JdbcDaoSupport implements UserDetailsService {
private String authoritiesByUsernameQuery;
private String usersByUsernameQuery;
//代码略.....................
public void setAuthoritiesByUsernameQuery(String queryString) {
authoritiesByUsernameQuery = queryString;
}
public void setUsersByUsernameQuery(String usersByUsernameQueryString) {
this.usersByUsernameQuery = usersByUsernameQueryString;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
List users = loadUsersByUsername(username); //访问数据库查询用户信息
if (users.size() == 0) {
logger.debug("Query returned no results for user '" + username + "'");
throw new UsernameNotFoundException(
messages.getMessage("JdbcDaoImpl.notFound", new Object[]{username}, "Username {0} not found"), username);
}
UserDetails user = users.get(0); // contains no GrantedAuthority[]
Set dbAuthsSet = new HashSet();
if (enableAuthorities) {
dbAuthsSet.addAll(loadUserAuthorities(user.getUsername())); //查询用户拥有的角色
}
if (enableGroups) {
dbAuthsSet.addAll(loadGroupAuthorities(user.getUsername()));
}
List dbAuths = new ArrayList(dbAuthsSet);
addCustomAuthorities(user.getUsername(), dbAuths);
if (dbAuths.size() == 0) {
logger.debug("User '" + username + "' has no authorities and will be treated as 'not found'");
throw new UsernameNotFoundException(
messages.getMessage("JdbcDaoImpl.noAuthority",
new Object[] {username}, "User {0} has no GrantedAuthority"), username);
}
return createUserDetails(username, user, dbAuths);//返回实现UserDetails接口的对象,将验证信息封装到此对象中
}
protected List loadUsersByUsername(String username) {
return getJdbcTemplate().query(usersByUsernameQuery, new String[]{
username}, new RowMapper() {
@Override
public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
String username = rs.getString(1);
String password = rs.getString(2);
boolean enabled = rs.getBoolean(3);
return new userDetailsImpl(username, password, enabled);//返回实现UserDetails接口的对象
}
});
}
//代码略.....................
}
//用户信息bean,必须实现UserDetails接口
public class userDetailsImpl implements UserDetails {
//代码略.....................
}
//由ExceptionTranslationFilter过滤器调用来进行权限判断
public abstract class AbstractSecurityInterceptor implements InitializingBean, ApplicationEventPublisherAware,
MessageSourceAware {
//代码略.....................
protected InterceptorStatusToken beforeInvocation(Object object) {
//这里读取配置FilterSecurityInterceptor的SecurityMetadataSource属性来获取配置的角色,这些属性配置了资源的安全设置
Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object);
if (attributes == null) {
if (rejectPublicInvocations) {
throw new IllegalArgumentException("Secure object invocation " + object +
" was denied as public invocations are not allowed via this interceptor. "
+ "This indicates a configuration error because the "
+ "rejectPublicInvocations property is set to 'true'");
}
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
//这里从SecurityContextHolder中去取Authentication对象,一般在登录时会放到SecurityContextHolder中去
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"), object, attributes);
}
// 如果前面没有处理鉴权,这里需要对鉴权进行处理
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
//通过投票器判断当前角色是否有权限访问该地址,如果没有权限则抛出异常,调用AffirmativeBased类中的decide的方法
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
//授权不成功向外发布事件
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
// 这里构建一个RunAsManager来替代当前的Authentication对象,默认情况下使用的是NullRunAsManager会把SecurityContextHolder中的Authentication对象清空
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
if (runAs == null) {
// no further work post-invocation
return new InterceptorStatusToken(authenticated, false, attributes, object);
} else {
SecurityContextHolder.getContext().setAuthentication(runAs);
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(authenticated, true, attributes, object);
}
}
//代码略.....................
}
//决策器
public class AffirmativeBased extends AbstractAccessDecisionManager {
//代码略.....................
public void decide(Authentication authentication, Object object, Collection 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: //result:1
return;
case AccessDecisionVoter.ACCESS_DENIED: //result:-1
//这里对反对票进行计数
deny++;
break;
default:
break;
}
}
//如果有反对票,抛出异常,整个授权不通过
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied",
"Access is denied"));
}
//这里对弃权票进行处理,看看是全是弃权票的决定情况,默认是不通过,由allowIfAllAbstainDecisions变量控制
checkAllowIfAllAbstainDecisions();
}
//代码略.....................
}
//角色投票器
public class RoleVoter implements AccessDecisionVoter {
//代码略.....................
public int vote(Authentication authentication, Object object, Collection attributes) {
int result = ACCESS_ABSTAIN;
Collection authorities = extractAuthorities(authentication);
for (ConfigAttribute attribute : attributes) {//这里取得资源的安全配置
if (this.supports(attribute)) {
result = ACCESS_DENIED;
//这里对资源配置的安全授权级别进行判断,也就是匹配ROLE为前缀的角色配置
//遍历每个配置属性,如果其中一个匹配该主体持有的GrantedAuthority,则访问被允许。
//当前用户拥有的角色集合,当有任何一个角色满足时授予权限
for (GrantedAuthority authority : authorities) {
if (attribute.getAttribute().equals(authority.getAuthority())) {
return ACCESS_GRANTED;
}
}
}
}
return result;
}
Collection extractAuthorities(Authentication authentication) {
return authentication.getAuthorities();
}
//代码略.....................
}