SpringSecurity(八)用户数据获取之SpringSecurityContextHolder深度剖析(下)

在上一篇中我们大致的说明了从Security中获取登录数据的逻辑以及SecurityContextHolder保存数据的策略,最后也遗留下了一个问题。—SpringBoot中不同的请求都是由不同的线程处理的,那为什么每一次请求都还能从SecurityContextHolder中获取到登录用户信息呢,这就得提到SpringSecurity过滤器链中最重要的一环了。

SecurityContextPersistenceFilter

前面几篇我们也介绍了SpringSecurity常见的过滤器,在这些过滤器中,存在一个非常重要的过滤器就是SecurityContextPersistenceFilter。

默认情况下,在SpringSecurity过滤器链中,SecurityContextPersistenceFilter是第二道防线,位于WebAsyncManagerIntegrationFilter之后。从SecurityContextPersistenceFilter这个名字上我们也能推断出来,它的作用就是为了存储SecurityContext而设计的。

整体来说,SecurityContextPersistenceFilter主要做了两年事情。

(1)、当一个请求到来时,从HttpSession中获取SecurityContext并存入SecurityContextHolder中,这样在同一个请求的后续处理过程中,我们始终可以通过SecurityContextHolder获取到当前登录用户的信息。

(2)、当一个请求处理完毕时,从SecurityContextHolder中获取SecurityContext并存入HttpSession中,方便下一个请求到来时,再从HttpSession中拿出来使用,同时擦除SecurityContextHolder中的登录信息。

注意:在SecurityContextPersistenceFilter过滤器中,当一个请求处理完毕时,从SecurityContextHolder中获取SecurityContext存入HttpSession中,这一步的操作主要是针对异步Servlet。如果不是异步Servlet,在响应提交时,就会将SecurityContext保存到HttpSession中了,而不会等到在SecurityContextPersistenceFilter过滤器中在去存储。

这就是SecurityContextPersistenceFilter大致上做的事情,这里在介绍SecurityContextPersistenceFilter之前,我们先来了解另外一个接口,SecurityContextRepository接口。

将SecurityContext存入HttpSession,或者从HttpSession中加载数据并转为SecurityContext对象,这些事情都是由SecurityContextRepository接口的实现类完成的,因此我们先来看这个接口。

public interface SecurityContextRepository {


	SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);

	
	void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response);

	
	boolean containsContext(HttpServletRequest request);

}

SecurityContextRepository接口中一共定义了三个方法:

  • loadContext:这个方法来加载SecurityContext对象出来,对于没有登录的用户,这里会返回一个空的SecurityContext对象,注意空的SecurityContext对象是指SecurityContext中不存在Authentication对象,而不是该方法返回null。
  • saveContext:该方法用来保存一个SecurityContext对象。
  • containsContext: 该方法可以判断SecurityContext对象是否存在。

在SpringSecurity框架中,为SecurityContextRepository接口一个提供了三个实现类,如下:

在这里插入图片描述

(1)、NullSecurityContextRepository实现类中,loadContext方法总是返回一个空的Security对象,saveContext方法未做任何实现,containsContext方法总是返回false,所以NullSecurityContextRepository实现类实际上未做SecurityContext的存储工作。

(2)、HttpSessionSecurityContextRepository:SpringSecurity中默认的实现类,通过HttpSessionSecurityContextRepository实现了将SecurityContext存储到HttpSession以及HttpSession中加载SecurityContext出来,我们会重点来介绍一下这个类。

(3)、既然说了SecurityContextRepository有三个实现类,那么为什么只列出了两个,其实还有一个TestSecurityContextRepository为单元测试提供支持,但是在后来版本中,SpringSecurity将它移除了。

HttpSessionSecurityContextRepository

我们先来看看HttpSessionSecurityContextRepository中定义的关于请求和封装的两个内部类。

首先是HttpSessionSecurityContextRepository中定义的对于响应的封装类SaveToSessionResponseWrapper,如下:

在这里插入图片描述

从这个继承关系中我们就可以看到,SaveToSessionResponseWrapper实际上就是我们熟悉的HttpServletResponse功能扩展,这里有三个关键的实现类:

(1)、HttpServletResponseWrapper:实现了HttpServletResponse接口,它是HttpServletResponse的装饰类,利用HttpServletRespinseWrapper可以方便地操作参数和输出流登。

