Spring security (一)架构框架-Component、Service、Filter分析

简介:   想要深入spring security的authentication (身份验证)和access-control(访问权限控制)工作流程,必须清楚spring security的主要技术点包括关键接口、类以及抽象类如何协同工作进行authentication 和access-control的实现。

想要深入spring security的authentication (身份验证)和access-control(访问权限控制)工作流程,必须清楚spring security的主要技术点包括关键接口、类以及抽象类如何协同工作进行authentication 和access-control的实现。


1.spring security 认证和授权流程


常见认证和授权流程可以分成:


  1. A user is prompted to log in with a username and password (用户用账密码登录)
  2. The system (successfully) verifies that the password is correct for the username(校验密码正确性)
  3. The context information for that user is obtained (their list of roles and so on).(获取用户信息context,如权限)
  4. A security context is established for the user(为用户创建security context)
  5. The user proceeds, potentially to perform some operation which is potentially protected by an access control mechanism which checks the required permissions for the operation against the current security context information.(访问权限控制,是否具有访问权限)


1.1 spring security 认证


上述前三点为spring security认证验证环节:


  1. 通常通过AbstractAuthenticationProcessingFilter过滤器将账号密码组装成Authentication实现类UsernamePasswordAuthenticationToken;
  2. 将token传递给AuthenticationManager验证是否有效,而AuthenticationManager通常使用ProviderManager实现类来检验;
  3. AuthenticationManager认证成功后将返回一个拥有详细信息的Authentication object(包括权限信息,身份信息,细节信息,但密码通常会被移除);
  4. 通过SecurityContextHolder.getContext().getAuthentication().getPrincipal()将Authentication设置到security context中。


1.2 spring security访问授权


  1. 通过FilterSecurityInterceptor过滤器入口进入;
  2. FilterSecurityInterceptor通过其继承的抽象类的AbstractSecurityInterceptor.beforeInvocation(Object object)方法进行访问授权,其中涉及了类AuthenticationManager、AccessDecisionManager、SecurityMetadataSource等。


根据上述描述的过程,我们接下来主要去分析其中涉及的一下Component、Service、Filter。


2.核心组件(Core Component )


2.1 SecurityContextHolder


SecurityContextHolder提供对SecurityContext的访问,存储security context(用户信息、角色权限等),而且其具有下列储存策略即工作模式:


  1. SecurityContextHolder.MODE_THREADLOCAL(默认):使用ThreadLocal,信息可供此线程下的所有的方法使用,一种与线程绑定的策略,此天然很适合Servlet Web应用。
  2. SecurityContextHolder.MODE_GLOBAL:使用于独立应用
  3. SecurityContextHolder.MODE_INHERITABLETHREADLOCAL:具有相同安全标示的线程


修改SecurityContextHolder的工作模式有两种方法 :


  1. 设置一个系统属性(system.properties) : spring.security.strategy;
  2. 调用SecurityContextHolder静态方法setStrategyName()


在默认ThreadLocal策略中,SecurityContextHolder为静态方法获取用户信息为:


Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
   if (principal instanceof UserDetails) {      
        String username = ((UserDetails)principal).getUsername();
   } else {
        String username = principal.toString();
   }
复制代码


但是一般不需要自身去获取。 其中getAuthentication()返回一个Authentication认证主体,接下来分析Authentication、UserDetails细节。


2.2 Authentication


Spring Security使用一个Authentication对象来描述当前用户的相关信息,其包含用户拥有的权限信息列表、用户细节信息(身份信息、认证信息)。Authentication为认证主体在spring security中时最高级别身份/认证的抽象,常见的实现类UsernamePasswordAuthenticationToken。Authentication接口源码:


