SpringBoot2整合SpringSecurity+Swagger3(源码分析四)


SpringBoot2整合SpringSecurity+Swagger3系列


Spring Securtiy的主要功能是通过Filter来实现的,更准确的说,是通过过滤器名称为springSecurityFilterChain的DelegatingFilterProxy来实现的。

Proxy for a standard Servlet Filter, delegating to a Spring-managed bean that implements the Filter interface. Supports a “targetBeanName” filter init-param in web.xml, specifying the name of the target bean in the Spring application context.
web.xml will usually contain a DelegatingFilterProxy definition, with the specified filter-name corresponding to a bean name in Spring’s root application context. All calls to the filter proxy will then be delegated to that bean in the Spring context, which is required to implement the standard Servlet Filter interface.
This approach is particularly useful for Filter implementation with complex setup needs, allowing to apply the full Spring bean definition machinery to Filter instances. Alternatively, consider standard Filter setup in combination with looking up service beans from the Spring root application context.
NOTE: The lifecycle methods defined by the Servlet Filter interface will by default not be delegated to the target bean, relying on the Spring application context to manage the lifecycle of that bean. Specifying the “targetFilterLifecycle” filter init-param as “true” will enforce invocation of the Filter.init and Filter.destroy lifecycle methods on the target bean, letting the servlet container manage the filter lifecycle.

当有web请求的时候,会调用这个过滤器的doFilter方法。如果是第一次,则会从Spring容器获取目标Bean(名称为springSecurityFilterChain),也就是所谓的proxy。

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
		throws ServletException, IOException {

	// Lazily initialize the delegate if necessary.
	Filter delegateToUse = this.delegate;
	if (delegateToUse == null) {
		synchronized (this.delegateMonitor) {
			delegateToUse = this.delegate;
			if (delegateToUse == null) {
				WebApplicationContext wac = findWebApplicationContext();
				if (wac == null) {
					throw new IllegalStateException("No WebApplicationContext found: " +
							"no ContextLoaderListener or DispatcherServlet registered?");
				}
				delegateToUse = initDelegate(wac);
			}
			this.delegate = delegateToUse;
		}
	}

	// Let the delegate perform the actual doFilter operation.
	invokeDelegate(delegateToUse, request, response, filterChain);
}

这个代理就是上一章谈及的DebugFilter,如果没有开启日志功能,则是FilterChainProxy。过滤功能主要是靠filters属性来实现的,如下图所示,这里包含了14个过滤器。
在这里插入图片描述
invokeDelegate方法调用会进入org.springframework.security.web.FilterChainProxy#doFilter方法,最后进入doFilterInternal方法。

private void doFilterInternal(ServletRequest request, ServletResponse response,
		FilterChain chain) throws IOException, ServletException {

	FirewalledRequest fwRequest = firewall
			.getFirewalledRequest((HttpServletRequest) request);
	HttpServletResponse fwResponse = firewall
			.getFirewalledResponse((HttpServletResponse) response);

	List<Filter> filters = getFilters(fwRequest);

	if (filters == null || filters.size() == 0) {
		if (logger.isDebugEnabled()) {
			logger.debug(UrlUtils.buildRequestUrl(fwRequest)
					+ (filters == null ? " has no matching filters"
							: " has an empty filter list"));
		}

		fwRequest.reset();

		chain.doFilter(fwRequest, fwResponse);

		return;
	}

	VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
	vfc.doFilter(fwRequest, fwResponse);
}

由于filters列表不为空,所以这里创建了一个过滤器链VirtualFilterChain实例。

/**
 * Internal {@code FilterChain} implementation that is used to pass a request through
 * the additional internal list of filters which match the request.
 */
private static class VirtualFilterChain implements FilterChain {
	private final FilterChain originalChain;
	private final List<Filter> additionalFilters;
	private final FirewalledRequest firewalledRequest;
	private final int size;
	private int currentPosition = 0;

	private VirtualFilterChain(FirewalledRequest firewalledRequest,
			FilterChain chain, List<Filter> additionalFilters) {
		this.originalChain = chain;
		this.additionalFilters = additionalFilters;
		this.size = additionalFilters.size();
		this.firewalledRequest = firewalledRequest;
	}
	...
}	

