上一章介绍了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;
}
}