public interface Authentication extends Principal, Serializable { 
    //权限信息列表,默认GrantedAuthority接口的一些实现类
    Collection<? extends GrantedAuthority> getAuthorities(); 
    //密码信息
    Object getCredentials();
    //细节信息,web应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值
    Object getDetails();
    //通常返回值为UserDetails实现类
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
复制代码


前面两个组件都涉及了UserDetails,以及GrantedAuthority其到底是什么呢?2.3小节分析。


2.3 UserDetails&GrantedAuthority


UserDetails提供从应用程序的DAO或其他安全数据源构建Authentication对象所需的信息,包含GrantedAuthority。其官方实现类为User,开发者可以实现其接口自定义UserDetails实现类。其接口源码:


public interface UserDetails extends Serializable {
     Collection<? extends GrantedAuthority> getAuthorities();
     String getPassword();
     String getUsername();
     boolean isAccountNonExpired();
     boolean isAccountNonLocked();
     boolean isCredentialsNonExpired();
     boolean isEnabled();
}
复制代码


UserDetails与Authentication接口功能类似,其实含义即是Authentication为用户提交的认证凭证(账号密码),UserDetails为系统中用户正确认证凭证,在UserDetailsService中的loadUserByUsername方法获取正确的认证凭证。其中在getAuthorities()方法中获取到GrantedAuthority列表是代表用户访问应用程序权限范围,此类权限通常是“role(角色)”,例如ROLE_ADMINISTRATOR或ROLE_HR_SUPERVISOR。GrantedAuthority接口常见的实现类SimpleGrantedAuthority。


3. 核心服务类(Core Services)


3.1 AuthenticationManager、ProviderManager以及AuthenticationProvider


AuthenticationManager是认证相关的核心接口,是认证一切的起点。但常见的认证流程都是AuthenticationManager实现类ProviderManager处理,而且ProviderManager实现类基于委托者模式维护AuthenticationProvider 列表用于不同的认证方式。例如:


  1. 使用账号密码认证方式DaoAuthenticationProvider实现类(继承了AbstractUserDetailsAuthenticationProvide抽象类),其为默认认证方式,进行数据库库获取认证数据信息。
  2. 游客身份登录认证方式AnonymousAuthenticationProvider实现类
  3. 从cookies获取认证方式RememberMeAuthenticationProvider实现类

  AuthenticationProvider为


ProviderManager源码分析:


public Authentication authenticate(Authentication authentication)
    throws AuthenticationException {
  Class<? extends Authentication> toTest = authentication.getClass();
  AuthenticationException lastException = null;
  Authentication result = null;
  //AuthenticationProvider列表依次认证
  for (AuthenticationProvider provider : getProviders()) {
    if (!provider.supports(toTest)) {
      continue;
    }
    try {
        //每个AuthenticationProvider进行认证
      result = provider.authenticate(authentication)
      if (result != null) {
        copyDetails(authentication, result);
        break;
      }
    }
    ....
    catch (AuthenticationException e) {
      lastException = e;
    }
  }
    //进行父类AuthenticationProvider进行认证
  if (result == null && parent != null) {
    // Allow the parent to try.
    try {
      result = parent.authenticate(authentication);
    }
    catch (AuthenticationException e) {
      lastException = e;
    }
  }
     // 如果有Authentication信息,则直接返回
  if (result != null) {
    if (eraseCredentialsAfterAuthentication
        && (result instanceof CredentialsContainer)) {
        //清除密码
      ((CredentialsContainer) result).eraseCredentials();
    }
    //发布登录成功事件
    eventPublisher.publishAuthenticationSuccess(result);
    return result;
  }
        //如果都没认证成功,抛出异常
  if (lastException == null) {
    lastException = new ProviderNotFoundException(messages.getMessage(
        "ProviderManager.providerNotFound",
        new Object[] { toTest.getName() },
        "No AuthenticationProvider found for {0}"));
  }
  prepareException(lastException, authentication);
  throw lastException;
    }  
复制代码


ProviderManager 中的AuthenticationProvider列表,会依照次序去认证,默认策略下,只需要通过一个AuthenticationProvider的认证,即可被认为是登录成功,而且AuthenticationProvider认证成功后返回一个Authentication实体,并为了安全会进行清除密码。如果所有认证器都无法认证成功,则ProviderManager 会抛出一个ProviderNotFoundException异常。


3.2 UserDetailsService


UserDetailsService接口作用是从特定的地方获取认证的数据源(账号、密码)。如何获取到系统中正确的认证凭证,通过loadUserByUsername(String username)获取认证信息,而且其只有一个方法:


UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;  
复制代码


其常见的实现类从数据获取的JdbcDaoImpl实现类,从内存中获取的InMemoryUserDetailsManager实现类,不过我们可以实现其接口自定义UserDetailsService实现类,如下:


public class CustomUserService implements UserDetailsService {
 @Autowired
 //用户mapper
 private UserInfoMapper userInfoMapper;
 @Autowired
 //用户权限mapper
 private PermissionInfoMapper permissionInfoMapper;
 @Override
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    UserInfoDTO userInfo = userInfoMapper.getUserInfoByUserName(username);
    if (userInfo != null) {
        List<PermissionInfoDTO> permissionInfoDTOS = permissionInfoMapper.findByAdminUserId(userInfo.getId());
        List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
        //组装权限GrantedAuthority object
        for (PermissionInfoDTO permissionInfoDTO : permissionInfoDTOS) {
            if (permissionInfoDTO != null && permissionInfoDTO.getPermissionName() != null) {
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(
                        permissionInfoDTO.getPermissionName());
                grantedAuthorityList.add(grantedAuthority);
            }
        }
        //返回用户信息
        return new User(userInfo.getUserName(), userInfo.getPasswaord(), grantedAuthorityList);
    }else {
        //抛出用户不存在异常
        throw new UsernameNotFoundException("admin" + username + "do not exist");
      }
    }
}   
复制代码


