写在前面

一、核心配置类 WebSecurityConfigurerAdapter

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
   
    @Autowired
    private UserDetailsService userDetailsService;
    @Bean
    public PasswordEncoder passwordEncoder() {
   
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
   
        http
        .csrf()
        .disable()
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        //.and()
            /*.formLogin() .successHandler(loginSuccessHandler()) .failureHandler(authenticationFailureHandler())*/
        .and()
            .exceptionHandling()
            .accessDeniedHandler(accessDeniedHandler())
        .and()
            .logout()
            .logoutSuccessHandler(logoutSuccessHandler())
        .and()
            .userDetailsService(userDetailsService)
            //OPTIONS请求全部放行
            .authorizeRequests()
            .antMatchers(/*HttpMethod.OPTIONS,*/ "/**").permitAll()
            .antMatchers("/auth/login").permitAll()
            .antMatchers("/auth/logout").permitAll()
            .accessDecisionManager(accessDecisionManager());
      //使用自定义的 Token过滤器 验证请求的Token是否合法
        http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
        super.configure(http);
    }

    @Bean
    public JwtTokenFilter authenticationTokenFilterBean() {
   
        return new JwtTokenFilter();
    }
    @Bean
    public JwtTokenProvider jwtTokenProvider() {
   
        return new JwtTokenProvider();
    }

}

二、用户/权限加载接口 UserDetailsService

@Component("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
   
    @Autowired
    private UserService userService;
    @Override
    public UserDetails loadUserByUsername(String userCode) throws UsernameNotFoundException {
   
        UserInfo userInfo = userService.findByUserCode(userCode);
        if (userInfo == null) {
   
            throw new UsernameNotFoundException("-------UserDetailsService---------->" + userCode);
        }
        List<String> resources = userService.findAllResourcesByUserCode(userCode);

        Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        resources.forEach(code -> {
   
            GrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(code);
            grantedAuthorities.add(simpleGrantedAuthority);
        });
        return new User(userCode, userInfo.getPassword(), grantedAuthorities);
    }
}

三、接入控制管理(AccessDecisionManager)

@Component("accessDecisionManager")
public class AuthAccessDecisionManager implements AccessDecisionManager {
   

    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
   
        // 鉴权验证
        if (!configAttributes.isEmpty()) {
   
            String authorize = null;
            for (ConfigAttribute configAttribute : configAttributes) {
   
                System.out.println("-------AccessDecisionManager--------------" + configAttribute.toString());
                authorize = configAttribute.toString();
                break;
            }
            if (Constants.NO_AUTHORIZE.equals(authorize)) {
   
                return;
            }
        }
        // 登陆验证
SecurityUtils.getCurrentUserLogin());
        if (Constants.ANONYMOUS_USER.equals(SecurityUtils.getCurrentUserLogin())) {
   
            throw new AccessDeniedException(" 没有权限访问! ");
        }
        //验证权限
        if (object instanceof FilterInvocation) {
   
            FilterInvocation web = (FilterInvocation) object;
            String uri = web.getRequestUrl();
            System.out.println("-------AccessDecisionManager--------------" + uri);

            String urlCode = this.getUrlCode(uri);
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

            List<? extends GrantedAuthority> collect = authorities.stream().collect(Collectors.toList());
            for (GrantedAuthority grantedAuthority : collect) {
   
                String authority = grantedAuthority.getAuthority();
                if (authority.equals(urlCode)) {
   
                    return;
                }
            }
        }
        throw new AccessDeniedException(" 没有权限访问! ");
    }


}

四、请求拦截(OncePerRequestFilter)

是封装在 org.springframework.web.filter 这个包下的,可用于拦截请求,验证请求头中的信息,或者初始基本信息
springSecurity中可用这个配置。如下的环境

五、核心验证器(AuthenticationManager)

5.1、AuthenticationManager

该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数;

public interface AuthenticationManager {
   
	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;
}

5.2、ProviderManager

它是 AuthenticationManager 的一个实现类,提供了基本的认证逻辑和方法;它包含了一个 List 对象,通过 AuthenticationProvider 接口来扩展出不同的认证提供者(当Spring Security默认提供的实现类不能满足需求的时候可以扩展AuthenticationProvider 覆盖supports(Class<?> authentication) 方法);

验证逻辑
AuthenticationManager 接收 Authentication 对象作为参数,并通过 authenticate(Authentication) 方法对其进行验证;AuthenticationProvider实现类用来支撑对 Authentication 对象的验证动作;UsernamePasswordAuthenticationToken实现了 Authentication主要是将用户输入的用户名和密码进行封装,并供给 AuthenticationManager 进行验证;验证完成以后将返回一个认证成功的 Authentication 对象;

Authentication
Authentication对象中的主要方法

public interface Authentication extends Principal, Serializable {
   
	//#1.权限结合,可使用AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_ADMIN")返回字符串权限集合
	Collection<? extends GrantedAuthority> getAuthorities();
	
	//#2.用户名密码认证时可以理解为密码
	Object getCredentials();
	