originalChain代表原始过滤器链(也是注册到ServletContext中的过滤器所组成的,由Tomcat容器创建,类型为ApplicationFilterChain)。在这里不妨称originalChain为Tomcat容器过滤器链,而VirtualFilterChain为Spring Security的过滤器链。
在这里插入图片描述
additionalFilters这是实现Spring Securtiy功能的过滤器列表,size和currentPosition则用于获取这个列表中的过滤器或者返回原过滤器调用。而firewalledRequest则当前的http请求包装之后的请求实例。对应的doFilter实现如下所示

@Override
public void doFilter(ServletRequest request, ServletResponse response)
		throws IOException, ServletException {
	if (currentPosition == size) {
		if (logger.isDebugEnabled()) {
			logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
					+ " reached end of additional filter chain; proceeding with original chain");
		}

		// Deactivate path stripping as we exit the security filter chain
		this.firewalledRequest.reset();

		originalChain.doFilter(request, response);
	}
	else {
		currentPosition++;

		Filter nextFilter = additionalFilters.get(currentPosition - 1);

		if (logger.isDebugEnabled()) {
			logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
					+ " at position " + currentPosition + " of " + size
					+ " in additional filter chain; firing Filter: '"
					+ nextFilter.getClass().getSimpleName() + "'");
		}

		nextFilter.doFilter(request, response, this);
	}
}

可以看到每次currentPosition都会加1然后从additionalFilters列表中获取一个过滤器执行过滤操作,如果currentPosition等于了size的时候则会执行Servlet容器的过滤器链了。不难看出,Spring Security的过滤器链式调用与Tocmat容器的异曲同工。典型的责任链模式。

  • WebAsyncManagerIntegrationFilter
protected void doFilterInternal(HttpServletRequest request,
		HttpServletResponse response, FilterChain filterChain)
		throws ServletException, IOException {
	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

	SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
			.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
	if (securityProcessingInterceptor == null) {
		asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,
				new SecurityContextCallableProcessingInterceptor());
	}

	filterChain.doFilter(request, response);
}

这里为WebAsyncManager 设置了一个调用拦截器SecurityContextCallableProcessingInterceptor,后者主要用于与SecurityContextHolder一起实现ServletContext对象的共享。相应方法如下

@Override
public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task)
		throws Exception {
	if (securityContext == null) {
		setSecurityContext(SecurityContextHolder.getContext());
	}
}

@Override
public <T> void preProcess(NativeWebRequest request, Callable<T> task)
		throws Exception {
	SecurityContextHolder.setContext(securityContext);
}

@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task,
		Object concurrentResult) throws Exception {
	SecurityContextHolder.clearContext();
}

private void setSecurityContext(SecurityContext securityContext) {
	this.securityContext = securityContext;
}
  • SecurityContextPersistenceFilter

与SecurityContextHolder搭配共享SecurityContext。什么是SecurityContext呢?接口如下

public interface SecurityContext extends Serializable {
	// ~ Methods
	// ========================================================================================================

	/**
	 * Obtains the currently authenticated principal, or an authentication request token.
	 *
	 * @return the <code>Authentication</code> or <code>null</code> if no authentication
	 * information is available
	 */
	Authentication getAuthentication();

	/**
	 * Changes the currently authenticated principal, or removes the authentication
	 * information.
	 *
	 * @param authentication the new <code>Authentication</code> token, or
	 * <code>null</code> if no further authentication information should be stored
	 */
	void setAuthentication(Authentication authentication);
}

