• SpringSecurity - 启动流程分析(五)



    活动地址:CSDN21天学习挑战赛

    在上篇文章 SpringSecurity - 启动流程分析(四) 中,我们大致了解了 SpringSecurity 的默认过滤器,这篇文章就主要来看一下其中一个比较重要的 FilterUsernamePasswordAuthenticationFilter

    public class UsernamePasswordAuthenticationFilter extends
    		AbstractAuthenticationProcessingFilter {
    		
    	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;
    	
    	public UsernamePasswordAuthenticationFilter() {
    		super(new AntPathRequestMatcher("/login", "POST"));
    	}
    
    	public Authentication attemptAuthentication(HttpServletRequest request,
    			HttpServletResponse response) throws AuthenticationException {
    		// 核心流程
    		...
    	}
    
    	@Nullable
    	protected String obtainPassword(HttpServletRequest request) {
    		return request.getParameter(passwordParameter);
    	}
    
    	@Nullable
    	protected String obtainUsername(HttpServletRequest request) {
    		return request.getParameter(usernameParameter);
    	}
    
    	protected void setDetails(HttpServletRequest request,
    			UsernamePasswordAuthenticationToken authRequest) {
    		authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    	}
    
    	public void setUsernameParameter(String usernameParameter) {
    		Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
    		this.usernameParameter = usernameParameter;
    	}
    	
    	public void setPasswordParameter(String passwordParameter) {
    		Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
    		this.passwordParameter = passwordParameter;
    	}
    
    	public void setPostOnly(boolean postOnly) {
    		this.postOnly = postOnly;
    	}
    
    	public final String getUsernameParameter() {
    		return usernameParameter;
    	}
    
    	public final String getPasswordParameter() {
    		return passwordParameter;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    从源码中可以看到:

    • 默认情况下会拦截 POST 类型的 /login 请求
    • 允许自定义用户名密码参数和请求类型是否只能是 POST

    我们看一下核心方法:doFilter(),这个方法定义在父类 AbstractAuthenticationProcessingFilter 中:

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    这个方法调用了子类 UsernamePasswordAuthenticationFilter 中的 attemptAuthentication() 方法,doFilter() 方法;成功会调用 sessionStrategy.onAuthentication() 方法处理 session,然后再调用 successfulAuthentication() 执行认证成功后的逻辑;失败会调用 unsuccessfulAuthentication() 方法,处理失败后的逻辑。

    我们看一下 attemptAuthentication() 方法:

    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 authRequest = new UsernamePasswordAuthenticationToken(
    			username, password);
    
    	// Allow subclasses to set the "details" property
    	setDetails(request, authRequest);
    	// 核心流程
    	return this.getAuthenticationManager().authenticate(authRequest);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    这个方法的核心流程是调用 AuthenticationManagerauthenticate() 方法进行认证:

    	/**
    	 * Attempts to authenticate the passed {@link Authentication} object, returning a
    	 * fully populated Authentication object (including granted authorities)
    	 * if successful.
    	 * 

    * An AuthenticationManager must honour the following contract concerning * exceptions: *

      *
    • A {@link DisabledException} must be thrown if an account is disabled and the * AuthenticationManager can test for this state.
    • *
    • A {@link LockedException} must be thrown if an account is locked and the * AuthenticationManager can test for account locking.
    • *
    • A {@link BadCredentialsException} must be thrown if incorrect credentials are * presented. Whilst the above exceptions are optional, an * AuthenticationManager must always test credentials.
    • *
    * Exceptions should be tested for and if applicable thrown in the order expressed * above (i.e. if an account is disabled or locked, the authentication request is * immediately rejected and the credentials testing process is not performed). This * prevents credentials being tested against disabled or locked accounts. * * @param authentication the authentication request object * * @return a fully authenticated object including credentials * * @throws AuthenticationException if authentication fails */
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    认证成功返回一个 Authentication 类型的变量,失败抛出 AuthenticationException 异常。可知具体的认证逻辑是交给了 AuthenticationManager 来处理。

  • 相关阅读:
    【Linux】操作系统之冯诺依曼体系
    把ipa文件上传到App Store教程步骤
    Day01——瑞吉外卖
    【数据库专家揭秘】MySql数据库设计黄金法则,让你的数据更稳定、更高效!
    nginx之keepalive详解与其配置
    item_search - 按关键字搜索EBAY商品
    TypeScript 学习笔记
    百度地图在vue中的使用
    优卡特脸爱云一脸通智慧管理平台权限绕过漏洞复现(CVE-2023-6099)
    c# --- 数据存储再识 与数组创建 与字符串
  • 原文地址:https://blog.csdn.net/qiaohao0206/article/details/126338050