spring-security 3(二)ソース分析

24464 ワード

断点を利用してspring-securityのソースコードの核心部分を歩きました.以下は自分の理解によってソースコードに対して説明しました.フィルタの先頭の記号は運行時のデフォルトの設定呼び出しの順序です.原理を理解しました.フィルタ、権限検証器、データの照会器、投票器などを継承して実現することができます.
1.SecurityConteext Persistence FilterはHttpSessionからSecurityConteetコンテキストを取得する.
2.logout Filterアクセスアドレスが/j_spring_securityロゴト、Logout Filterはユーザーをログアウトします.
3.AbstractAuthentication ProcessigFilter権限マネージャがアクセスアドレスがあれば/j_spring_securityチェックすると、対応するデータクエリを選択して、記憶されているユーザ関連情報を取得します.
4.BaicAuthentication Filter
5.Request CacheAwareFilter
6.SecurityContect HolderAwarequest Filter
7.Remember MeAuthentication Filter現在のSecurityConttext Holderにユーザオブジェクトがない場合、cookieで検索する
8.AnonymousAuthentication Filterが現在SecurityConttext Holderにユーザーオブジェクトがない場合、匿名のオブジェクトを作成する
9.Session Management Filterはsessionがタイムアウトしたかどうかを確認します.
10.Exception Translation FilterはFilter Security Interceptorを呼び出し、AbstractSecurity Interceptorは投票器を使って権限判断を行う.
11.Switch UserFilterユーザーが高権限ユーザを低権限ユーザに切り替える
// 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();
}

// .....................
}