spring-security安全框架解读

1、导入依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2、springsecurity的过滤器链

Alt
对于以上的每个过滤器的作用,可参看spring security的官方文档spring security的官方文档进行学习。

3、用户认证流程

3.1 AbstractAuthenticationProcessingFilter过滤器

package org.sicFilterBean;
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
		implements ApplicationEventPublisherAware, MessageSourceAware {
	......
	//用户认证成功后的结果处理
	private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
	//用户认证失败后的结果处理
	private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
	//对请求路径进行匹配,用户可在配置类中自定义
	protected AbstractAuthenticationProcessingFilter(
			RequestMatcher requiresAuthenticationRequestMatcher) {
		Assert.notNull(requiresAuthenticationRequestMatcher,
				"requiresAuthenticationRequestMatcher cannot be null");
		this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
	}
	//核心代码
	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:该对象为Spring Security方式的认证结果主体类
		Authentication authResult;
		try {
		//最为核心的代码,其认证过程主要是依靠具体的子类实现,包括两部分,认证(常用的用户名+密码,手机号+验证码,邮箱+验证码),授权(用户权限的判定)
			authResult = attemptAuthentication(request, response);
			if (authResult == null) {
				// return immediately as subclass has indicated that it hasn't completed
				// authentication
				return;
			}
			//认证结果放置于session会话中
			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 successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, Authentication authResult)
			throws IOException, ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
					+ authResult);
		}
		//将认证结果放置于SecurityContex及安全的上下文环境中
		SecurityContextHolder.getContext().setAuthentication(authResult);
		//记住我功能
		rememberMeServices.loginSuccess(request, response, authResult);
		// Fire event
		if (this.eventPublisher != null) {
			eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
					authResult, this.getClass()));
		}
		//调用AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler()的成功处理逻辑
		successHandler.onAuthenticationSuccess(request, response, 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);
		//调用AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler()的失败处理逻辑
		failureHandler.onAuthenticationFailure(request, response, failed);
	}
	//使用认证管理器
	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}
	//使用记住我服务	
	public void setRememberMeServices(RememberMeServices rememberMeServices) {
		Assert.notNull(rememberMeServices, "rememberMeServices cannot be null");
		this.rememberMeServices = rememberMeServices;
	}
	//session环境的管理	
	public void setSessionAuthenticationStrategy(
			SessionAuthenticationStrategy sessionStrategy) {
		this.sessionStrategy = sessionStrategy;
	}
	//认证成功的处理者
	public void setAuthenticationSuccessHandler(
			AuthenticationSuccessHandler successHandler) {
		Assert.notNull(successHandler, "successHandler cannot be null");
		this.successHandler = successHandler;
	}
	//认证失败的处理者
	public void setAuthenticationFailureHandler(
			AuthenticationFailureHandler failureHandler) {
		Assert.notNull(failureHandler, "failureHandler cannot be null");
		this.failureHandler = failureHandler;
	}
	
}

AbstractAuthenticationProcessingFilter源码分析:

AbstractAuthenticationProcessingFilter 抽象类中的doFilter方法定义了一个完整的用户认证授权流程:

  • 对请求的地址进行匹配,地址匹配则进行相应的认证权限校验,不匹配直接返回
//进行请求过滤地址的匹配
		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);
			return;
		}
  • 对请求进行认证及授权处理 ,这一步是由对应的子类实现去处理的,默认实现是UsernamePasswordAuthenticationFilter
authResult = attemptAuthentication(request, response);
  • 将认证结果放置于session会话中
//认证结果放置于session会话中
	sessionStrategy.onAuthentication(authResult, request, response);
  • 如果在调用认证授权过程中出现任何异常,则会调用相应的认证失败的业务逻辑,unsuccessfulAuthentication,并且默认调用SimpleUrlAuthenticationFailureHandler接口对象的onAuthenticationFailure方法。
		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;
		}
===================================================================================================================================

//InternalAuthenticationServiceException  异常,属于网络连接的异常信息,主要是用于 OAuth 认证服务器与业务服务器通信异常

//AuthenticationException 主要是指认证权限不通过的异常信息,springsecurity框架已经有部分实现,可通过继承关系进行查看,所以在我们代码过程中我们可以直接使用

