spring-security 学习笔记一 --- 基于默认配置

1.前言

本文主要讲解 spring-security 在不做任何配置情况下,它的启动流程和认证过程。

1. 准备工作

这里是基于springboot 2.2.5版本对应 spring-security 5.2.2版本演示的 (按我下面导入即可,版本是它自己匹配的)

  1. 引入依赖

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.2.5.RELEASE</spring-boot.version>
    </properties>
    
    <dependency>
      <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-security</artifactId>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
    
  2. 编写程序

  • 这里我们直接创建一个springboot 启动程序即可,可以不做任何配置,在导入spring-security 依赖后,它会自动给我们进行了一些默认配置。我在这里简单配置了一下端口号

    # 应用名称
    spring.application.name=spring-security
    # 应用服务 WEB 访问端口
    server.port=8082
    
  • 编写一个controller

    @RestController
    public class HelloController {
        @GetMapping("/hello")
        public String hello() {
            return "hello";
        }
    }
    
  1. 启动项目
    在这里插入图片描述

2. 访问 /hello

在这里插入图片描述我们访问 http://localhost:8082/hello , 发现它会给我们重定向到 http://localhost:8082/login 这个页面。

-思考:我们只写了一个处理 /hello 的方法

2. 流程分析

  • 我们都知道spring-security 主要是基于 一层层的 Filters 来对web 请求做处理的,完成其中的认证授权等一系列功能。

1. 自定义一个Filter

@Component
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("进入自定义的filter");
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

我们自定义的过滤器逻辑很简单,只做一个简单的输出 (在这个地方打个断点,方便我们后续调试),然后放行,把它添加到容器中

@Configuration
public class MyConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        // 这个方法表示把我们自定义的过滤器 加在 UsernamePasswordAuthenticationFilter 这个过滤器之前
        http.addFilterBefore(new MyFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

2. debug模式重启项目,访问 http://localhost:8082/login ,随便输入参数,提交表单

在这里插入图片描述

  • 我们点开filterChain(过滤器链,主要是用来管理过滤器的),我们发现除掉我们自定义的过滤器,它自己给我们添加了十五个 过滤器

至于为什么是这个顺序?

FilterComparator() {
        FilterComparator.Step order = new FilterComparator.Step(100, 100);
        this.put(ChannelProcessingFilter.class, order.next());
        this.put(ConcurrentSessionFilter.class, order.next());
        this.put(WebAsyncManagerIntegrationFilter.class, order.next());
        this.put(SecurityContextPersistenceFilter.class, order.next());
        this.put(HeaderWriterFilter.class, order.next());
        this.put(CorsFilter.class, order.next());
        this.put(CsrfFilter.class, order.next());
        this.put(LogoutFilter.class, order.next());
        this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter", order.next());
        this.filterToOrder.put("org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationRequestFilter", order.next());
        this.put(X509AuthenticationFilter.class, order.next());
        this.put(AbstractPreAuthenticatedProcessingFilter.class, order.next());
        this.filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order.next());
        this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter", order.next());
        this.filterToOrder.put("org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter", order.next());
        this.put(UsernamePasswordAuthenticationFilter.class, order.next());
        this.put(ConcurrentSessionFilter.class, order.next());
        this.filterToOrder.put("org.springframework.security.openid.OpenIDAuthenticationFilter", order.next());
        this.put(DefaultLoginPageGeneratingFilter.class, order.next());
        this.put(DefaultLogoutPageGeneratingFilter.class, order.next());
        this.put(ConcurrentSessionFilter.class, order.next());
        this.put(DigestAuthenticationFilter.class, order.next());
        this.filterToOrder.put("org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter", order.next());
        this.put(BasicAuthenticationFilter.class, order.next());
        this.put(RequestCacheAwareFilter.class, order.next());
        this.put(SecurityContextHolderAwareRequestFilter.class, order.next());
        this.put(JaasApiIntegrationFilter.class, order.next());
        this.put(RememberMeAuthenticationFilter.class, order.next());
        this.put(AnonymousAuthenticationFilter.class, order.next());
        this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter", order.next());
        this.put(SessionManagementFilter.class, order.next());
        this.put(ExceptionTranslationFilter.class, order.next());
        this.put(FilterSecurityInterceptor.class, order.next());
        this.put(SwitchUserFilter.class, order.next());
    }

