Spring Security源码解析(二)

上一章介绍了Spring Security的相关知识点,这章将详细分析源码。

DelegatingFilterProxy类的一些内部运行机制,其实主要作用就是一个代理模式的应用,可以把servlet 容器中的filter同spring容器中的bean关联起来”我觉得楼主说得这句是最重要的!

spring security为什么要用filter实现,而非aop、interceptor等等,?这些组件都是在dispatcherServlet之后执行的,此时再做一些安全校验是不是太晚(自己瞎猜的,主要是助于自己理解,在此做个笔记,有错希望指出!!!)

而确定了filter实现spring security,那么为什么又整出来一个DelegatingFilterProxy代理类啊? 如果没有这个代理类,那么你就需要把spring security框架中的filter都配置到web.xml中,这样的用户体验过太差了,而且耦合得太紧密了;同时你通过web.xml配置这些filter,而没有通过spring ioc容器进行管理,有点不符合整体思想!而filter是属于java web的东西,必须配置在web.xml中,所以就有了目前的机制,通过配置一个DelegatingFilterProxy类到web.xml中,其他的spring security中的filter配置到ioc容器中管理,通过DelegatingFilterProxy代理类把javaweb中的filter和spring ioc容器中的filter关联起来了!(是自己目前的一种理解,如果有错,希望大家指出来,谢谢)

一、FilterChain执行顺序

首先看下filterChain执行顺序。

                                   

重要的几个filter是

  • SecurityContextPersistenceFilter
  • LogoutFilter
  • UsernamePasswordAuthenticationFilter
  • AnonymousAuthenticationFilter
  • FilterSecurityInterceptor

通过用户名、密码授权,大致过程可归纳为

  • SecurityContextPersistenceFilter从session中获取SecurityContext,存入SecurityContextHolder中。完成一次请求后,会自动SecurityContextHolder.clearContext(),并将新的SecurityContext存入session中
  • AbstractAuthenticationProcessingFilter(UsernamePasswordAuthenticationFilter)过滤器拦截请求,判断request是否需要验证身份信息

        如果request无需验证身份信息,则直接跳转到下一个filter——RequestCacheAwareFilter,AnonymousAuthenticationFilter会判断SecurityContextHolder是否有Authentication,没有就加一个AnonymousAuthentication进去,依次执行到最后一个filter——FilterSecurityInterceptor,获取SecurityContextHolder中的Authentication,判断该Authentication能否进入请求的url地址。如果没有权限,则由ExceptionTranslationFilter跳转到/login地址

        如果request需要验证身份信息且post请求/login,则执行下面的步骤:

  • 将request中的username和password存入UsernamePasswordAuthentication中,用ProviderManager(包含AuthenticationProvider的集合,依次执行多个AuthenticationProvider中的验证方法)的Authenticate方法进行补全优化
  • DaoAuthenticationProvider类中的retriveUser(username,authentication)会调用UserDetailsService中的loadUserByUsername()方法,查询获得数据库中的用户基本信息并存入UserDetail的实现类中。
  • 获得的resultAuthentication会存入UserCache,且erase掉credentials。
  • authentication存入SecurityContextHolder,跳转到directUrl页面,相当于一个新的请求,重新从WebAsyncManagerIntegrationFilter进行。

1.AbstractAuthenticationProcessingFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
              // 判断request是否为post,是否需要验证身份信息;需要则进入attemptAuthentication中,不需要则直接进入filterChain。
		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);
		}
        
        ...

         // 成功验证用户信息,存入SecurityHolder中,且跳转到targetUrl页面!!!
        successfulAuthentication(request, response, chain, authResult);
}

        // 继承类会实现该方法,实际通过request获得Authentication
        // 继承类有UsernamePasswordAuthenticationFilter,OAuth2ClientAuthenticationProcessingFilter等
public abstract Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException, IOException,
			ServletException;