doFilter方法如下

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
		throws IOException, ServletException {
	HttpServletRequest request = (HttpServletRequest) req;
	HttpServletResponse response = (HttpServletResponse) res;

	if (request.getAttribute(FILTER_APPLIED) != null) {
		// ensure that filter is only applied once per request
		chain.doFilter(request, response);
		return;
	}

	final boolean debug = logger.isDebugEnabled();

	request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

	if (forceEagerSessionCreation) {
		HttpSession session = request.getSession();

		if (debug && session.isNew()) {
			logger.debug("Eagerly created session: " + session.getId());
		}
	}

	HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
			response);
	SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

	try {
		SecurityContextHolder.setContext(contextBeforeChainExecution);

		chain.doFilter(holder.getRequest(), holder.getResponse());

	}
	finally {
		SecurityContext contextAfterChainExecution = SecurityContextHolder
				.getContext();
		// Crucial removal of SecurityContextHolder contents - do this before anything
		// else.
		SecurityContextHolder.clearContext();
		repo.saveContext(contextAfterChainExecution, holder.getRequest(),
				holder.getResponse());
		request.removeAttribute(FILTER_APPLIED);

		if (debug) {
			logger.debug("SecurityContextHolder now cleared, as request processing completed");
		}
	}
}

可以看出在每次执行后续过滤器之前setContext,而在finally(请求返回时)当中clearContext进行清除。

SecurityContextHolder本身并不存储SecurityContext,而是通过SecurityContextHolderStrategy来实现的。用户可以通过spring.security.strategy系统参数设置。Spring Security提供了三种实现,ThreadLocal共享、InheritableThreadLocal共享(子线程使用父线程的值)、全局共享。具体参考源码。当然这里也支持用户自定义。

-- org.springframework.security.core.context.SecurityContextHolder
public static final String SYSTEM_PROPERTY = "spring.security.strategy";
private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
private static SecurityContextHolderStrategy strategy;
private static int initializeCount = 0;

static {
	initialize();
}

private static void initialize() {
	if (!StringUtils.hasText(strategyName)) {
		// Set default
		strategyName = MODE_THREADLOCAL;
	}

	if (strategyName.equals(MODE_THREADLOCAL)) {
		strategy = new ThreadLocalSecurityContextHolderStrategy();
	}
	else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
		strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
	}
	else if (strategyName.equals(MODE_GLOBAL)) {
		strategy = new GlobalSecurityContextHolderStrategy();
	}
	else {
		// Try to load a custom strategy
		try {
			Class<?> clazz = Class.forName(strategyName);
			Constructor<?> customStrategy = clazz.getConstructor();
			strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
		}
		catch (Exception ex) {
			ReflectionUtils.handleReflectionException(ex);
		}
	}

	initializeCount++;
}
  • HeaderWriterFilter

用于往当前的response中添加一些头信息。

Filter implementation to add headers to the current response. Can be useful to add certain headers which enable browser protection. Like X-Frame-Options, X-XSS-Protection and X-Content-Type-Options.

@Override
protected void doFilterInternal(HttpServletRequest request,
		HttpServletResponse response, FilterChain filterChain)
				throws ServletException, IOException {

	HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
			response, this.headerWriters);
	HeaderWriterRequest headerWriterRequest = new HeaderWriterRequest(request,
			headerWriterResponse);

	try {
		filterChain.doFilter(headerWriterRequest, headerWriterResponse);
	}
	finally {
		headerWriterResponse.writeHeaders();
	}
}

在这里插入图片描述

  • CsrfFilter

Applies CSRF protection using a synchronizer token pattern. Developers are required to ensure that CsrfFilter is invoked for any request that allows state to change. Typically this just means that they should ensure their web application follows proper REST semantics (i.e. do not change state with the HTTP methods GET, HEAD, TRACE, OPTIONS).

Typically the CsrfTokenRepository implementation chooses to store the CsrfToken in HttpSession with HttpSessionCsrfTokenRepository wrapped by a LazyCsrfTokenRepository. This is preferred to storing the token in a cookie which can be modified by a client application.

