Spring Security源码解析


Spring Security的核心思想是授权和认证。认证可以访问系统的用户,而授权则是用户可以访问的资源

认证是调用authenticationManager.authenticate()方法来获得证书authentication,一般我们采用用户名、密码方式认证,那么authentication的实现类就是UsernamePasswordAuthentication。

一、Authentication,AuthenticationManager,AuthenticationProvider

Authentication的继承关系大致如下,其中Principal代表用户主体的概念,如用User,login id或者username代表一个entity,主要方法有equals()和getName();

 

Authentication用于存储身份验证信息,接口内容如下,包括getAuthorities(),getDetails(),getPrinciple(),getCredentials()以及isAuthenticated()。

public interface Authentication extends Principal, Serializable {
        /**
        *   一般在JPAEntity中继承UserDetail,重写该方法,
        *   存储验证信息Authority集合
        *   GrantedAuthority是个接口,有方法 String getAuthority();
        *   GrantedAuthority代表授权,a representation of the granted authority,
        *   getAuthority()是String类型,一般用SimpleGrantedAuthority(role)来实例化
        */
        Collection<? extends GrantedAuthority> getAuthorities();


        /**
        *   返回证明用户身份的证书,一般是用户密码
        */ 
        Object getCredentials();
        

        /**
        *   身份验证request额外的细节,如IP地址,和证书序列号
        */ 
        Object getDetails();

        
        /**
        *   返回用户身份,一般是继承了UserDetail的Entity
        */ 
        Object getPrincipal();



        /**
        *   如果是true,则表示token已经验证通过了,无需再调用AuthenticationManager进行验证
        */ 
        boolean isAuthenticated();



        /** 
        * 见isAuthenticated()
        */
	  void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

介绍完了Authentication,再来聊聊AuthenticationManager和AuthenticationProvider,内容如下。

public interface AuthenticationManager {
	 /**
	 * 验证传入的authentication信息
         * 验证成功则返回一个包含authorities,并设置isAuthorized=true的完整Authentication
         * 验证失败则抛出异常
	 * @param authentication the authentication request object
	 *
	 * @return a fully authenticated object including credentials
	 *
	 * @throws AuthenticationException if authentication fails
	 */
	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;
}
public interface AuthenticationProvider {
	 /**
	 * 和AuthenticationManager中的authenticate方法一致
	 * @throws AuthenticationException if authentication fails.
	 */
	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;

	/**
	 * 判断AuthenticationProvider是否支持authentication的补全
	 */
	boolean supports(Class<?> authentication);
}


二、UserDetail,UserDetailsService,UserCache,User

  •  UserDetail提供基本用户信息,如getUserName(),getPassword(),List<? extends GrantedAuthority>   getAuthorities()等,但一般不会直接调用UserDetail,而是包装后放到Authentication中的principle和authorities中。一般用户实体类继承UserDetail。
  • UserDetailsService就简单了,只有一个方法,通过用户名找用户信息UserDetail。
public interface UserDetailsService {
	/**
	 * Locates the user based on the username. In the actual implementation, the search
	 * may possibly be case sensitive, or case insensitive depending on how the
	 * implementation instance is configured. In this case, the <code>UserDetails</code>
	 * object that comes back may have a username that is of a different case than what
	 * was actually requested..
	 *
	 * @param username the username identifying the user whose data is required.
	 *
	 * @return a fully populated user record (never <code>null</code>)
	 *
	 * @throws UsernameNotFoundException if the user could not be found or the user has no
	 * GrantedAuthority
	 */
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
  • UserCache可以代替UserDetailsService获得用户基本信息UserDetail,如果有缓存则不用每次都执行loadUserByUsername,只需从UserCache中调用UserDetail getUserFromCache(String Username)方法即可。如果没有缓存就执行loadUserByUsername,并把获取的UserDetail存入缓存中。
  • User继承了UserDetails和CredentialsContainer两个接口,常用于UserDetailsService中返回loadUserByUsername()方法的结果(代替自己定义的User实体),但注意的是,必须每次返回的都是新建的User,因为它不是immutable。CredentialsContainer用于清除敏感信息。

                

       User包含属性和方法如下,

        private String password;
	private final String username;
	private final Set<GrantedAuthority> authorities;
	private final boolean accountNonExpired;
	private final boolean accountNonLocked;
	private final boolean credentialsNonExpired;
	private final boolean enabled;

        //  继承自CredentialsContainer,继承该方法的类如Authentication需要把敏感信息credential给去除掉
        public void eraseCredentials() {
		password = null;
	}

二、Spring Security执行过程

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

  • 过滤器拦截请求,判断request是否需要验证身份信息
  • 将request中的username和password存入UsernamePasswordAuthentication中,用ProviderManager(包含AuthenticationProvider的集合,依次执行多个AuthenticationProvider中的验证方法)的Authenticate方法进行补全优化
  • retriveUser(username,authentication)会调用UserDetailsService中的loadUserByUsername()方法,查询获得数据库中的用户基本信息并存入UserDetail的实现类中。
  • 获得的resultAuthentication会存入UserCache,且erase掉credentials。

1.AbstractAuthenticationProcessingFilter

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

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
              // 判断request是否为post,是否需要验证身份信息
		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);
		}
        
        ...
}
扫描二维码关注公众号,回复: 2268226 查看本文章

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 ...

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		Authentication result = null;
		boolean debug = logger.isDebugEnabled();

		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 {
                    // 这里调用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/81082453