(2)、OnCommittedResponseWrapper:继承自HttpServletResponseWrapper,对其功能进行了增强,最重要地增强在于可以获取HttpServletResponse地提交行为,当HttpServletResponse地sendError、sendRedirect、flushBuffer、flush以及close登方法被调用时,onResponseCommitted方法会被出发,我们可以在onResponseCommitted方法中做一些数据保存操作,例如保存SecurityContext,不过OnCommittedResponseWrapper中地onResponeCommitted方法只是一个抽象方法,并没有具体地实现,具体的实现在它的实现类SaveContextOnUpdateOrErrorResponseWrapper中。

(3)、SaveContextOnUpdateOrErrorResponseWrapper:继承自OnCommittedResponseWrapper并对onResponseCommitted方法做了实现,在SaveContextOnUpdateOrErrorResponseWrapper类中声明了一个contextSaved变量,表示SecurityContext是否已经存储成功,当HttpServletResponse提交时,会调用onResponseCommitted方法并在该方法中调用saveContext方法,将SecurityContext保存到HttpSession中,同时将contextSaved变量标记为true。saveContext方法在这里也是一个抽象方法,具体的实现类则在SaveToSessionResponseWrapper类中。

我们来看一下HttpSessionSecurityContextRepository中SaveToSessionResponseWrapper的定义:

final class SaveToSessionResponseWrapper extends SaveContextOnUpdateOrErrorResponseWrapper {

		private final Log logger = HttpSessionSecurityContextRepository.this.logger;

		private final HttpServletRequest request;

		private final boolean httpSessionExistedAtStartOfRequest;

		private final SecurityContext contextBeforeExecution;

		private final Authentication authBeforeExecution;

		private boolean isSaveContextInvoked;

		
		SaveToSessionResponseWrapper(HttpServletResponse response, HttpServletRequest request,
				boolean httpSessionExistedAtStartOfRequest, SecurityContext context) {
			super(response, HttpSessionSecurityContextRepository.this.disableUrlRewriting);
			this.request = request;
			this.httpSessionExistedAtStartOfRequest = httpSessionExistedAtStartOfRequest;
			this.contextBeforeExecution = context;
			this.authBeforeExecution = context.getAuthentication();
		}

		
		@Override
		protected void saveContext(SecurityContext context) {
			final Authentication authentication = context.getAuthentication();
			HttpSession httpSession = this.request.getSession(false);
			String springSecurityContextKey = HttpSessionSecurityContextRepository.this.springSecurityContextKey;
			// See SEC-776
			if (authentication == null
					|| HttpSessionSecurityContextRepository.this.trustResolver.isAnonymous(authentication)) {
				if (httpSession != null && this.authBeforeExecution != null) {
					// SEC-1587 A non-anonymous context may still be in the session
					// SEC-1735 remove if the contextBeforeExecution was not anonymous
					httpSession.removeAttribute(springSecurityContextKey);
					this.isSaveContextInvoked = true;
				}
				if (this.logger.isDebugEnabled()) {
					if (authentication == null) {
						this.logger.debug("Did not store empty SecurityContext");
					}
					else {
						this.logger.debug("Did not store anonymous SecurityContext");
					}
				}
				return;
			}
			httpSession = (httpSession != null) ? httpSession : createNewSessionIfAllowed(context, authentication);
			// If HttpSession exists, store current SecurityContext but only if it has
			// actually changed in this thread (see SEC-37, SEC-1307, SEC-1528)
			if (httpSession != null) {
				// We may have a new session, so check also whether the context attribute
				// is set SEC-1561
				if (contextChanged(context) || httpSession.getAttribute(springSecurityContextKey) == null) {
					httpSession.setAttribute(springSecurityContextKey, context);
					this.isSaveContextInvoked = true;
					if (this.logger.isDebugEnabled()) {
						this.logger.debug(LogMessage.format("Stored %s to HttpSession [%s]", context, httpSession));
					}
				}
			}
		}

		private boolean contextChanged(SecurityContext context) {
			return this.isSaveContextInvoked || context != this.contextBeforeExecution
					|| context.getAuthentication() != this.authBeforeExecution;
		}