protected void doFilterInternal(HttpServletRequest request,
		HttpServletResponse response, FilterChain filterChain)
				throws ServletException, IOException {
	request.setAttribute(HttpServletResponse.class.getName(), response);

	CsrfToken csrfToken = this.tokenRepository.loadToken(request);
	final boolean missingToken = csrfToken == null;
	if (missingToken) {
		csrfToken = this.tokenRepository.generateToken(request);
		this.tokenRepository.saveToken(csrfToken, request, response);
	}
	request.setAttribute(CsrfToken.class.getName(), csrfToken);
	request.setAttribute(csrfToken.getParameterName(), csrfToken);

	if (!this.requireCsrfProtectionMatcher.matches(request)) {
		filterChain.doFilter(request, response);
		return;
	}

	String actualToken = request.getHeader(csrfToken.getHeaderName());
	if (actualToken == null) {
		actualToken = request.getParameter(csrfToken.getParameterName());
	}
	if (!csrfToken.getToken().equals(actualToken)) {
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Invalid CSRF token found for "
					+ UrlUtils.buildFullRequestUrl(request));
		}
		if (missingToken) {
			this.accessDeniedHandler.handle(request, response,
					new MissingCsrfTokenException(actualToken));
		}
		else {
			this.accessDeniedHandler.handle(request, response,
					new InvalidCsrfTokenException(csrfToken, actualToken));
		}
		return;
	}

	filterChain.doFilter(request, response);
}

首先从tokenRepository获取token

@Override
public CsrfToken loadToken(HttpServletRequest request) {
	Cookie cookie = WebUtils.getCookie(request, this.cookieName);
	if (cookie == null) {
		return null;
	}
	String token = cookie.getValue();
	if (!StringUtils.hasLength(token)) {
		return null;
	}
	return new DefaultCsrfToken(this.headerName, this.parameterName, token);
}

第一次获取为空,通过tokenRepository创建一个token,并且保存起来。默认的实现为tokenRepositoryCookieCsrfTokenRepository。

@Override
public CsrfToken generateToken(HttpServletRequest request) {
	return new DefaultCsrfToken(this.headerName, this.parameterName,
			createNewToken());
}

private String createNewToken() {
	return UUID.randomUUID().toString();
}

所谓创建token,其实通过UUID生成一个值。
在这里插入图片描述
保存token就是设置到Cookie当中。

@Override
public void saveToken(CsrfToken token, HttpServletRequest request,
		HttpServletResponse response) {
	String tokenValue = token == null ? "" : token.getToken();
	Cookie cookie = new Cookie(this.cookieName, tokenValue);
	cookie.setSecure(request.isSecure());
	if (this.cookiePath != null && !this.cookiePath.isEmpty()) {
			cookie.setPath(this.cookiePath);
	} else {
			cookie.setPath(this.getRequestContext(request));
	}
	if (token == null) {
		cookie.setMaxAge(0);
	}
	else {
		cookie.setMaxAge(-1);
	}
	if (cookieHttpOnly && setHttpOnlyMethod != null) {
		ReflectionUtils.invokeMethod(setHttpOnlyMethod, cookie, Boolean.TRUE);
	}

	response.addCookie(cookie);
}

在这里插入图片描述
将生成的token设置到请求当中
在这里插入图片描述
还要进行一些匹配,这里的请求匹配器使用默认的DefaultRequiresCsrfMatcher,只要是请求类型为"GET", “HEAD”, “TRACE”, "OPTIONS"中的一种,就不需要继续处理、
在这里插入图片描述
这里默认的

private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
	private final HashSet<String> allowedMethods = new HashSet<>(
			Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS"));

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * org.springframework.security.web.util.matcher.RequestMatcher#matches(javax.
	 * servlet.http.HttpServletRequest)
	 */
	@Override
	public boolean matches(HttpServletRequest request) {
		return !this.allowedMethods.contains(request.getMethod());
	}
}

如果是其他类型的请求呢?则会比较从Cookie中获取的token值和从请求信息中获取的_csrf值进行比较。如果Cookie当中不包含有Token,则抛出MissingCsrfTokenException异常(Could not verify the provided CSRF token because your session was not found.),如果两个token值不相等,则抛出InvalidCsrfTokenException异常(Invalid CSRF Token ... was found on the request parameter ...)。

  • LogoutFilter

用于匹配登出请求,如果当前路径为logout,则获取认证用户信息,然后执行登出操作。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
		throws IOException, ServletException {
	HttpServletRequest request = (HttpServletRequest) req;
	HttpServletResponse response = (HttpServletResponse) res;

	if (requiresLogout(request, response)) {
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();

		if (logger.isDebugEnabled()) {
			logger.debug("Logging out user '" + auth
					+ "' and transferring to logout destination");
		}

		this.handler.logout(request, response, auth);

		logoutSuccessHandler.onLogoutSuccess(request, response, auth);

		return;
	}

	chain.doFilter(request, response);
}
  • UsernamePasswordAuthenticationFilter