3.3 AccessDecisionManager&SecurityMetadataSource


AccessDecisionManager是由AbstractSecurityInterceptor调用,负责做出最终的访问控制决策。


AccessDecisionManager接口源码:


//访问控制决策
  void decide(Authentication authentication, Object secureObject,Collection<ConfigAttribute> attrs) 
        throws AccessDeniedException;
  //是否支持处理传递的ConfigAttribute
  boolean supports(ConfigAttribute attribute);
  //确认class是否为AccessDecisionManager
  boolean supports(Class clazz);
复制代码


SecurityMetadataSource包含着AbstractSecurityInterceptor访问授权所需的元数据(动态url、动态授权所需的数据),在AbstractSecurityInterceptor授权模块中结合AccessDecisionManager进行访问授权。其涉及了ConfigAttribute。


SecurityMetadataSource接口:


Collection<ConfigAttribute> getAttributes(Object object)
    throws IllegalArgumentException;
Collection<ConfigAttribute> getAllConfigAttributes();
boolean supports(Class<?> clazz);
复制代码


我们还可以自定义SecurityMetadataSource数据源,实现接口FilterInvocationSecurityMetadataSource。例:


public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    public List<ConfigAttribute> getAttributes(Object object) {
        FilterInvocation fi = (FilterInvocation) object;
        String url = fi.getRequestUrl();
        String httpMethod = fi.getRequest().getMethod();
        List<ConfigAttribute> attributes = new ArrayList<ConfigAttribute>();
        // Lookup your database (or other source) using this information and populate the
        // list of attributes
        return attributes;
    }
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}
复制代码


3.4 PasswordEncoder


为了存储安全,一般要对密码进行算法加密,而spring security提供了加密PasswordEncoder接口。其实现类有使用BCrypt hash算法实现的BCryptPasswordEncoder,SCrypt hashing 算法实现的SCryptPasswordEncoder实现类,实现类内部实现可看源码分析。而PasswordEncoder接口只有两个方法:


public interface PasswordEncoder {
    //密码加密
    String encode(CharSequence rawPassword);
    //密码配对
    boolean matches(CharSequence rawPassword, String encodedPassword);
} 
复制代码


4 核心 Security 过滤器(Core Security Filters)


4.1 FilterSecurityInterceptor


FilterSecurityInterceptor是Spring security授权模块入口,该类根据访问的用户的角色,权限授权访问那些资源(访问特定路径应该具备的权限)。