3. WebAsyncManagerIntegrationFilte

  • GenericFilterBean 子类,将Security上下文与Spring Web中用于处理异步请求映射的 WebAsyncManager(spring 中用于管理异步请求的核心类) 进行集成。

4. SecurityContextPersistenceFilter

  • GenericFilterBean 子类,在每次请求处理之前将该请求相关的安全上下文信息加载到SecurityContextHolder中,然后在该次请求处理完成之后,将SecurityContextHolder中关于这次请求的信息存储到一个仓储中,然后将SecurityContextHolder中的信息清除
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		
		// FILTER_APPLIED : 如果有值,说明当前请求已经被这个 Filter 拦截过,直接放行
		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();
		// 给 FILTER_APPLIED 设置一个值,表示当前请求已被 当前Filter 处理过 
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

		// private boolean forceEagerSessionCreation = false;
		// 默认是false ,  日志记录一下 早起创建的session
		if (forceEagerSessionCreation) {
			HttpSession session = request.getSession();

			if (debug && session.isNew()) {
				logger.debug("Eagerly created session: " + session.getId());
			}
		}
		
		// 创建一个 HttpRequestResponseHolder 
		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
				response);
		// private SecurityContextRepository repo; 默认实现是 : HttpSessionSecurityContextRepository
		// 初次进入的时候,它会先尝试从session 中获取,第一次获取不到,它会创建一个默认的没有权限认证的  SecurityContext
		// 认证通过后 它会保存在sesion 中,下次进入可以直接取出来
		SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

		try {
			// 把从对应的 SecurityContextRepository  获取的securityContext存入SecurityContextHolder中 
			SecurityContextHolder.setContext(contextBeforeChainExecution);
			// 放行
			chain.doFilter(holder.getRequest(), holder.getResponse());

		}
		// 这里代码 是当当前请求被所有过滤器处理完毕后,才会执行的
		finally {
			// 从 SecurityContextHolder 中获取 所有Filter执行完毕后的 SecurityContext 
			SecurityContext contextAfterChainExecution = SecurityContextHolder
					.getContext();
			// Crucial removal of SecurityContextHolder contents - do this before anything
			// else.
			// 清除 SecurityContextHolder 中的 SecurityContext
			SecurityContextHolder.clearContext();
			// 把 前面获取的 SecurityContext  保存到 SecurityContextRepository repo
			repo.saveContext(contextAfterChainExecution, holder.getRequest(),
					holder.getResponse());
			// 清除掉这个标记,下次进入还会拦截		
			request.removeAttribute(FILTER_APPLIED);

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

5. HeaderWriterFilter

GenericFilterBean 子类,主要是处理请求头信息的

@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		// shouldWriteHeadersEagerly : 指示是否需要在请求开始前,写入请求头数据
		if (this.shouldWriteHeadersEagerly) {
		// 如果需要的话 就写入请求头放行
			doHeadersBefore(request, response, filterChain);
		} else {
		// 如果不需要,它会把请求包装一下,等所有过滤器链执行完毕后,在写入请求头信息
			doHeadersAfter(request, response, filterChain);
		}
	}

	private void doHeadersBefore(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
		
		writeHeaders(request, response);
		filterChain.doFilter(request, response);
	}

	private void doHeadersAfter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
		HeaderWriterResponse headerWriterResponse = new HeaderWriterResponse(request,
				response);
		HeaderWriterRequest headerWriterRequest = new HeaderWriterRequest(request,
				headerWriterResponse);
		try {
			filterChain.doFilter(headerWriterRequest, headerWriterResponse);
		} finally {
			headerWriterResponse.writeHeaders();
		}
	}

6. CsrfFilter

GenericFilterBean 子类,用于处理跨站请求伪造

protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
					throws ServletException, IOException {
		request.setAttribute(HttpServletResponse.class.getName(), response);
		// 从token 仓库中查看这个请求是否携带有 csrfToken 
		CsrfToken csrfToken = this.tokenRepository.loadToken(request);
		final boolean missingToken = csrfToken == null;
		// 如果没有的话,就给这个 request 创建一个默认的token,uuid随机生成的
		if (missingToken) {
			csrfToken = this.tokenRepository.generateToken(request);
			this.tokenRepository.saveToken(csrfToken, request, response);
		}
		// 把token 信息设置到 请求中
		request.setAttribute(CsrfToken.class.getName(), csrfToken);
		request.setAttribute(csrfToken.getParameterName(), csrfToken);

		// 决定策略实现的规则是否与提供的请求匹配。 如果匹配直接放行
		if (!this.requireCsrfProtectionMatcher.matches(request)) {
			filterChain.doFilter(request, response);
			return;
		}
		// 根据前面设置的csrfToken ,从请求头中获取名为csrfToken.getHeaderName()的属性值 默认"X-CSRF-TOKEN"
		String actualToken = request.getHeader(csrfToken.getHeaderName());
		// 如果为null的话 就会从请求参数中获取
		if (actualToken == null) {
			actualToken = request.getParameter(csrfToken.getParameterName());
		}
		// 这里会拿 actualToken  和 前面创建的默认的 csrfToken的token 进行比对,如果不相等,说明这里被修改过 打印日志,回显异常
		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);
	}

7. LogoutFilter

主要是用来处理登出的。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
		// 确认是否进行登出操作,里面主要是一些路径方法匹配
		if (requiresLogout(request, response)) {
			// SecurityContextHolder的容器中的一些权限认证信息
			Authentication auth = SecurityContextHolder.getContext().getAuthentication();

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

			// 这个LogoutHandler是一个复合handler 里面维护了一个  List<LogoutHandler> 
			// 遍历执行每一个 LogoutHandler 的登出操作,处理各种认证信息
			this.handler.logout(request, response, auth);

			// 默认情况下是一个 SimpleUrlLogoutSuccessHandler,处理重定向信息
			logoutSuccessHandler.onLogoutSuccess(request, response, auth);

			return;
		}
		// 如果没有登出操作直接放行。
		chain.doFilter(request, response);
	}

8. UsernamePasswordAuthenticationFilter

这个应该是我们大家比较了解,也是重写比较多的一个 Filter,用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理post方式“/login”的请求。
从表单中获取用户名和密码时,默认使用的表单name值为“username”和“password”,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改。


	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;

public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		// 校验它是 post 方式
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}
		// 从request 中获取 参数名为 username,和 password 对应的参数
		String username = obtainUsername(request);
		String password = obtainPassword(request);

		// 当它们是null时,给他们一个 ""字符串
		if (username == null) {
			username = "";
		}

		if (password == null) {
			password = "";
		}

		username = username.trim();
		// 创建一个 UsernamePasswordAuthenticationToken 这里会保存用户的权限认证等信息
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		// 默认会得到 一个 ProviderManager,调用它的 authenticate(),因为我们没配置他第一次进入是会 
		// 从 InMemoryUserDetailsManager的一个 map中根据用户名查找用户信息,用户名:user,密码:前面启动项目打印的那个
		// 拿着这个查找后的用户信息,和我们输入的密码进行比对,相同则认证成功,否则失败
		return this.getAuthenticationManager().authenticate(authRequest);
	}

this.getAuthenticationManager()
-----》ProviderManager
----》result = provider.authenticate(authentication)
-----》 AbstractUserDetailsAuthenticationProvider
----》UserDetails user = this.userCache.getUserFromCache(username); 先从缓存中判断没有再往下执行
----》 user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
—》DaoAuthenticationProvider
—》UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
—》InMemoryUserDetailsManager
—》UserDetails user = (UserDetails)this.users.get(username.toLowerCase()); 这个users是一个map,通过username 从这里面获取 用户信息
// private final Map<String, MutableUserDetails> users = new HashMap();
—》接着回到 AbstractUserDetailsAuthenticationProvider
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); 检查当前用户密码和从map中查到的信息是否匹配

9. 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;
		}

10 DefaultLogoutPageGeneratingFilter

当我们没有配置退出登录页面时,生成一个的用户退出登录页面,默认情况下,当用户请求为GET /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);
		}
	}

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

11 BasicAuthenticationFilter

这个拦截器主要是处理请求头上认证信息,如果有,则通过basic64 编码器解密请求头所携带的信息,
封装成一个 UsernamePasswordAuthenticationToken 对象,然后比对认证是否成功,认证成功的话把认证成功的信息放到当前上下文中。

protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain)
					throws IOException, ServletException {
		final boolean debug = this.logger.isDebugEnabled();
		try {
		// 尝试获取请求头封装的信息返回的 UsernamePasswordAuthenticationToken ,若为null,直接放行,详解见下面
			UsernamePasswordAuthenticationToken authRequest = authenticationConverter.convert(request);
			// 如果没有直接返回
			if (authRequest == null) {
				chain.doFilter(request, response);
				return;
			}
			// 获取用户名
			String username = authRequest.getName();

			if (debug) {
				this.logger
						.debug("Basic Authentication Authorization header found for user '"
								+ username + "'");
			}
			
			// 仅当用户名与SecurityContextHolder和用户不匹配时或者用户名没经过认证 返回true 需进行验证
			if (authenticationIsRequired(username)) {
				// 具体的校验逻辑 可自己重写,认证成功后把它放到容器中即可。
				Authentication authResult = this.authenticationManager
						.authenticate(authRequest);

				if (debug) {
					this.logger.debug("Authentication success: " + authResult);
				}

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


public UsernamePasswordAuthenticationToken convert(HttpServletRequest request) {
		//  public static final String AUTHORIZATION = "Authorization";
		String header = request.getHeader(AUTHORIZATION);
		// 如果这个 Authorization 请求头为null 直接返回。
		if (header == null) {
			return null;
		}
		//  获取 Authorization Basic 后面的信息
		header = header.trim();
		if (!StringUtils.startsWithIgnoreCase(header, AUTHENTICATION_SCHEME_BASIC)) {
			return null;
		}
		// base64 解密请求头认证信息
		byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
		byte[] decoded;
		try {
			decoded = Base64.getDecoder().decode(base64Token);
		}
		catch (IllegalArgumentException e) {
			throw new BadCredentialsException(
					"Failed to decode basic authentication token");
		}

		// getCredentialsCharset(request) 默认 UTF-8
		String token = new String(decoded, getCredentialsCharset(request));

		int delim = token.indexOf(":");

		if (delim == -1) {
			throw new BadCredentialsException("Invalid basic authentication token");
		}
		// 构造这个对象 UsernamePasswordAuthenticationToken  最后返回
		UsernamePasswordAuthenticationToken result  = new UsernamePasswordAuthenticationToken(token.substring(0, delim), token.substring(delim + 1));
		result.setDetails(this.authenticationDetailsSource.buildDetails(request));
		return result;
	}

12 RequestCacheAwareFilter

主要作用是用于用户登录成功后,重新恢复因为登录被打断的请求,被打断也是有前提条件的,支持打断后可以被恢复的异常有AuthenticationException、AccessDeniedException,这个操作是ExceptionTranslationFilter中触发的,并且RequestCacheAwareFilter只支持GET方法,而默认TokenEndpoint支持Post获取Token信息,进行登录.

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

13 SecurityContextHolderAwareRequestFilter

Spring Security TokenEndpoint中获取token的请求,有这样一个参数:Principal。 对于一个普通HttpServletRequest,是没有Principal参数类型的。SecurityContextHolderAwareRequestFilter通过HttpServletRequestFactory将HttpServletRequest请求包装成SecurityContextHolderAwareRequestWrapper,它实现了HttpServletRequest,并进行了扩展,添加一些额外的方法,比如:getPrincipal()方法等。这样就可以那些需要Principal等参数的Controller就可以接收到对应参数了。除了这个地方的应用,在其他地方,也可以直接调用request#getUserPrincipal()获取对应信息。

14 AnonymousAuthenticationFilter

相关推荐

  1. Spring Security)架构概览

    2024-04-30 09:24:03       31 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-30 09:24:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-30 09:24:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-30 09:24:03       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-30 09:24:03       20 阅读

热门阅读

  1. AnolisOS8.8基于yum安装mariadb并配置远程访问

    2024-04-30 09:24:03       10 阅读
  2. js执行顺序

    2024-04-30 09:24:03       10 阅读
  3. Visual Studio Installer 运行python 汉字

    2024-04-30 09:24:03       10 阅读
  4. 使用WSGI服务器在生产环境中运行Flask应用程序

    2024-04-30 09:24:03       10 阅读
  5. Jenkins下拉取gitlab的branches和tags的字段说明

    2024-04-30 09:24:03       12 阅读