调用requiresAuthentication方法判断当前的请求是否需要被认证以及是否需要被当前过滤器进行处理。如果是一个认证请求,那么会调用attemptAuthentication方法执行认证。有三种可能的结果会出现:

  1. 返回一个 Authentication 对象。 将调用配置的 SessionAuthenticationStrategy(以处理任何与会话相关的行为,例如创建新会话以防止会话固定攻击),然后调用 successAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, Authentication) 方法
  2. 身份验证期间发生 AuthenticationException。 将调用 unsuccessfulAuthentication 方法
  3. 返回Null,表示认证过程未完成。 然后该方法将立即返回,假设子类已完成任何必要的工作(例如重定向)以继续身份验证过程。 假设是此方法将接收稍后的请求,其中返回的 Authentication 对象不为空。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
		throws IOException, ServletException {

	HttpServletRequest request = (HttpServletRequest) req;
	HttpServletResponse response = (HttpServletResponse) res;

	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);
	}
	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);
}
  • DefaultLoginPageGeneratingFilter

如果有登录需求的请求(登录请求、登录失败、登出成功),则创建一个登录页面,然后写入反应流。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
	throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;

boolean loginError = isErrorPage(request);
boolean logoutSuccess = isLogoutSuccess(request);
if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
	String loginPageHtml = generateLoginPageHtml(request, loginError,
			logoutSuccess);
	response.setContentType("text/html;charset=UTF-8");
	response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
	response.getWriter().write(loginPageHtml);

	return;
}

chain.doFilter(request, response);
}

这个创建登录页面的方法也挺有意思,直接代码写死的。

private String generateLoginPageHtml(HttpServletRequest request, boolean loginError,
		boolean logoutSuccess) {
	String errorMsg = "Invalid credentials";

	if (loginError) {
		HttpSession session = request.getSession(false);

		if (session != null) {
			AuthenticationException ex = (AuthenticationException) session
					.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
			errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";
		}
	}

	StringBuilder sb = new StringBuilder();

	sb.append("<!DOCTYPE html>\n"
			+ "<html lang=\"en\">\n"
			+ "  <head>\n"
			+ "    <meta charset=\"utf-8\">\n"
			+ "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
			+ "    <meta name=\"description\" content=\"\">\n"
			+ "    <meta name=\"author\" content=\"\">\n"
			+ "    <title>Please sign in</title>\n"
			+ "    <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
			+ "    <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
			+ "  </head>\n"
			+ "  <body>\n"
			+ "     <div class=\"container\">\n");

	String contextPath = request.getContextPath();
	if (this.formLoginEnabled) {
		sb.append("      <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.authenticationUrl + "\">\n"
				+ "        <h2 class=\"form-signin-heading\">Please sign in</h2>\n"
				+ createError(loginError, errorMsg)
				+ createLogoutSuccess(logoutSuccess)
				+ "        <p>\n"
				+ "          <label for=\"username\" class=\"sr-only\">Username</label>\n"
				+ "          <input type=\"text\" id=\"username\" name=\"" + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
				+ "        </p>\n"
				+ "        <p>\n"
				+ "          <label for=\"password\" class=\"sr-only\">Password</label>\n"
				+ "          <input type=\"password\" id=\"password\" name=\"" + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n"
				+ "        </p>\n"
				+ createRememberMe(this.rememberMeParameter)
				+ renderHiddenInputs(request)
				+ "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
				+ "      </form>\n");
	}

	if (openIdEnabled) {
		sb.append("      <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.openIDauthenticationUrl + "\">\n"
				+ "        <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n"
				+ createError(loginError, errorMsg)
				+ createLogoutSuccess(logoutSuccess)
				+ "        <p>\n"
				+ "          <label for=\"username\" class=\"sr-only\">Identity</label>\n"
				+ "          <input type=\"text\" id=\"username\" name=\"" + this.openIDusernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n"
				+ "        </p>\n"
				+ createRememberMe(this.openIDrememberMeParameter)
				+ renderHiddenInputs(request)
				+ "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n"
				+ "      </form>\n");
	}

	if (oauth2LoginEnabled) {
		sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h2>");
		sb.append(createError(loginError, errorMsg));
		sb.append(createLogoutSuccess(logoutSuccess));
		sb.append("<table class=\"table table-striped\">\n");
		for (Map.Entry<String, String> clientAuthenticationUrlToClientName : oauth2AuthenticationUrlToClientName.entrySet()) {
			sb.append(" <tr><td>");
			String url = clientAuthenticationUrlToClientName.getKey();
			sb.append("<a href=\"").append(contextPath).append(url).append("\">");
			String clientName = HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue());
			sb.append(clientName);
			sb.append("</a>");
			sb.append("</td></tr>\n");
		}
		sb.append("</table>\n");
	}
	sb.append("</div>\n");
	sb.append("</body></html>");

	return sb.toString();
}
  • DefaultLogoutPageGeneratingFilter