// 验证成功后的操作,写入SecurityContextHolder,跳转到directUrl页面
protected void successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, Authentication authResult)
			throws IOException, ServletException {

		if (logger.isDebugEnabled()) {
			logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
					+ authResult);
		}

		SecurityContextHolder.getContext().setAuthentication(authResult);

		rememberMeServices.loginSuccess(request, response, authResult);

		// Fire event
		if (this.eventPublisher != null) {
			eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
					authResult, this.getClass()));
		}

		successHandler.onAuthenticationSuccess(request, response, authResult);
	}

2.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());
		}
           // request.getParameter("username")
		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);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);

		return this.getAuthenticationManager().authenticate(authRequest);
	}

3.ProviderManager  implements AuthenticationManager ...

private List<AuthenticationProvider> providers = Collections.emptyList();

// 对象适配器,将AuthenticationProvider传入AuthenticationManager的实现类中

public ProviderManager(List<AuthenticationProvider> providers) {
	this(providers, null);
}

// AuthenticationManager中的接口方法authenticate调用了AuthenticationProvider中的authenticate方法
// AbstractUserDetailsAuthenticationProvider   DaoAuthenticationProvider
public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		Authentication result = null;
		boolean debug = logger.isDebugEnabled();
                // 遍历循环Providers,进行authenticate
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}

			try {
				result = provider.authenticate(authentication);

				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			...
		}

		...

		prepareException(lastException, authentication);

		throw lastException;
	}

4.AbstractUserDetailsAuthenticationProvider implements   AuthenticationProvider ...

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;
            // 第一次调用是没有userCache的,所以user是null
		UserDetails user = this.userCache.getUserFromCache(username);

		if (user == null) {
			cacheWasUsed = false;

			try {
                        // 这里其实调用的是DaoAuthenticationProvider中的实现方法retrieveUser()
                        // 包含userDetailsService中的loadUserByUsername(String name)
				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 {
                // 判断user是不是isEnabled(),isAccountNonLocked(),isAccountNonExpired()等
			preAuthenticationChecks.check(user);
                // 验证authentication(request获取)的密码是否和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;
			}
		}
            // user密码是否过期
		postAuthenticationChecks.check(user);

		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}

		return createSuccessAuthentication(principalToReturn, authentication, user);
	}


        protected Authentication createSuccessAuthentication(Object principal,
			Authentication authentication, UserDetails user) {
            // 重新生成了新的UsernamePasswordAuthenticationToken,且isAuthorized=true
		UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
				principal, authentication.getCredentials(),
				authoritiesMapper.mapAuthorities(user.getAuthorities()));
		result.setDetails(authentication.getDetails());

		return result;
	}

最终返回的result如下,remove了credentials,存入SecurityContextHolder.getContext().setAuthentication(authResult);

                                        

四.OAUTH2工作流程

核心原理还是用authenticationManager的实现类去authenticate(),验证成功后得到token。

先了解些基本概念。

TokenGranter和它的父级,TokenGranter中就一个方法,根据grantType、tokenRequest授予token,返回结果为OAuth2AccessToken。它的实现类实现了不同的生成方法。

public interface TokenGranter {

	OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest);

}

                         

步骤如下

1.POSTMAN这里传入的Authorization type 为Basic Auth,则自动用Base64加密后存入名为Authorization的header表头中。

2.BasicAuthenticationFilter位于Spring Security包中,过滤器拦截获取request的表头,解密获得Basic Auth中的Username和Password,接着用authenticationManager.authenticate()获取authentication,并存入SecurityContextHolder中,继续chain.doFilter();