		private HttpSession createNewSessionIfAllowed(SecurityContext context, Authentication authentication) {
			if (isTransientAuthentication(authentication)) {
				return null;
			}
			if (this.httpSessionExistedAtStartOfRequest) {
				this.logger.debug("HttpSession is now null, but was not null at start of request; "
						+ "session was invalidated, so do not create a new session");
				return null;
			}
			if (!HttpSessionSecurityContextRepository.this.allowSessionCreation) {
				this.logger.debug("The HttpSession is currently null, and the "
						+ HttpSessionSecurityContextRepository.class.getSimpleName()
						+ " is prohibited from creating an HttpSession "
						+ "(because the allowSessionCreation property is false) - SecurityContext thus not "
						+ "stored for next request");
				return null;
			}
			// Generate a HttpSession only if we need to
			if (HttpSessionSecurityContextRepository.this.contextObject.equals(context)) {
				this.logger.debug(LogMessage.format(
						"HttpSession is null, but SecurityContext has not changed from "
								+ "default empty context %s so not creating HttpSession or storing SecurityContext",
						context));
				return null;
			}
			try {
				HttpSession session = this.request.getSession(true);
				this.logger.debug("Created HttpSession as SecurityContext is non-default");
				return session;
			}
			catch (IllegalStateException ex) {
				// Response must already be committed, therefore can't create a new
				// session
				this.logger.warn("Failed to create a session, as response has been committed. "
						+ "Unable to store SecurityContext.");
			}
			return null;
		}

	}

在SaveToSessionResponseWrapper中其实主要定义了三个方法:saveContext、contextChanged以及createNewSessionIfAllowed

(1)、saveContext:该方法主要是用来保存SecurityContext,如果authentication对象为null或者它是一个匿名对象,则不需要保存SecurityContext,同时,如果HttpSession部位null并且authBeforeExecution也不为null,就从HttpSession中将保存的登录用户数据移除,这个主要是为了防止我们在注销成功的回调中继续调用chain.doFilter方法,进而导致原始的登录信息无法清除的问题。如果HttpSession为null,则取创建一个HttpSession对象,最后,如果SecurityContext发生了变化或者HttpSession中没有保存SecurityContext,则调用httpSession中的setAttribute方法将SecurityContext保存起来。

(2)、contextChanged:该方法主要用来判断SecurityContext是否发生变化,因为在程序运行过程中,我们可能修改了SecurityContext中的Authentication对象。

(3)、createNewSessionIfAllowed:该方法用来创建一个HttpSession对象。

这就是HttpSessionSecurityContextRepository中封装的SaveToSessionResponseWrapper对象,一个核心功能就是在HttpServletResponse提交的时候,将SecurityContext保存到HttpSession中。


我们来看一下HttpSessionSecurityContextRepository中SaveToSessionRequestWrapper的定义,相对而言SaveToSessionRequestWrapper就要简单的多了:

	private static class SaveToSessionRequestWrapper extends HttpServletRequestWrapper {

		private final SaveContextOnUpdateOrErrorResponseWrapper response;

		SaveToSessionRequestWrapper(HttpServletRequest request, SaveContextOnUpdateOrErrorResponseWrapper response) {
			super(request);
			this.response = response;
		}

		@Override
		public AsyncContext startAsync() {
			this.response.disableSaveOnResponseCommitted();
			return super.startAsync();
		}

		@Override
		public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse)
				throws IllegalStateException {
			this.response.disableSaveOnResponseCommitted();
			return super.startAsync(servletRequest, servletResponse);
		}

	}

SaveToSessionRequestWrapper实际上是在SpringSecurity3.2之后出现的封装类,封装它的作用主要是禁止在异步提交Servlet时,自动保存SecurityContext。

那么为什么要禁止呢?我们来看一下例子:

@GetMapping("/user2")
    public void userInfo(HttpServletRequest request, HttpServletResponse response){
        final AsyncContext async = request.startAsync();
        CompletableFuture.runAsync(()->{
            try {
                final PrintWriter printWriter = async.getResponse().getWriter();
                printWriter.write("hello spring security");
                async.complete();
            }catch (Exception e){

            }
        });
    }

可以看到,在异步Servlet中,当任务执行完毕之后,HttpServletResponse也会自动提交,在提交的过程中会自动保存SecurityContext到HttpSession中,但是由于在子线程中,因此无法获取到SecurityContext对象(SecurityContextHolder默认是将数据存储在ThreadLocal中,有兴趣的可以参考我的前几篇文章)所以会保存失败。如果我们使用了异步Servlet,则默认情况下会禁用HttpServletResponse提交时保存到SecurityContext这一功能,改为在SecurityContextPersistenceFilter过滤器中完成SecurityContext保存操作。

看完了HttpSessionSecurityContextRepository中封装的两个请求/响应对象后,我们来整体看一下HttpSessionSecurityContextRepository类的功能:

public class HttpSessionSecurityContextRepository implements SecurityContextRepository {

    public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";

    protected final Log logger = LogFactory.getLog(this.getClass());

    private final Object contextObject = SecurityContextHolder.createEmptyContext();

    private boolean allowSessionCreation = true;

    private boolean disableUrlRewriting = false;

    private String springSecurityContextKey = SPRING_SECURITY_CONTEXT_KEY;

    private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

    @Override
    public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
        HttpServletRequest request = requestResponseHolder.getRequest();
        HttpServletResponse response = requestResponseHolder.getResponse();
        HttpSession httpSession = request.getSession(false);
        SecurityContext context = readSecurityContextFromSession(httpSession);
        if (context == null) {
            context = generateNewContext();
            if (this.logger.isTraceEnabled()) {
                this.logger.trace(LogMessage.format("Created %s", context));
            }
        }
        org.springframework.security.web.context.HttpSessionSecurityContextRepository.SaveToSessionResponseWrapper wrappedResponse = new org.springframework.security.web.context.HttpSessionSecurityContextRepository.SaveToSessionResponseWrapper(response, request,
                httpSession != null, context);
        requestResponseHolder.setResponse(wrappedResponse);
        requestResponseHolder.setRequest(new org.springframework.security.web.context.HttpSessionSecurityContextRepository.SaveToSessionRequestWrapper(request, wrappedResponse));
        return context;
    }

    @Override
    public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
        SaveContextOnUpdateOrErrorResponseWrapper responseWrapper = WebUtils.getNativeResponse(response,
                SaveContextOnUpdateOrErrorResponseWrapper.class);
        Assert.state(responseWrapper != null, () -> "Cannot invoke saveContext on response " + response
                + ". You must use the HttpRequestResponseHolder.response after invoking loadContext");
        responseWrapper.saveContext(context);
    }

    @Override
    public boolean containsContext(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return false;
        }
        return session.getAttribute(this.springSecurityContextKey) != null;
    }

    /**
     * @param httpSession the session obtained from the request.
     */
    private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
        if (httpSession == null) {
            this.logger.trace("No HttpSession currently exists");
            return null;
        }
        // Session exists, so try to obtain a context from it.
        Object contextFromSession = httpSession.getAttribute(this.springSecurityContextKey);
        if (contextFromSession == null) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace(LogMessage.format("Did not find SecurityContext in HttpSession %s "
                        + "using the SPRING_SECURITY_CONTEXT session attribute", httpSession.getId()));
            }
            return null;
        }

        // We now have the security context object from the session.
        if (!(contextFromSession instanceof SecurityContext)) {
            this.logger.warn(LogMessage.format(
                    "%s did not contain a SecurityContext but contained: '%s'; are you improperly "
                            + "modifying the HttpSession directly (you should always use SecurityContextHolder) "
                            + "or using the HttpSession attribute reserved for this class?",
                    this.springSecurityContextKey, contextFromSession));
            return null;
        }

        if (this.logger.isTraceEnabled()) {
            this.logger.trace(
                    LogMessage.format("Retrieved %s from %s", contextFromSession, this.springSecurityContextKey));
        }
        else if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("Retrieved %s", contextFromSession));
        }
        // Everything OK. The only non-null return from this method.
        return (SecurityContext) contextFromSession;
    }

    protected SecurityContext generateNewContext() {
        return SecurityContextHolder.createEmptyContext();
    }

    public void setAllowSessionCreation(boolean allowSessionCreation) {
        this.allowSessionCreation = allowSessionCreation;
    }

    public void setDisableUrlRewriting(boolean disableUrlRewriting) {
        this.disableUrlRewriting = disableUrlRewriting;
    }

    public void setSpringSecurityContextKey(String springSecurityContextKey) {
        Assert.hasText(springSecurityContextKey, "springSecurityContextKey cannot be empty");
        this.springSecurityContextKey = springSecurityContextKey;
    }

    private boolean isTransientAuthentication(Authentication authentication) {
        return AnnotationUtils.getAnnotation(authentication.getClass(), Transient.class) != null;
    }

    public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
        Assert.notNull(trustResolver, "trustResolver cannot be null");
        this.trustResolver = trustResolver;
    }

    private static class SaveToSessionRequestWrapper extends HttpServletRequestWrapper {

        //省略
    }

    final class SaveToSessionResponseWrapper extends SaveContextOnUpdateOrErrorResponseWrapper {

            //省略
        }

        private boolean contextChanged(SecurityContext context) {
            return this.isSaveContextInvoked || context != this.contextBeforeExecution
                    || context.getAuthentication() != this.authBeforeExecution;
        }

        private HttpSession createNewSessionIfAllowed(SecurityContext context, Authentication authentication) {
            if (isTransientAuthentication(authentication)) {
                return null;
            }
            if (this.httpSessionExistedAtStartOfRequest) {
                this.logger.debug("HttpSession is now null, but was not null at start of request; "
                        + "session was invalidated, so do not create a new session");
                return null;
            }
            if (!org.springframework.security.web.context.HttpSessionSecurityContextRepository.this.allowSessionCreation) {
                this.logger.debug("The HttpSession is currently null, and the "
                        + org.springframework.security.web.context.HttpSessionSecurityContextRepository.class.getSimpleName()
                        + " is prohibited from creating an HttpSession "
                        + "(because the allowSessionCreation property is false) - SecurityContext thus not "
                        + "stored for next request");
                return null;
            }
            // Generate a HttpSession only if we need to
            if (org.springframework.security.web.context.HttpSessionSecurityContextRepository.this.contextObject.equals(context)) {
                this.logger.debug(LogMessage.format(
                        "HttpSession is null, but SecurityContext has not changed from "
                                + "default empty context %s so not creating HttpSession or storing SecurityContext",
                        context));
                return null;
            }
            try {
                HttpSession session = this.request.getSession(true);
                this.logger.debug("Created HttpSession as SecurityContext is non-default");
                return session;
            }
            catch (IllegalStateException ex) {
                // Response must already be committed, therefore can't create a new
                // session
                this.logger.warn("Failed to create a session, as response has been committed. "
                        + "Unable to store SecurityContext.");
            }
            return null;
        }

    }

}

