一,源码解读
先来两张官方的类图,熟悉下类之间关系
1,身份验证大流程
AbstractAuthenticationProcessingFilter. java
/**
处理基于浏览器交互的HTTP验证请求
也就是说AbstractAuthenticationProcessingFilter处理所有的HTTP Request和Response对象,并将其封装成 AuthenticationManager可以处理的Authentication(Token),
并且在身份验证成功或失败之后,返回response
默认是有 UsernamePasswordAuthenticationFilter实现的
*/
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//判断拦截到的url是否需要进行权限鉴定,默认是除了/login外(一般设置/login什么权限的人都可以,具体在子类UsernamePasswordAuthenticationFilter中传入参数的)
//不是/login就执行if中语句
if (!requiresAuthentication(request, response)) {
//进行下一个Filter(一般情况下一个就是FilterSecurityInterceptor,即权限鉴定)
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
//UsernamePasswordAuthenticationFilter 的 attemptAuthentication 方法
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 (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authResult);
}
}
UsernamePasswordAuthenticationFilter. java
/**
登录的表单必须有username和password两个parameter,如常量定义的,但是也可以改变usernameParameter和passwordParameter的值
来改变传入的parameter字段的命名,但是没必要修改
另外过滤器只匹配/login以及POST请求为登录页为入口页,
*/
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
// ~ Static fields/initializers
// =====================================================================================
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
// ~ Constructors
// ===================================================================================================
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
// ~ Methods
// ========================================================================================================
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);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
//初始化一个 UsernamePasswordAuthenticationToken 其中 authenticated 属性初始化默认为false(也就还没通过身份验证)
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
//获取session如果过存在session则否返回当前session,如果不存在则返回null,然后把这些信息set到Token的details属性值中
setDetails(request, authRequest);
//获取到ProviderManager并调用它身份验证方法
return this.getAuthenticationManager().authenticate(authRequest);
}
}
ProviderManager. java
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
//身份验证
//Authentication 就是各种Token
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()) {
//通过各种provider,看有没有哪个provider支持验证这个Token
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
//最后是由 AbstractUserDetailsAuthenticationProvider 的 authenticate 方法执行的
result = provider.authenticate(authentication);
if (result != null) {
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 {
//如果result还是为null那么继续让parent进行执行
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;
}
// 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;
}
}
AbstractUserDetailsAuthenticationProvider. java
/**
看名字就知道这是提供对 UserDetails 进行解析然后身份验证的一个类
*/
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信息的
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//缓存中拿不到就从数据库中获取UserDetails信息 默认实现是 DaoAuthenticationProvider 的 retrieveUser方法
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的各种状态, 用户过期, 密码过期等
preAuthenticationChecks.check(user);
//密码匹配校验, 调用加密类 PasswordEncoder (可以自己定义)
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) {
//将UserDetails放入缓存
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
//将用户所有的所有合法角色放入Token 中的authorities中 并且 authenticated 设置为true 表示验证通过了
return createSuccessAuthentication(principalToReturn, authentication, user);
}
}
DaoAuthenticationProvider. java
/**
很简单就是提供 利用数据库进行身份验证
*/
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
//重要方法 调用自定义 UserDetailsService 的 loadUserByUsername 方法
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
}
UserService. java
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUserByUserName(username);
if (user == null) {
throw new UsernameNotFoundException("账户不存在");
}
List<Role> roles = userMapper.getUserRolesByUid(user.getId());
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
user.setAuthorities(authorities);
return user;
}
}
LoginUrlAuthenticationEntryPoint. java
/**
如果全权限不足,会抛出异常,然后被ExceptionTranslationFilter中catch住
try {
chain.doFilter(request, response);
logger.debug("Chain processed normally");
}
像这样,会catch住整个chain上抛出的异常,然后如果是AccessDeniedException(我们定义的鉴定方法就是抛出这个异常)的话,
就会调用如下方法进行请求重定向到/login
*/
public class LoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoint,
InitializingBean {
/**
* Performs the redirect (or forward) to the login form URL.
*/
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
String redirectUrl = null;
//默认为false所以不是转发
if (useForward) {
if (forceHttps && "http".equals(request.getScheme())) {
// First redirect the current request to HTTPS.
// When that request is received, the forward to the login page will be
// used.
redirectUrl = buildHttpsRedirectUrlForRequest(request);
}
if (redirectUrl == null) {
String loginForm = determineUrlToUseForThisRequest(request, response,
authException);
if (logger.isDebugEnabled()) {
logger.debug("Server side forward to: " + loginForm);
}
RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
dispatcher.forward(request, response);
return;
}
}
//重定向
else {
// 构建一个重定向链接到login页
redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
}
//最终还是使用 response.sendRedirect(redirectUrl);
redirectStrategy.sendRedirect(request, response, redirectUrl);
}
}
2,权限鉴定大流程
FilterSecurityInterceptor. java
/**
本文使用的FilterSecurityInterceptor类型是针对某个请求的层级进行拦截和安全检查,是比较常用的
还有支持方法层级的,AspectJ层级的(更细的方法层级)
继承自AbstractSecurityInterceptor详细见父类
*/
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
//权限鉴定入口,由filter链进行调用
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
//调用开始
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
//fi.getRequest()一定不为null,observeOncePerRequest默认为true
//getAttribute(FILTER_APPLIED)第一次进来没有值
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
//进来这里表示已经处理过一次请求了,不需要重新做安全检查
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
//进到这里表示第一次请求,需要进行安全检查
if (fi.getRequest() != null && observeOncePerRequest) {
//将FILTER_APPLIED标识放入request中
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
}
FilterInvocation. java
/**
保存与HTTP过滤器关联的对象
保证请求对象和响应对象是HttpServletRequest的实例和HttpServletResponse的实例,并且没有null对象
以上就是为了security框架内的类方便访问这些对象而不用每次都判断一下,转化一下
*/
public class FilterInvocation {
public FilterInvocation(ServletRequest request, ServletResponse response,
FilterChain chain) {
//保证获取到非null的request和response
if ((request == null) || (response == null) || (chain == null)) {
throw new IllegalArgumentException("Cannot pass null values to constructor");
}
this.request = (HttpServletRequest) request;
this.response = (HttpServletResponse) response;
this.chain = chain;
}
}
AbstractSecurityInterceptor. java
/**
抽象安全拦截器,确保安全拦截器的正确启动配置
它还将实现安全对象调用的正确处理,即:SecurityContextHolder中保存的Authentication对象
确定该请求是否与SecurityMetadataSource中配置的参数有关
如果这个请求是符合安全的那么,这里有一个ConfigAttribute列表给安全对象进行匹配:
如果org.springframework.security.core.Authentication#isAuthenticated()返回false,或者
#alwaysReauthenticate是true,根据配置的AuthenticationManager对请求进行身份验证
然后还会调用AccessDecisionManager进行权限鉴定
*/
public abstract class AbstractSecurityInterceptor implements InitializingBean,ApplicationEventPublisherAware, MessageSourceAware {
//object为FilterInvocation
protected InterceptorStatusToken beforeInvocation(Object object) {
/*省略次要代码*/
//在此处获取ConfigAttribute集合,是通过调用SecurityMetadataSource的getAttributes方法获取的,
//本文使用的是自定义的FilterInvocationSecurityMetadataSource
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
//第一次进来会会获取到AnonymousAuthenticationToken,他是在AnonymousAuthenticationFilter中初始化的,也就是匿名请求
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
//判断是否检查当前身份,验证令牌,并返回Authentication对象
//第一次进去不符合条件直接返回匿名Token对象
Authentication authenticated = authenticateIfRequired();
//尝试进行授权
try {
//真正进行鉴定权限的地方通过的方法他是在AccessDecisionManager中的,本文使用的是它的自定义类
//第一次进来是匿名Token对象,角色也是"ROLE_anonymous"没有一定会抛异常
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
if (debug) {
logger.debug("Authorization successful");
}
if (publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);
if (runAs == null) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);
}
else {
if (debug) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
/**
主要就是身份验证,如果权限都不够那么也就不用身份认证了,直接返回
如果org.springframework.security.core.Authentication#isAuthenticated()方法返回false,
或者alwaysReauthenticate已经被设置为true,则检查当前的身份验证令牌并将其传递给AuthenticationManager
*/
private Authentication authenticateIfRequired() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication.isAuthenticated() && !alwaysReauthenticate) {
if (logger.isDebugEnabled()) {
logger.debug("Previously Authenticated: " + authentication);
}
return authentication;
}
authentication = authenticationManager.authenticate(authentication);
// We don't authenticated.setAuthentication(true), because each provider should do
// that
if (logger.isDebugEnabled()) {
logger.debug("Successfully Authenticated: " + authentication);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
}
CustomFilterInvocationSecurityMetadataSource. java
/**
* 自定义认证数据源
*
*
* @author: baifan
* @date: 2020/9/23
*/
@Service
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
//ant风格的URL匹配
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Autowired
MenuMapper menuMapper;
/**
* @param object 一个FilterInvocation
* @return Collection<ConfigAttribute> 当前请求URL所需的角色
* @throws IllegalArgumentException
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
//从FilterInvocation中获取当前请求的URL
String requestUrl = ((FilterInvocation) object).getRequestUrl();
//从数据库中获取所有的资源(角色和menu都查询)信息,可以缓存
List<Menu> allMenus = menuMapper.getAllMenus();
//遍历获取当前请求的URL所需要的角色信息
for (Menu menu : allMenus) {
if (antPathMatcher.match(menu.getPattern(), requestUrl)) {
List<Role> roles = menu.getRoles();
String[] roleArr = new String[roles.size()];
for (int i = 0; i < roleArr.length; i++) {
roleArr[i] = roles.get(i).getName();
}
return SecurityConfig.createList(roleArr);
}
}
//假设不存在URL对应的角色,则登录后即可访问
return SecurityConfig.createList("ROLE_LOGIN");
}
//获取所有定义好的权限资源
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
//返回类对象是否支持校验
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
ConfigAttribute. java
/**
存储与安全系统有关的配置属性
当一个AbstractSecurityInterceptor建立后,这里就有一个安全对象的样式(本文设定这个样式是角色名)的list
这个list对于RunAsManager和AccessDecisionManager以及AccessDecisionManager的继承类有特殊的意义,
对于AccessDecisionManager可以用这个list进行决定访问的对象是否符合安全样式
本文就是自定义了AccessDecisionManager
ConfigAttribute为接口, 本文具体实现使用的SecurityConfig
*/
public interface ConfigAttribute extends Serializable {
String getAttribute();
}
SecurityConfig. java
/**
保存的都是String数据类型的ConfigAttribute
*/
public class SecurityConfig implements ConfigAttribute {
private final String attrib;
// ~ Constructors
// ===================================================================================================
public SecurityConfig(String config) {
Assert.hasText(config, "You must provide a configuration attribute");
this.attrib = config;
}
}
CustomAccessDecisionManager. java
/**
* 当一个请求走完FilterInvocationSecurityMetadataSource中的getAttributes方法后
* 就会到AccessDecisionManager中进行角色信息的对比
*
* @author: baifan
* @date: 2020/9/23
*/
@Service
public class CustomAccessDecisionManager implements AccessDecisionManager {
/**
* 在该方法中判断当前登录的用户是否具备当前请求URL所需要的角色信息
* 如果不具备,就抛出AccessDeniedException异常
* @param authentication 包含当前登录用户的信息
* @param object FilterInvocation
* @param configAttributes FilterInvocationSecurityMetadataSource中的getAttributes方法返回值
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (ConfigAttribute configAttribute : configAttributes) {
if ("ROLE_LOGIN".equals(configAttribute.getAttribute()) && authentication instanceof UsernamePasswordAuthenticationToken/*说明已登录*/) {
return;
}
for (GrantedAuthority authority : authorities) {
if (configAttribute.getAttribute().equals(authority.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("权限不足");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
二,执行流程
第一种情况:未登录,第一次访问/login
1,请求被AbstractAuthenticationProcessingFilter拦截执行doFilter方法
2,doFilter方法中requiresAuthentication方法判断请求不符合POST/login请求
3,进入FilterChain中下一个FilterDefaultLoginPageGeneratingFilter
4,执行DefaultLoginPageGeneratingFilter的doFilter方法,它会将来自/login/login?error/login?logout这些跳转链接统一重定向到自己生成的登录页并且执行了return即不在执行后续的Filter了
所以默认情况下看到的登录页,全都是代码硬编码写死的,不好用也不可取,只要在WebSecurityConfig中配置了loginPage就可以自定义登录页面了(使用前后部分里),很鸡肋的登录页啊!
第二种情况:未登录, 直接访问其它链接如 /db
由于在自定义的 WebSecurityConfig中没有设置 /db可以允许任何人访问
1,请求被 AbstractAuthenticationProcessingFilter 拦截 执行 doFilter 方法
2,doFilter 方法中 requiresAuthentication 方法判断请求不符合 POST /login 请求
3,进入FilterChain中下一个Filter DefaultLoginPageGeneratingFilter
4,执行DefaultLoginPageGeneratingFilter的 doFilter 方法由于 不符合 /login /login?error /login?logout 路径所以不会立刻反回登录页
5,进入FilterChain中下一个Filter RequestCacheAwareFilter
6,RequestCacheAwareFilter 中获取session中有没有SPRING_SECURITY_SAVED_REQUEST缓存 并且将获取到的缓存传入下一个Filter SecurityContextHolderAwareRequestFilter
7,SecurityContextHolderAwareRequestFilter 其中定义了 private String rolePrefix = "ROLE_"; 并且将请求转化为 HttpServletRequest 继续下一个Filter AnonymousAuthenticationFilter
8,AnonymousAuthenticationFilter 生成了一个AnonymousToken(与UserNameToken对比)包含匿名的验证对象 继续下一个Filter AbstractSecurityInterceptor
9,不管生成怎样的Token都要 在AbstractSecurityInterceptor 和它的子类 FilterSecurityInterceptor 中进行权限鉴定 主要做了如下事情
(1)调用 FilterInvocationSecurityMetadataSource (本文为自定义子类) 中的 getAttributes方法 根据 url获取匹配的角色列表,并且转换为 ConfigAttribute 列表
(2)调用 AccessDecisionManager (本文为自定义子类) 中的 decide 方法判断是否通过权限鉴定 很显然本文没有任何匿名角色即ROLE_ANONYMOUS的配置,所有没有通过 抛出了AccessDeniedException
10,经过或许Filter处理,回到登录页,至此结束
第三种情况比较复杂用流程图表示
附录
spring-security中的过滤器
1,OncePerRequestFilter(通用): Spring中默认所有Filter最前面的,作用是兼容各种请求,保证每次执行一个Filter
2,WebAsyncManagerIntegrationFilter(通用): Web异步管理集成过滤器,作用是集成SecurityContext到Spring异步执行机制中的WebAsyncManager
3,SecurityContextPersistenceFilter: 先从session查找有没有securityContext,没有的创建一个并且放入到SecurityContextHolder中
4,HeaderWriterFilter: 在请求前后向往前请求头或者响应头写入一些信息
5,LogoutFilter: 登出过滤器
6,AbstractAuthenticationProcessingFilter(起重要作用): 处理所有基于HTTP请求form登录的过滤器, 身份认证获取Token
7,DefaultLoginPageGeneratingFilter: 是否跳转默认登录页Filter
8,RequestCacheAwareFilter
9,SecurityContextHolderAwareRequestFilter
10,AnonymousAuthenticationFilter: 处理匿名用户访问
11,SessionManagementFilter
12,ExceptionTranslationFilter: security系统异常处理
13,AbstractSecurityInterceptor(FilterSecurityInterceptor起重要作用): 根据Token进行权限鉴定