	//#3.认证时包含的一些信息。
	Object getDetails();
	
	//#4.用户名密码认证时可理解时用户名
	Object getPrincipal();
	
	#5.是否被认证,认证为true	
	boolean isAuthenticated();
	
	#6.设置是否能被认证
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
	...
	...
	}

ProviderManager是AuthenticationManager的实现类,提供了基本认证实现逻辑和流程;

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
   
		//#1.获取当前的Authentication的认证类型
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		Authentication result = null;
		boolean debug = logger.isDebugEnabled();
		//#2.遍历所有的providers使用supports方法判断该provider是否支持当前的认证类型,不支持的话继续遍历
		for (AuthenticationProvider provider : getProviders()) {
   
			if (!provider.supports(toTest)) {
   
				continue;
			}

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

			try {
   
				#3.支持的话调用provider的authenticat方法认证
				result = provider.authenticate(authentication);

				if (result != null) {
   
					#4.认证通过的话重新生成Authentication对应的Token
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException e) {
   
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			}
			catch (InternalAuthenticationServiceException e) {
   
				prepareException(e, authentication);
				throw e;
			}
			catch (AuthenticationException e) {
   
				lastException = e;
			}
		}

		if (result == null && parent != null) {
   
			// Allow the parent to try.
			try {
   
				#5.如果#1 没有验证通过,则使用父类型AuthenticationManager进行验证
				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;
			}
		}
		#6. 是否擦出敏感信息
		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;
		}

		// Parent was null, or didn't authenticate (or throw an exception).

		if (lastException == null) {
   
			lastException = new ProviderNotFoundException(messages.getMessage(
					"ProviderManager.providerNotFound",
					new Object[] {
    toTest.getName() },
					"No AuthenticationProvider found for {0}"));
		}

		prepareException(lastException, authentication);

		throw lastException;
	}
  • 1.遍历所有的 Providers,然后依次执行该 Provider 的验证方法
    如果某一个 Provider 验证成功,则跳出循环不再执行后续的验证;
    如果验证成功,会将返回的 result 既 Authentication 对象进一步封装为 Authentication Token; 比如 UsernamePasswordAuthenticationToken、RememberMeAuthenticationToken 等;这些 Authentication Token 也都继承自 Authentication 对象;
  • 2.如果 #1 没有任何一个 Provider 验证成功,则试图使用其 parent Authentication Manager 进行验证;
  • 3.是否需要擦除密码等敏感信息;

5.3、AuthenticationProvider

ProviderManager 通过 AuthenticationProvider 扩展出更多的验证提供的方式;而 AuthenticationProvider 本身也就是一个接口,从类图中我们可以看出它的实现类AbstractUserDetailsAuthenticationProvider 和AbstractUserDetailsAuthenticationProvider的子类DaoAuthenticationProvider 。DaoAuthenticationProvider 是Spring Security中一个核心的Provider,对所有的数据库提供了基本方法和入口。

DaoAuthenticationProvider
DaoAuthenticationProvider主要做了以下事情

对用户身份尽心加密操作;
#1.可直接返回BCryptPasswordEncoder,也可以自己实现该接口使用自己的加密算法核心方法String encode(CharSequence rawPassword);和boolean matches(CharSequence rawPassword, String encodedPassword);
private PasswordEncoder passwordEncoder;
实现了 AbstractUserDetailsAuthenticationProvider 两个抽象方法,
获取用户信息的扩展点
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser;

try {
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
}
主要是通过注入UserDetailsService接口对象,并调用其接口方法 loadUserByUsername(String username) 获取得到相关的用户信息。UserDetailsService接口非常重要。

实现 additionalAuthenticationChecks 的验证方法(主要验证密码);
AbstractUserDetailsAuthenticationProvider
AbstractUserDetailsAuthenticationProvider为DaoAuthenticationProvider提供了基本的认证方法;

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);

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

		try {
			#1.获取用户信息由子类实现即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 {
		#2.前检查由DefaultPreAuthenticationChecks类实现(主要判断当前用户是否锁定,过期,冻结User接口)
		preAuthenticationChecks.check(user);
		#3.子类实现
		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;
		}
	}
	#4.检测用户密码是否过期对应#2 的User接口
	postAuthenticationChecks.check(user);

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

	Object principalToReturn = user;

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

	return createSuccessAuthentication(principalToReturn, authentication, user);
}

AbstractUserDetailsAuthenticationProvider主要实现了AuthenticationProvider的接口方法 authenticate 并提供了相关的验证逻辑;

获取用户返回UserDetails AbstractUserDetailsAuthenticationProvider定义了一个抽象的方法
protected abstract UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException;
三步验证工作
preAuthenticationChecks
additionalAuthenticationChecks(抽象方法,子类实现)
postAuthenticationChecks
将已通过验证的用户信息封装成 UsernamePasswordAuthenticationToken 对象并返回;该对象封装了用户的身份信息,以及相应的权限信息,相关源码如下,
protected Authentication createSuccessAuthentication(Object principal,
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());

 return result;

}

六、验证流程图,分析