栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

权限框架之Spring Security 认证源码分析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

权限框架之Spring Security 认证源码分析

文章目录
  • Spring Security 源码分析
    • 一 基本知识
      • 1.1 核心概念
      • 1.2 基本原理
    • 二 源码分析
      • 2.1 登录验证
        • 2.1.1 基本流程
        • 2.1.2 验证过滤器
        • 2.1.3 尝试认证
        • 2.1.4 封装密码认证令牌
        • 2.1.5 认证与校验
        • 2.1.6 认证失败与成功

Spring Security 源码分析 一 基本知识 1.1 核心概念
  • AuthenticationManager, 用户认证的管理类,所有的认证请求(比如login)都会通过提交一个token给AuthenticationManager的authenticate()方法来实现。当然事情肯定不是它来做,具体校验动作会由AuthenticationManager将请求转发给具体的实现类来做。根据实现反馈的结果再调用具体的Handler来给用户以反馈。这个类基本等同于shiro的SecurityManager。
  • AuthenticationProvider, 认证的具体实现类,一个provider是一种认证方式的实现,比如提交的用户名密码我是通过和DB中查出的user记录做比对实现的,那就有一个DaoProvider;如果我是通过CAS请求单点登录系统实现,那就有一个CASProvider。这个是不是和shiro的Realm的定义很像?基本上你可以帮他们当成同一个东西。按照Spring一贯的作风,主流的认证方式它都已经提供了默认实现,比如DAO、LDAP、CAS、OAuth2等。
    前面讲了AuthenticationManager只是一个代理接口,真正的认证就是由AuthenticationProvider来做的。一个AuthenticationManager可以包含多个Provider,每个provider通过实现一个support方法来表示自己支持那种Token的认证。AuthenticationManager默认的实现类是ProviderManager。
  • UserDetailService, 用户认证通过Provider来做,所以Provider需要拿到系统已经保存的认证信息,获取用户信息的接口spring-security抽象成UserDetailService。虽然叫Service,但是我更愿意把它认为是我们系统里经常有的UserDao。
  • AuthenticationToken, 所有提交给AuthenticationManager的认证请求都会被封装成一个Token的实现,比如最容易理解的UsernamePasswordAuthenticationToken。这个就不多讲了,连名字都跟Shiro中一样。
  • SecurityContext,当用户通过认证之后,就会为这个用户生成一个唯一的SecurityContext,里面包含用户的认证信息Authentication。通过SecurityContext我们可以获取到用户的标识Principle和授权信息GrantedAuthrity。在系统的任何地方只要通过SecurityHolder.getSecruityContext()就可以获取到SecurityContext。在Shiro中通过SecurityUtils.getSubject()到达同样的目的。
1.2 基本原理

二 源码分析 2.1 登录验证 2.1.1 基本流程
  • AbstractAuthenticationProcessingFilter
  • 基于浏览器的基于 HTTP 的身份验证请求的抽象处理器。
  • 认证流程,过滤器要求您设置authenticationManager属性。 需要AuthenticationManager来处理由实现类创建的身份验证请求令牌。
  • 如果请求与setRequiresAuthenticationRequestMatcher(RequestMatcher)匹配,此过滤器将拦截请求并尝试从该请求执行身份验证。
  • 身份验证由attemptAuthentication方法执行,该方法必须由子类实现。
  • 认证成功,如果身份验证成功,则生成的Authentication对象将被放入当前线程的SecurityContext ,可供全局使用,该线程保证已由较早的过滤器创建。
  • 然后将调用配置的AuthenticationSuccessHandler以在成功登录后重定向到适当的目的地。
  • 默认行为在SavedRequestAwareAuthenticationSuccessHandler中实现,它将利用ExceptionTranslationFilter设置的任何DefaultSavedRequest并将用户重定向到其中包含的 URL。
  • 验证失败,如果身份验证失败,它将委托给配置的AuthenticationFailureHandler以允许将失败信息传达给客户端。 默认实现是SimpleUrlAuthenticationFailureHandler,它向客户端发送 401 错误代码。 也可以使用失败 URL 进行配置作为替代。 同样,您可以在此处注入所需的任何行为。
2.1.2 验证过滤器

AbstractAuthenticationProcessingFilter

  • 当发起HTTP请求进行拦截
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		//HTTP请求,与响应
		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;
			}
			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) {
            //认证失败
			unsuccessfulAuthentication(request, response, failed);

			return;
		}

		// 认证成功,放行请求,并返回认证之后的结果
		if (continueChainBeforeSuccessfulAuthentication) {
			chain.doFilter(request, response);
		}

		successfulAuthentication(request, response, chain, authResult);
	}
2.1.3 尝试认证
//这是一个接口,我们查看他的实现类,当然我们也可以自定义实现类,来重写里面的认证方法
public abstract Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException, IOException,
			ServletException;		

UsernamePasswordAuthenticationFilter

public class UsernamePasswordAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {
 //默认匹配的URL路径,与提交方式,当然我们也可以更改其默认路径,			formLogin().loginProcessingUrl(“XXXX”)			
  public UsernamePasswordAuthenticationFilter() {
		super(new AntPathRequestMatcher("/login", "POST"));
	}  
    
