spring security源码解析

一、重要的类

源码讲解

SecurityContextPersistenceFilter作用

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   // ensure that filter is only applied once per request
   if (request.getAttribute(FILTER_APPLIED) != null) {
      chain.doFilter(request, response);
      return;
   }
   request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
   if (this.forceEagerSessionCreation) {
      HttpSession session = request.getSession();
      if (this.logger.isDebugEnabled() && session.isNew()) {
         this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
      }
   }
   HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
    //每次请求开始,先从session中取认证信息
   SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
   try {
       //取到后,放入ThreadLocal中
      SecurityContextHolder.setContext(contextBeforeChainExecution);
      if (contextBeforeChainExecution.getAuthentication() == null) {
         logger.debug("Set SecurityContextHolder to empty SecurityContext");
      }
      else {
         if (this.logger.isDebugEnabled()) {
            this.logger
                  .debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
         }
      }
       //放行,执行后续Filter和目标方法
      chain.doFilter(holder.getRequest(), holder.getResponse());
   }
   finally {
      SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
      //清空ThreadLocal认证信息
      SecurityContextHolder.clearContext();
       //认证信息存入session(之前就有,也就是覆盖一道)
      this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
      request.removeAttribute(FILTER_APPLIED);
      this.logger.debug("Cleared SecurityContextHolder to complete request");
   }
}
  • UsernamePasswordAuthenticationFilter和AbstractAuthenticationProcessingFilter

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   //判断是不是登录请求/login && POST,不是直接放行
    if (!requiresAuthentication(request, response)) {
      chain.doFilter(request, response);
      return;
   }
   try {
      Authentication authenticationResult = attemptAuthentication(request, response);
      if (authenticationResult == null) {
         // return immediately as subclass has indicated that it hasn't completed
         return;
      }
      this.sessionStrategy.onAuthentication(authenticationResult, request, response);
      // Authentication success
      if (this.continueChainBeforeSuccessfulAuthentication) {
         chain.doFilter(request, response);
      }
       //登录成功,把认证信息存入session中
      successfulAuthentication(request, response, chain, authenticationResult);
   }
   catch (InternalAuthenticationServiceException failed) {
      this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
      unsuccessfulAuthentication(request, response, failed);
   }
   catch (AuthenticationException ex) {
      // Authentication failed
      unsuccessfulAuthentication(request, response, ex);
   }
}

FilterSecurityInterceptor

public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
   if (isApplied(filterInvocation) && this.observeOncePerRequest) {
      filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
      return;
   }
   if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
      filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
   }
    //调用目标方法前,校验是否有权限,没有权限,直接抛AccessDeniedException异常,异常Filter捕获到后,会进行相应处理
   InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
   try {
      filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
   }
   finally {
      super.finallyInvocation(token);
   }
   super.afterInvocation(token, null);
}

过滤器链

WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
UsernamePasswordAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor

  • 为什么引入spring-boot-starter-security,springboot项目所有资源都被保护起来了呢?

WebSecurityConfiguration

创建bean:springSecurityFilterChain

SpringBootWebSecurityConfiguration

  • 默认的自动配置类,给我们配置好了认证授权规则,即所有请求都需要认证授权
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {

    /**
    springboot默认帮我们配置好的:
    	所有请求都需要认证,可以是formLogin认证,httpBasic基本是不用了
    */
	@Bean
	@Order(SecurityProperties.BASIC_AUTH_ORDER)
	SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
		http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
		return http.build();
	}

}

@ConditionalOnDefaultWebSecurity

WebSecurityConfigurerAdapter

  • 如果我们需要自定义管理认证授权,我们可以重写WebSecurityConfigurerAdapter,使得默认配置类不生效

UserDetailsServiceAutoConfiguration

  • 自动注入 InMemoryUserDetailsManager,基于内存的认证

UserDetailsService

  • 认证数据源

二、认证

自定义资源认证规则

在这里插入图片描述

@Configuration
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/hello").permitAll() //放行/hello,必须在.anyRequest()之前
                .anyRequest().authenticated() //其余请求需要认证
                .and().formLogin() //表单登录进行认证
                .and().httpBasic();
    }
}

自定义登录成功处理(前后端分离)

在这里插入图片描述

/*
登录成功处理
*/
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        Map<String, Object> map = MapUtil.of("code", "0000");
        map.put("authentication", authentication);
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json");
        PrintWriter out = response.getWriter();
        out.write(new ObjectMapper().writeValueAsString(map));
        out.flush();
        out.close();
    }
}

显示登录错误信息

原理:AbstractAuthenticationProcessingFilter.unsuccessfulAuthentication

<h4>
  <span th:text="${session.SPRING_SECURITY_LAST_EXCEPTION}"></span>
</h4>

自定义登录失败(前后端分离)

在这里插入图片描述

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        Map<String, Object> map = MapUtil.of("code", "9999");
        map.put("msg", "认证失败,用户名或密码错误");
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json");
        PrintWriter out = response.getWriter();
        out.write(new ObjectMapper().writeValueAsString(map));
        out.flush();
        out.close();
    }
}

注销登录配置

默认是 /loginout

在这里插入图片描述

登录成功后获取用户信息

spring security 登录成功后,会将用户信息保存在session中,还有线程绑定,

默认是单线程中获取,不能在子线程中获取

  • SecurityContextHolder

  • SecurityContextHolderStrategy

代码中获取

@RequestMapping("/hello")
public Object hello(){
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    User principal = (User) authentication.getPrincipal();
    
    new Thread(() -> {
        Authentication authentication2 = SecurityContextHolder.getContext().getAuthentication();
        User principal2 = (User) authentication2.getPrincipal();
        log.info("principal2:{}", principal2);
    }).start();
    return authentication;
}

子线程中也能获取,需要改变配置:

-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL

在这里插入图片描述
在这里插入图片描述

页面中获取

<dependency>
  <groupId>org.thymeleaf.extras</groupId>
  <artifactId>thymeleaf-extras-springsecurity5</artifactId>
  <version>3.0.4.RELEASE</version>
</dependency>
  • 页面加入命名空间、使用
<html lang="en" xmlns:th="https://www.thymeleaf.org" 
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
    
<!--获取认证用户名-->
<ul>
  <li sec:authentication="principal.username"></li>
  <li sec:authentication="principal.authorities"></li>
  <li sec:authentication="principal.accountNonExpired"></li>
  <li sec:authentication="principal.accountNonLocked"></li>
  <li sec:authentication="principal.credentialsNonExpired"></li>
</ul>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值