FilterSecurityInterceptor封装FilterInvocation对象进行操作,所有的请求到了这一个filter,如果这个filter之前没有执行过的话,那么首先执行其父类AbstractSecurityInterceptor提供的InterceptorStatusToken token = super.beforeInvocation(fi),在此方法中使用AuthenticationManager获取Authentication中用户详情,使用ConfigAttribute封装已定义好访问权限详情,并使用AccessDecisionManager.decide()方法进行访问权限控制。


FilterSecurityInterceptor源码分析:


public void invoke(FilterInvocation fi) throws IOException, ServletException {
  if ((fi.getRequest() != null)
      && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
      && observeOncePerRequest) {
    fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
  }
  else {
    // first time this request being called, so perform security checking
    if (fi.getRequest() != null && observeOncePerRequest) {
      fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
    }
        //回调其继承的抽象类AbstractSecurityInterceptor的方法
    InterceptorStatusToken token = super.beforeInvocation(fi);
    try {
      fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
    }
    finally {
      super.finallyInvocation(token);
    }
    super.afterInvocation(token, null);
  }
}
复制代码


AbstractSecurityInterceptor源码分析:


protected InterceptorStatusToken beforeInvocation(Object object) {
  ....
  //获取所有访问权限(url-role)属性列表(已定义在数据库或者其他地方)
  Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
      .getAttributes(object);
  ....
  //获取该用户访问信息(包括url,访问权限)
  Authentication authenticated = authenticateIfRequired();
  // Attempt authorization
  try {
      //进行授权访问
    this.accessDecisionManager.decide(authenticated, object, attributes);
  }catch
  ....
}
复制代码


4.2 UsernamePasswordAuthenticationFilter


UsernamePasswordAuthenticationFilter使用username和password表单登录使用的过滤器,也是最为常用的过滤器。其源码:


public Authentication attemptAuthentication(HttpServletRequest request,
    HttpServletResponse response) throws AuthenticationException {
     //获取表单中的用户名和密码
     String username = obtainUsername(request);
     String password = obtainPassword(request);
     ...
     username = username.trim();
     //组装成username+password形式的token
     UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
     username, password);
     // Allow subclasses to set the "details" property
     setDetails(request, authRequest);
     //交给内部的AuthenticationManager去认证,并返回认证信息
     return this.getAuthenticationManager().authenticate(authRequest);
}   
复制代码


其主要代码为创建UsernamePasswordAuthenticationToken的Authentication实体以及调用AuthenticationManager进行authenticate认证,根据认证结果执行successfulAuthentication或者unsuccessfulAuthentication,无论成功失败,一般的实现都是转发或者重定向等处理,不再细究AuthenticationSuccessHandler和AuthenticationFailureHandle。兴趣的可以研究一下其父类AbstractAuthenticationProcessingFilter过滤器。


4.3 AnonymousAuthenticationFilter


AnonymousAuthenticationFilter是匿名登录过滤器,它位于常用的身份认证过滤器(如UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、RememberMeAuthenticationFilter)之后,意味着只有在上述身份过滤器执行完毕后,SecurityContext依旧没有用户信息,AnonymousAuthenticationFilter该过滤器才会有意义——基于用户一个匿名身份。


AnonymousAuthenticationFilter源码分析:


public class AnonymousAuthenticationFilter extends GenericFilterBean implements
  InitializingBean {
  ...
  public AnonymousAuthenticationFilter(String key) {
      this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
    }
        ...
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
    throws IOException, ServletException {
      if (SecurityContextHolder.getContext().getAuthentication() == null) {
        //创建匿名登录Authentication的信息
        SecurityContextHolder.getContext().setAuthentication(
            createAuthentication((HttpServletRequest) req));
            ...
      }
      chain.doFilter(req, res);
    }
    //创建匿名登录Authentication的信息方法
    protected Authentication createAuthentication(HttpServletRequest request) {
      AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
      principal, authorities);
      auth.setDetails(authenticationDetailsSource.buildDetails(request));
      return auth;
    }
}
复制代码