    //尝试认证
    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());
		}
		//前端传递的信息
		String username = obtainUsername(request);
		String password = obtainPassword(request);

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

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

		username = username.trim();
		//将前端传递的信息,封装成用户名密码认证令牌。方便AuthenticationManager,调用其他方法进行认证处理
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);

		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		//获取身份验证器,进行验证
		return this.getAuthenticationManager().authenticate(authRequest);
	}
    
		}
2.1.4 封装密码认证令牌

UsernamePasswordAuthenticationToken

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {

    
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
		super(null);
		this.principal = principal;
		this.credentials = credentials;
		setAuthenticated(false);
	}
    
     
    public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
			Collection authorities) {
		super(authorities);
		this.principal = principal;
		this.credentials = credentials;
		super.setAuthenticated(true); // must use super, as we override
	}
}
  • 获取身份验证器,进行验证

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
		InitializingBean {
			//进行认证
            public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
                //遍历身份验证程序
                for (AuthenticationProvider provider : getProviders()) {
                //是否支持    
			if (!provider.supports(toTest)) {
				continue;
			}

			if (debug) {
				logger.debug("Authentication attempt using "
						+ provider.getClass().getName());
			}

            //支持进行验证,并返回验证结果        
			try {
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException e) {
				prepareException(e, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw e;
			} catch (AuthenticationException e) {
				lastException = e;
			}
		}
                
            }
		
		}

2.1.5 认证与校验

AbstractUserDetailsAuthenticationProvider

  • 一个基本的AuthenticationProvider ,它允许子类覆盖和使用UserDetails对象。
  • 该类旨在响应UsernamePasswordAuthenticationToken身份验证请求。
  • 验证成功后,将创建UsernamePasswordAuthenticationToken并将其返回给调用者。
  • 缓存是通过存储放置在UserCache的UserDetails对象来处理的。 这确保可以验证具有相同用户名的后续请求,而无需查询UserDetailsService 。
public abstract class AbstractUserDetailsAuthenticationProvider implements
		AuthenticationProvider, InitializingBean, MessageSourceAware {
		
    	//验证
		public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {

		// Determine username
		String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
				: authentication.getName();

		boolean cacheWasUsed = true;
		UserDetails user = this.userCache.getUserFromCache(username);
		try {
            //检查用户情况,账户锁定,账户过期,凭证过期
			preAuthenticationChecks.check(user);
            //账号,密码检查
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);
		}
	
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}

		Object principalToReturn = user;

		if (forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
		//返回创建成功凭证
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}
		}
	protected Authentication createSuccessAuthentication(Object principal,
			Authentication authentication, UserDetails user) {
		// Ensure we return the original credentials the user supplied,
		// so subsequent attempts are successful even with encoded passwords.
		// Also ensure we return the original getDetails(), so that future
		// authentication events after cache expiry contain the details
		UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
				principal, authentication.getCredentials(),
				authoritiesMapper.mapAuthorities(user.getAuthorities()));
		result.setDetails(authentication.getDetails());
		//返回认证时候的结果
		return result;
	}

DaoAuthenticationProvider

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
//密码
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
 //用户信息,数据库中
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
    
//认证检查,注意这个方法是废弃了
protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		if (authentication.getCredentials() == null) {
			logger.debug("Authentication failed: no credentials provided");

			throw new BadCredentialsException(messages.getMessage(
					"AbstractUserDetailsAuthenticationProvider.badCredentials",
					"Bad credentials"));
		}
		//前端传递的密码
		String presentedPassword = authentication.getCredentials().toString();
		//数据库是中的密码,与前端传递的密码进行比较
		if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
			logger.debug("Authentication failed: password does not match stored value");

			throw new BadCredentialsException(messages.getMessage(
					"AbstractUserDetailsAuthenticationProvider.badCredentials",
					"Bad credentials"));
		}
	}
}
2.1.6 认证失败与成功

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

//认证失败,当然我们也可以重写失败的提示
protected void unsuccessfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException failed)
			throws IOException, ServletException {
		SecurityContextHolder.clearContext();

		if (logger.isDebugEnabled()) {
			logger.debug("Authentication request failed: " + failed.toString(), failed);
			logger.debug("Updated SecurityContextHolder to contain null Authentication");
			logger.debug("Delegating to authentication failure handler " + failureHandler);
		}

		rememberMeServices.loginFail(request, response);

		failureHandler.onAuthenticationFailure(request, response, failed);
	}


//认证成功,当然我们也可以重写
protected void successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, Authentication authResult)
			throws IOException, ServletException {

		if (logger.isDebugEnabled()) {
			logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
					+ authResult);
		}
		//存入上下文
		SecurityContextHolder.getContext().setAuthentication(authResult);

		rememberMeServices.loginSuccess(request, response, authResult);

		// Fire event
		if (this.eventPublisher != null) {
			eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
					authResult, this.getClass()));
		}

		successHandler.onAuthenticationSuccess(request, response, authResult);
	}

  • 以上就是认证的源码分析流程
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/434181.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号