===================================================================================================================================
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);
		//调用AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler()的失败处理逻辑
		failureHandler.onAuthenticationFailure(request, response, failed);
	}
===========================================================================================================================
public void onAuthenticationFailure(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException exception)
			throws IOException, ServletException {
		if (defaultFailureUrl == null) {
			logger.debug("No failure URL set, sending 401 Unauthorized error");
			response.sendError(HttpStatus.UNAUTHORIZED.value(),
				HttpStatus.UNAUTHORIZED.getReasonPhrase());
		}
		else {
			saveException(request, exception);
			if (forwardToDestination) {
				logger.debug("Forwarding to " + defaultFailureUrl);
				request.getRequestDispatcher(defaultFailureUrl)
						.forward(request, response);
			}
			else {
				logger.debug("Redirecting to " + defaultFailureUrl);
				redirectStrategy.sendRedirect(request, response, defaultFailureUrl);
			}
		}
	}
  • 如果在调用认证授权成功,则会调用相应的认证成功的业务逻辑,successfulAuthentication*,并且默认调用SavedRequestAwareAuthenticationSuccessHandler接口对象的onAuthenticationSuccess方法,因此用户可以自定义认证成功的处理结果业务逻辑,实现SavedRequestAwareAuthenticationSuccessHandler即可。
//认证成功后的处理逻辑
	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);
		}
		//将认证结果放置于SecurityContex及安全的上下文环境中
		SecurityContextHolder.getContext().setAuthentication(authResult);
		//记住我功能
		rememberMeServices.loginSuccess(request, response, authResult);
		// Fire event
		if (this.eventPublisher != null) {
			eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
					authResult, this.getClass()));
		}
		//调用AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler()的成功处理逻辑
		successHandler.onAuthenticationSuccess(request, response, authResult);
	}
==========================================================================================================================
//将本次的request请求从会话缓存中取出
private RequestCache requestCache = new HttpSessionRequestCache();
public void onAuthenticationSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication authentication)
			throws ServletException, IOException {
		SavedRequest savedRequest = requestCache.getRequest(request, response);
		if (savedRequest == null) {
			super.onAuthenticationSuccess(request, response, authentication);
			return;
		}
		String targetUrlParameter = getTargetUrlParameter();
		if (isAlwaysUseDefaultTargetUrl()
				|| (targetUrlParameter != null && StringUtils.hasText(request
						.getParameter(targetUrlParameter)))) {
			requestCache.removeRequest(request, response);
			super.onAuthenticationSuccess(request, response, authentication);
			return;
		}
		clearAuthenticationAttributes(request);
		// Use the DefaultSavedRequest URL
		String targetUrl = savedRequest.getRedirectUrl();
		logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
		getRedirectStrategy().sendRedirect(request, response, targetUrl);
	}

3.1 UsernamePasswordAuthenticationFilter过滤器(springsecurity认证授权的核心过滤器)

package org.springframework.security.web.authentication;

public class UsernamePasswordAuthenticationFilter extends
		AbstractAuthenticationProcessingFilter {
	// 默认页面表单取值为username和password,
	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;
	// 默认的登录页面请求地址为login,post请求
	public UsernamePasswordAuthenticationFilter() {
		super(new AntPathRequestMatcher("/login", "POST"));
	}
	//核心方法:用户名密码的请求认证
	// ========================================================================================================
	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);
	}
	//从页面参数中获取用户密码
	protected String obtainPassword(HttpServletRequest request) {
		return request.getParameter(passwordParameter);
	}
	//从页面参数中获取用户名
	protected String obtainUsername(HttpServletRequest request) {
		return request.getParameter(usernameParameter);
	}
	//设置用户的token信息
	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;
	}
	//对于http请求参数的自定义
	public void setPostOnly(boolean postOnly) {
		this.postOnly = postOnly;
	}
}