我们来捋一下:

(1)、首先通过SPRING_SECURITY_CONTEXT_KEY变量定义了SecurityContext在HttpSession中存储的key,如果我们需要手动操作HttpSession中存储的SecurityContext,可以通过该key来操作。

(2)、trustResolver时一个用户身份评估器,用来判断当前用户是否是匿名用户还是通过RememberMe登录的用户。

(3)、在loadContext方法中,通过调用readSecurityContextFromSession方法来获取SecurityContext对象,如果获取到的对象为null,则调用generateNewContext方法生成一个空的SecurityContext对象,最后构造请求和响应的装饰类并存入requestResponseHolder对象中。

(4)、saveContext方法用来保存SecurityContext,在保存之前,会先调用isContextSaved方法判断是否已经保存了,如果已经保存了,则不在保存,正常情况下,在HttpServletResponse提交时SecurityContext就已经保存到HttpSession中了,如果是异步Servlet,则提交时不会自动将SecurityContext保存到HttpSession,此时会在这里进行保存操作。

(5)、containsContext方法用来判断请求中是否存在SecurityContext对象。

(6)、readSecurityContextFromSession方法执行具体的SecurityContext读取逻辑,从HttpSession中获取SecurityContext并返回。

(7)、generateNewContext方法用来生成一个不包含Authentication的空的SecurityContext对象。

(8)、setAllowSessionCreation方法用来设置是否允许创建HttpSession,默认是true。

(9)、setDisableUrlRewriting方法表示是否禁用URL重写,默认是false

(10)、setSpringSecurityContextKey方法可以用来配置HttpSession中存储SecurityContext中的key

(11)、isTransientAuthentication方法用来判断Authentication是否免于存储

(12)、setTrustResolver方法用来配置身份评估器

这就是HttpSessionSecurityContextRepository所提供的所有功能,这些功能都将在SecurityContextPersistenceFilter过滤器中进行调用:

public class SecurityContextPersistenceFilter extends GenericFilterBean {

	static final String FILTER_APPLIED = "__spring_security_scpf_applied";

	private SecurityContextRepository repo;

	private boolean forceEagerSessionCreation = false;

	public SecurityContextPersistenceFilter() {
		this(new HttpSessionSecurityContextRepository());
	}