如果当前请求时logout,生成一个登出页面

@Override
protected void doFilterInternal(HttpServletRequest request,
		HttpServletResponse response, FilterChain filterChain)
		throws ServletException, IOException {
	if (this.matcher.matches(request)) {
		renderLogout(request, response);
	} else {
		filterChain.doFilter(request, response);
	}
}

渲染登出页面和上面登录页面差不多,都是直接java代码写一个页面

private void renderLogout(HttpServletRequest request, HttpServletResponse response)
		throws IOException {
	String page =  "<!DOCTYPE html>\n"
			+ "<html lang=\"en\">\n"
			+ "  <head>\n"
			+ "    <meta charset=\"utf-8\">\n"
			+ "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
			+ "    <meta name=\"description\" content=\"\">\n"
			+ "    <meta name=\"author\" content=\"\">\n"
			+ "    <title>Confirm Log Out?</title>\n"
			+ "    <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
			+ "    <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
			+ "  </head>\n"
			+ "  <body>\n"
			+ "     <div class=\"container\">\n"
			+ "      <form class=\"form-signin\" method=\"post\" action=\"" + request.getContextPath() + "/logout\">\n"
			+ "        <h2 class=\"form-signin-heading\">Are you sure you want to log out?</h2>\n"
			+ renderHiddenInputs(request)
			+ "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Log Out</button>\n"
			+ "      </form>\n"
			+ "    </div>\n"
			+ "  </body>\n"
			+ "</html>";

	response.setContentType("text/html;charset=UTF-8");
	response.getWriter().write(page);
}
  • RequestCacheAwareFilter

如果一个请求被缓存并且与当前请求匹配,则负责重新构造保存的请求。
它将在配置的 RequestCache 上调用 getMatchingRequest。 如果该方法返回一个值(已保存请求的包装器),它将将此值传递给过滤器链的 doFilter 方法。 如果缓存返回null,则使用原始请求,过滤器不起作用。

public void doFilter(ServletRequest request, ServletResponse response,
		FilterChain chain) throws IOException, ServletException {

	HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
			(HttpServletRequest) request, (HttpServletResponse) response);

	chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
			response);
}

默认情况下在,这里的缓存使用的是session.

public SavedRequest getRequest(HttpServletRequest currentRequest,
		HttpServletResponse response) {
	HttpSession session = currentRequest.getSession(false);

	if (session != null) {
		return (SavedRequest) session.getAttribute(this.sessionAttrName);
	}

	return null;
}
  • FilterSecurityInterceptor
/**
 * Method that is actually called by the filter chain. Simply delegates to the
 * {@link #invoke(FilterInvocation)} method.
 *
 * @param request the servlet request
 * @param response the servlet response
 * @param chain the filter chain
 *
 * @throws IOException if the filter chain fails
 * @throws ServletException if the filter chain fails
 */
public void doFilter(ServletRequest request, ServletResponse response,
		FilterChain chain) throws IOException, ServletException {
	FilterInvocation fi = new FilterInvocation(request, response, chain);
	invoke(fi);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lang20150928

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

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

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

打赏作者

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

抵扣说明:

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

余额充值