UsernamePasswordAuthenticationFilter源码分析

  • 对于用户认证和权限的校验 ,该核心过滤器给定了请求参数的默认值,如url="/login",用户名和用户密码的取值参数,http的请求方式等,但都给用户自定义的空间。
  • 核心代码:
		username = username.trim();
		//根据用户名和密码,生产权限认证的主要核心类
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);
		// 将本次请求的详细信息,包括用户访问协议、路径以及IP地址等放置在认证主体中供下一级的调用
		setDetails(request, authRequest);
		//真正的权限校验的方法
		return this.getAuthenticationManager().authenticate(authRequest);
  • UsernamePasswordAuthenticationToken的解读
    看一下他的继承体系
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken
public abstract class AbstractAuthenticationToken implements Authentication,CredentialsContainer 

  所以说:Authentication 是一个接口,用来表示用户认证信息的,在用户登录认证之前相关信息会封装为一个 Authentication 具体实现类的对象,在登录认证成功之后又会生成一个信息更全面,包含用户权限等信息的 Authentication 对象,然后把它保存在 SecurityContextHolder 所持有的 SecurityContext 中,供后续的程序进行调用,如访问权限的鉴定等。

  1. 深入理解(默认机制):
//认证开始之前,我们调用的是该构造器,因此默认的principa(需要认证的用户对象)为我们自己所实现的UserDetils
//而我们自定义的UserDetils实现了,其实主要也就是两个组成部分,一个是用户详情,一个是权限范围
//credentials(认证凭据)默认给定的就是用户密码
//认证状态给定的是false,未认证
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
		super(null);
		this.principal = principal;
		this.credentials = credentials;
		setAuthenticated(false);
	}
  1. 深入理解(自定义):
//我们使用的是
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
   		Collection<? extends GrantedAuthority> authorities) {
   	super(authorities);
   	this.principal = principal;
   	this.credentials = credentials;
   	super.setAuthenticated(true); // must use super, as we override
   }
   //及我们已经通过密码规则的校验,现在进行权限的校验及分配
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,
                   userDetails.getPassword(), userDetails.getAuthorities());
  • AuthenticationManagerAuthenticationProvider

  AuthenticationManager 是一个用来处理认证(Authentication)请求的接口。在其中只定义了一个方法 authenticate(),该方法只接收一个代表认证请求的 Authentication 对象作为参数,如果认证成功,则会返回一个封装了当前用户权限等信息的 Authentication 对象进行返回。

Authentication authenticate(Authentication authentication) throws AuthenticationException;

   在 Spring Security 中,AuthenticationManager 的默认实现是 ProviderManager,而且它不直接自己处理认证请求,而是委托给其所配置的 AuthenticationProvider 列表,然后会依次使用每一个 AuthenticationProvider 进行认证,如果有一个 AuthenticationProvider 认证后的结果不为 null,则表示该 AuthenticationProvider 已经认证成功,之后的 AuthenticationProvider 将不再继续认证。然后直接以该 AuthenticationProvider 的认证结果作为 ProviderManager 的认证结果。如果所有的 AuthenticationProvider 的认证结果都为 null,则表示认证失败,将抛出一个 ProviderNotFoundException。校验认证请求最常用的方法是根据请求的用户名加载对应的 UserDetails,然后比对 UserDetails 的密码与认证请求的密码是否一致,一致则表示认证通过。Spring Security 内部的 DaoAuthenticationProvider 就是使用的这种方式。其内部使用 UserDetailsService 来负责加载 UserDetails,在认证成功以后会使用加载的 UserDetails 来封装要返回的 Authentication 对象,加载的 UserDetails 对象是包含用户权限等信息的。认证成功返回的 Authentication 对象将会保存在当前的 SecurityContext 中。
   默认情况下,在认证成功后 ProviderManager 将清除返回的 Authentication 中的凭证信息,如密码。所以如果你在无状态的应用中将返回的 Authentication 信息缓存起来了,那么以后你再利用缓存的信息去认证将会失败,因为它已经不存在密码这样的凭证信息了。所以在使用缓存的时候你应该考虑到这个问题。一种解决办法是设置 ProviderManager 的 eraseCredentialsAfterAuthentication 属性为 false,或者想办法在缓存时将凭证信息一起缓存。

  • AuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(authException.getMessage())));
        response.getWriter().flush();
    }

  • AccessDeineHandler 用来解决认证过的用户访问无权限资源时的异常
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler{
    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage())));
        response.getWriter().flush();
    }

  • 加入自定义的认证处理
		//添加自定义未授权和未登录结果返回
        httpSecurity.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthenticationEntryPoint);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值