public class BasicAuthenticationFilter extends OncePerRequestFilter {
    @Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain)
					throws IOException, ServletException {
		final boolean debug = this.logger.isDebugEnabled();

		String header = request.getHeader("Authorization");
            // 如果不是Basic OAuth,则直接到下一个过滤器
		if (header == null || !header.startsWith("Basic ")) {
			chain.doFilter(request, response);
			return;
		}

		try {
            // Base64解码获得tokens  ["yuqiyu_home_pc","yuqiyu_secret"]
			String[] tokens = extractAndDecodeHeader(header, request);
			assert tokens.length == 2;

			String username = tokens[0];

			...

			if (authenticationIsRequired(username)) {
                    // 创建UsernamePasswordAuthenticationToken 对象
				UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
						username, tokens[1]);
				authRequest.setDetails(
						this.authenticationDetailsSource.buildDetails(request));
                    // SpringSecurity 验证token 
				Authentication authResult = this.authenticationManager
						.authenticate(authRequest);

				if (debug) {
					this.logger.debug("Authentication success: " + authResult);
				}
                    // 写入SecurityContextHolder中
				SecurityContextHolder.getContext().setAuthentication(authResult);

				this.rememberMeServices.loginSuccess(request, response, authResult);

				onSuccessfulAuthentication(request, response, authResult);
			}

		}
		catch (AuthenticationException failed) {
			SecurityContextHolder.clearContext();

			if (debug) {
				this.logger.debug("Authentication request for failed: " + failed);
			}

			this.rememberMeServices.loginFail(request, response);

			onUnsuccessfulAuthentication(request, response, failed);

			if (this.ignoreFailure) {
				chain.doFilter(request, response);
			}
			else {
				this.authenticationEntryPoint.commence(request, response, failed);
			}

			return;
		}

		chain.doFilter(request, response);
	}


}

值得注意的是this.authenticationManager.authenticate(authRequest) 执行过程中会用到userDetailsService.loadUserByUsername()

不同于用户自定义的class继承userDetailsService,这里的loadUserByUsername调用的类是ClientDetailsUserDetailsService,结构如下,返回一个User类,包含用户在@Configuration中自定义的属性

public class ClientDetailsUserDetailsService implements UserDetailsService {

	private final ClientDetailsService clientDetailsService;
	private String emptyPassword = "";
	
	public ClientDetailsUserDetailsService(ClientDetailsService clientDetailsService) {
		this.clientDetailsService = clientDetailsService;
	}
	
	/**
	 * @param passwordEncoder the password encoder to set
	 */
	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
		this.emptyPassword = passwordEncoder.encode("");
	}

	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		ClientDetails clientDetails;
		try {
			clientDetails = clientDetailsService.loadClientByClientId(username);
		} catch (NoSuchClientException e) {
			throw new UsernameNotFoundException(e.getMessage(), e);
		}
		String clientSecret = clientDetails.getClientSecret();
		if (clientSecret== null || clientSecret.trim().length()==0) {
			clientSecret = emptyPassword;
		}
		return new User(username, clientSecret, clientDetails.getAuthorities());
	}

}

                                             

用户自定义的类如下,其中configure(ClientDetailsServiceConfigurer clients) 方法提供了上面User所需要的信息。

@Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {

        private static final String ENV_OAUTH = "authentication.oauth.";
        private static final String PROP_CLIENTID = "clientid";
        private static final String PROP_SECRET = "secret";
        private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";

        private RelaxedPropertyResolver propertyResolver;

        @Autowired
        private DataSource dataSource;

        @Bean
        public TokenStore tokenStore() {
            return new JdbcTokenStore(dataSource);
        }

        @Autowired
        @Qualifier("authenticationManagerBean")
        private AuthenticationManager authenticationManager;

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints)
                throws Exception {
            endpoints
                    .tokenStore(tokenStore())
                    .authenticationManager(authenticationManager);
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients
                    .inMemory()
                    .withClient(propertyResolver.getProperty(PROP_CLIENTID))
                    .scopes("read", "write")
                    .authorities(Authorities.ROLE_ADMIN.name(), Authorities.ROLE_USER.name())
                    .authorizedGrantTypes("password", "refresh_token")
                    .secret(propertyResolver.getProperty(PROP_SECRET))
                    .accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800));
        }

        @Override
        public void setEnvironment(Environment environment) {
            this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
        }

    }