4.4 SecurityContextPersistenceFilter


SecurityContextPersistenceFilter的两个主要作用便是request来临时,创建SecurityContext安全上下文信息和request结束时清空SecurityContextHolder。源码后续分析。


小节总结:


. AbstractAuthenticationProcessingFilter:主要处理登录

. FilterSecurityInterceptor:主要处理鉴权


总结


  经过上面对核心的Component、Service、Filter分析,初步了解了Spring Security工作原理以及认证和授权工作流程。Spring Security认证和授权还有很多负责的过程需要深入了解,所以下次会对认证模块和授权模块进行更具体工作流程分析以及案例呈现。最后以上纯粹个人结合博客和官方文档总结,如有错请指出!


各位看官还可以吗?喜欢的话,动动手指点个💗,点个关注呗!!谢谢支持!



目录
相关文章
|
24天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
1月前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
38 0
|
28天前
|
前端开发 Java 数据库连接
Spring 框架:Java 开发者的春天
Spring 框架是一个功能强大的开源框架,主要用于简化 Java 企业级应用的开发,由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立,并由Pivotal团队维护。
43 1
Spring 框架:Java 开发者的春天
|
20天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
38 2
|
20天前
|
消息中间件 NoSQL Java
springboot整合常用中间件框架案例
该项目是Spring Boot集成整合案例,涵盖多种中间件的使用示例,每个案例项目使用最小依赖,便于直接应用到自己的项目中。包括MyBatis、Redis、MongoDB、MQ、ES等的整合示例。
78 1
|
28天前
|
Java 数据库连接 开发者
Spring 框架:Java 开发者的春天
【10月更文挑战第27天】Spring 框架由 Rod Johnson 在 2002 年创建,旨在解决 Java 企业级开发中的复杂性问题。它通过控制反转(IOC)和面向切面的编程(AOP)等核心机制,提供了轻量级的容器和丰富的功能,支持 Web 开发、数据访问等领域,显著提高了开发效率和应用的可维护性。Spring 拥有强大的社区支持和丰富的生态系统,是 Java 开发不可或缺的工具。
|
10天前
|
缓存 负载均衡 JavaScript
探索微服务架构下的API网关模式
【10月更文挑战第37天】在微服务架构的海洋中,API网关犹如一座灯塔,指引着服务的航向。它不仅是客户端请求的集散地,更是后端微服务的守门人。本文将深入探讨API网关的设计哲学、核心功能以及它在微服务生态中扮演的角色,同时通过实际代码示例,揭示如何实现一个高效、可靠的API网关。
|
8天前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####
|
9天前
|
Dubbo Java 应用服务中间件
服务架构的演进:从单体到微服务的探索之旅
随着企业业务的不断拓展和复杂度的提升,对软件系统架构的要求也日益严苛。传统的架构模式在应对现代业务场景时逐渐暴露出诸多局限性,于是服务架构开启了持续演变之路。从单体架构的简易便捷,到分布式架构的模块化解耦,再到微服务架构的精细化管理,企业对技术的选择变得至关重要,尤其是 Spring Cloud 和 Dubbo 等微服务技术的对比和应用,直接影响着项目的成败。 本篇文章会从服务架构的演进开始分析,探索从单体项目到微服务项目的演变过程。然后也会对目前常见的微服务技术进行对比,找到目前市面上所常用的技术给大家进行讲解。
23 1
服务架构的演进:从单体到微服务的探索之旅
|
7天前
|
消息中间件 监控 安全
后端架构演进:从单体到微服务####
在数字化转型的浪潮中,企业应用的后端架构经历了从传统单体架构到现代微服务架构的深刻变革。本文探讨了这一演进过程的背景、驱动力、关键技术及面临的挑战,揭示了如何通过微服务化实现系统的高可用性、扩展性和敏捷开发,同时指出了转型过程中需克服的服务拆分、数据管理、通信机制等难题,为读者提供了一个全面理解后端架构演变路径的视角。 ####
24 8
下一篇
无影云桌面