	public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
		this.repo = repo;
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// ensure that filter is only applied once per request
		if (request.getAttribute(FILTER_APPLIED) != null) {
			chain.doFilter(request, response);
			return;
		}
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
		if (this.forceEagerSessionCreation) {
			HttpSession session = request.getSession();
			if (this.logger.isDebugEnabled() && session.isNew()) {
				this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
			}
		}
		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
		SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
		try {
			SecurityContextHolder.setContext(contextBeforeChainExecution);
			if (contextBeforeChainExecution.getAuthentication() == null) {
				logger.debug("Set SecurityContextHolder to empty SecurityContext");
			}
			else {
				if (this.logger.isDebugEnabled()) {
					this.logger
							.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
				}
			}
			chain.doFilter(holder.getRequest(), holder.getResponse());
		}
		finally {
			SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
			// Crucial removal of SecurityContextHolder contents before anything else.
			SecurityContextHolder.clearContext();
			this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
			request.removeAttribute(FILTER_APPLIED);
			this.logger.debug("Cleared SecurityContextHolder to complete request");
		}
	}

	public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
		this.forceEagerSessionCreation = forceEagerSessionCreation;
	}

}

过滤器的核心方法当然是doFilter,我们就从这里开始

(1)、首先从request中获取FILTER_APPLIED属性,如果该属性不为null,则直接执行chain.doFilter方法,当前过滤器到此为止,这个判断主要是确保该请求只执行一次该过滤器,如果确实是该request第一次经过该过滤器,则给其设置上FILTER_APPLIED属性。

(2)、forceEagerSessionCreation变量表示是否要在过滤器链执行之前确保会话有效,由于这是一个比较耗费资源的操作,因此默认false。

(3)、构造HttpRequestResponseHolder对象,将HttpServletRequest和HttpServletResponse都存储进去。

(4)、调用repo.loadContext方法去加载SecurityContext,repo实际上就是我们前面所说的HttpSessionSecurityContextPepository的实例,所以loadContext这里不在赘述了。

(5)、将读取到了SecurityContext存入到SecurityContextHolder之中,这样在接下来的处理逻辑中,我们就可以直接通过SecurityContextHolder获取当前登录用户对象了。

(6)、调用chain.doFilter方法使请求继续向下走,但是要注释,此时传递的request和response对象是在HttpSessionSecurityContextRepository中封装后的对象,即SaveToSessionResponseWrapper和SaveToSessionRequestWrapper的实例。

(7)、当请求处理完毕后,在finally模块中,获取最新的SecurityContext对象(我们可能在后续处理中修改了SecurityContext中的Authentication对象),然后清空SecurityContextHolder中的数据,在调用repo.saveContext方法保存到SecurityContext,关于如何保存上面也已经说了,这里不在赘述

(8)、最后从request中移除FILTER_APPLIED属性。

这就是整个SecurityContextPersistenceFilter过滤器的工作逻辑,整体就是:请求在到达SecurityContextPersistenceFilter过滤器之后,先从HttpSession中读取SecurityContext出来,并存入SecurityContextHolder中以备后续使用,当请求离开SecurityContextPersistenceFilter过滤器的时候,获取最新的SecurityContext并存入HttpSession中,同时清空SecurityContextHolder中的登录用户信息。

这就是第一种登录数据的获取方法,从SecurityContextHolder中获取。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Spring Security 中,要获取用户的权限可以通过以下步骤来实现: 1. 配置 Spring Security:首先,你需要在 Spring Security 的配置中定义用户的权限信息。这可以通过编写一个实现了 `UserDetailsService` 接口的自定义类来实现。在这个类中,你可以从数据库或其他数据源中获取用户的权限信息,并将其返回给 Spring Security。 2. 获取当前认证的用户:在需要获取用户权限的地方,你可以使用 `SecurityContextHolder` 类的静态方法 `getContext()` 获取当前认证的安全上下文对象。然后,可以通过调用 `getAuthentication()` 方法来获取当前认证的用户信息。 3. 获取用户权限:从上一步中获取到的用户信息中,你可以调用 `getAuthorities()` 方法来获取用户所拥有的权限列表。这个方法将返回一个 `Collection<? extends GrantedAuthority>` 类型的对象,其中包含了用户的权限信息。 以下是一个示例代码,演示了如何获取当前认证用户的权限: ```java import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; // 获取当前认证用户的权限 public Collection<? extends GrantedAuthority> getCurrentUserAuthorities() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { return authentication.getAuthorities(); } return null; } ``` 请注意,以上代码中的 `getAuthorities()` 方法返回的是一个权限列表,每个权限都是一个 `GrantedAuthority` 对象。你可以根据具体需求进一步处理这些权限信息。 希望这个回答能够帮助到你!如果你还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈橙橙丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值