3.chain.doFilter()后方法执行到了TokenEndPoint中,这个类位于oauth2的包里,获得了上流传来的Basic OAuth---usernamePasswordAthentication,向下转换成了Principal,该参数如下。parameters则是post中填写的参数,包括username,password和grant_type(password),主要用来和数据库中的数据进行用户名密码比对。

                                           

@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {

@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
	public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
	Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {

		if (!(principal instanceof Authentication)) {
			throw new InsufficientAuthenticationException(
					"There is no client authentication. Try adding an appropriate authentication filter.");
		}

		String clientId = getClientId(principal);
		ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

		TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

		if (clientId != null && !clientId.equals("")) {
			// Only validate the client details if a client authenticated during this
			// request.
			if (!clientId.equals(tokenRequest.getClientId())) {
				// double check to make sure that the client ID in the token request is the same as that in the
				// authenticated client
				throw new InvalidClientException("Given client ID does not match authenticated client");
			}
		}
		
                    ...
                    // tokenGranter进行授权,返回OAuth2AccessToken 
		OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
		if (token == null) {
			throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
		}

		return getResponse(token);

	}
}

4.执行tokenGranter.grant(grant_type,tokenRequest)方法,方法如下

public final class AuthorizationServerEndpointsConfigurer {

private TokenGranter tokenGranter() {
		if (tokenGranter == null) {
			tokenGranter = new TokenGranter() {
                  
				private CompositeTokenGranter delegate;

				@Override
				public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {                
// 第一次执行时为null,调用getDefaultTokenGranters()方法,获得5个父类集合,第二次就有了值
					if (delegate == null) {                            
						delegate = new CompositeTokenGranter(getDefaultTokenGranters());
					}
                            // CompositeTokenGranter grant()方法相当于遍历执行tokenGranter.grant()
					return delegate.grant(grantType, tokenRequest);
				}
			};
		}
		return tokenGranter;
	}
}

          其中delegate包含了多种tokenGranter的父类,因为grant_type=password,所以我们执行的类是ResourceOwnerPasswordTokenGranter,然后里面执行了authenticationManager.authenticate()和userDetailsService.loadUserByUsername();

                                            

5.CreateOauthToken,存入数据库

public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
		ConsumerTokenServices, InitializingBean {
@Transactional
	public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

		OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
		OAuth2RefreshToken refreshToken = null;
		if (existingAccessToken != null) {
			if (existingAccessToken.isExpired()) {
				if (existingAccessToken.getRefreshToken() != null) {
					refreshToken = existingAccessToken.getRefreshToken();
					// The token store could remove the refresh token when the
					// access token is removed, but we want to
					// be sure...
					tokenStore.removeRefreshToken(refreshToken);
				}
				tokenStore.removeAccessToken(existingAccessToken);
			}
			else {
				// Re-store the access token in case the authentication has changed
				tokenStore.storeAccessToken(existingAccessToken, authentication);
				return existingAccessToken;
			}
		}

		// Only create a new refresh token if there wasn't an existing one
		// associated with an expired access token.
		// Clients might be holding existing refresh tokens, so we re-use it in
		// the case that the old access token
		// expired.
		if (refreshToken == null) {
			refreshToken = createRefreshToken(authentication);
		}
		// But the refresh token itself might need to be re-issued if it has
		// expired.
		else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
			ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
			if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
				refreshToken = createRefreshToken(authentication);
			}
		}

		OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
            // 存入数据库中表AccessToken		
            tokenStore.storeAccessToken(accessToken, authentication);
		// In case it was modified
		refreshToken = accessToken.getRefreshToken();
		if (refreshToken != null) {
                    // 存入数据库中表RefreshToken		
			tokenStore.storeRefreshToken(refreshToken, authentication);
		}
		return accessToken;

	}
}

猜你喜欢

转载自blog.csdn.net/qq_30905661/article/details/81561976