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

  想要深入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//用户mapperprivate UserInfoMapper userInfoMapper;@Autowired//用户权限mapperprivate PermissionInfoMapper permissionInfoMapper;@Overridepublic 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 objectfor (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;//是否支持处理传递的ConfigAttributeboolean supports(ConfigAttribute attribute);//确认class是否为AccessDecisionManagerboolean 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 attributesreturn 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 checkingif (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 authorizationtry {//进行授权访问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形式的tokenUsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Allow subclasses to set the "details" propertysetDetails(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 implementsInitializingBean {...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认证和授权还有很多负责的过程需要深入了解,所以下次会对认证模块和授权模块进行更具体工作流程分析以及案例呈现。最后以上纯粹个人结合博客和官方文档总结,如有错请指出!

转载于:https://juejin.im/post/5d074dc1f265da1bce3dd10f

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/251640.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows下手动安装composer

1.下载compser.phar 地址 https://getcomposer.org/download/ 2.新建composer.bat 文件&#xff0c;写入“php "%~dp0composer.phar" %*” 3.把composer.bat composer.phar 两个文件放入 4.向环境变量里面写人“;D:\phpStudy\php\php-5.4.45;D:\phpStudy\php\php-5…

写更漂亮的javascript

用更合理的方式写 JavaScript 目录 声明变量对象数组字符串函数箭头函数模块迭代器和生成器属性变量提升比较运算符和等号代码块注释空白逗号分号类型转换命名规则声明变量 1.1 使用let和const代替var 不会变的声明用const//bad var $cat $(.cat)//good const $cat $(.cat)…

笔试小结---树

平衡二叉树(Balanced Binary Tree):又被称为AVL树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1&#xff0c;并且左右两个子树都是一棵平衡二叉树。 二叉搜索树&#xff1a;是一颗二叉树&#xff0c;可能为空;若非空,则满足以下特征: 1.每个元素有一…

iOS 快速实现分页界面的搭建

级别&#xff1a; ★★☆☆☆ 标签&#xff1a;「iOS」「分页」「QiPageMenuView」 作者&#xff1a; 沐灵洛 审校&#xff1a; QiShare团队 iOS 快速实现分页界面的搭建 项目中我们经常会遇到滚动分页的设计效果&#xff0c;被用来对不同数据界面的展示进行分类。我们先可以来…

java中String的常用方法

java中String的常用方法 转自&#xff1a;http://archer-zhou.iteye.com/blog/443864 java中String的常用方法1、length() 字符串的长度例&#xff1a;char chars[]{a,b.c};String snew String(chars);int lens.length();2、charAt() 截取一个字符例&#xff1a;char ch;ch&quo…

笔试小结---非对称加密算法

非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey).公开密钥和私有密钥是一对,如果公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥进行加密,那么只有用对应的公开密钥才能解密. 非对称加密算法的保密性比较好,它消除了最终用户交换…

登录令牌 Token 介绍

Token值介绍 token 值: 登录令牌.利用 token 值来判断用户的登录状态.类似于 MD5 加密之后的长字符串. 用户登录成功之后,在后端(服务器端)会根据用户信息生成一个唯一的值.这个值就是 token 值. 基本使用: 在服务器端(数据库)会保存这个 token 值,以后利用这个 token 值来检索…

java-number

通常&#xff0c;当我使用number类型的时候&#xff0c;我们可以使用原始数据类型例如byte&#xff0c;int,long,double等 int i 5000; float b 13.65; double m 0xaf; 所有包装类&#xff08;整型&#xff0c;长型&#xff0c;字节型&#xff0c;双精度型&#xff0c;浮点型&a…

您的浏览器没有获得Java Virtual Machine(JVM)支持。可能由于没有安装JVM或者已安装但是没有启用。请安装JVM1.5或者以上版本,如果已安装则启用它。...

您的浏览器没有获得Java Virtual Machine(JVM)支持。可能由于没有安装JVM或者已安装但是没有启用。请安装JVM1.5或者以上版本&#xff0c;如果已安装则启用它。 https://www.java.com/zh_CN/download/faq/remove_olderversions.xml https://jingyan.baidu.com/article/6d704a13…

指令定义

restict&#xff1a;它告诉AngularJS这个指令在DOM中可以以何种形式被声明。 E(元素&#xff09; <my-directive> </mydirective>A(属性) <div my-directive“expression”> </div>C(类名) <div class“my-directive:expression;”> </div>…

MyBatis学习总结(9)——使用MyBatis Generator自动创建代码

2019独角兽企业重金招聘Python工程师标准>>> 由于MyBatis属于一种半自动的ORM框架&#xff0c;所以主要的工作就是配置Mapping映射文件&#xff0c;但是由于手写映射文件很容易出错&#xff0c;所以可利用MyBatis生成器自动生成实体类、DAO接口和Mapping映射文件。这…

[BZOJ2125]最短路(圆方树DP)

题意&#xff1a;仙人掌图最短路。 算法&#xff1a;圆方树DP&#xff0c;$O(n\log nQ\log n)$ 首先建出仙人掌圆方树&#xff08;与点双圆方树的区别在于直接连割边&#xff0c;也就是存在圆圆边&#xff09;&#xff0c;然后考虑点u-v的最短路径&#xff0c;显然就是&#xf…

20162317 2017-2018-1 《程序设计与数据结构》第8周学习总结

20162317 2017-2018-1 《程序设计与数据结构》第8周学习总结 教材学习内容总结 1、二叉查找树的定义、性质2、向二叉查找树中添加元素的方法3、在二叉查找树中删除元素的方法4、旋转的定义、方法、意义 教材学习中的问题和解决过程问题1&#xff1a;我在17章中看到这么一句话&a…

ES6模块的转码

浏览器目前还不支持ES6模块,为了实现立刻使用,我们可以将其转为ES5的写法.除了Babel可以用来转码,还有以下两个方法也可以用来转码: ES6 moudule transpilerSystemJS ES6 moudule transpiler是square公司开源的一个转码器,可以将ES6模块转为CommonJS模块或AMD模块,从而在浏览器…

Java基础学习总结(22)——异常处理

2019独角兽企业重金招聘Python工程师标准>>> 一、异常的概念 异常指的是运行期出现的错误&#xff0c;也就是当程序开始执行以后执行期出现的错误。出现错误时观察错误的名字和行号最为重要。 1 package cn.javastudy.summary;2 3 public class TestEx{4 5 …

XAML中格式化日期

要求被格式化数据的类型是DateTime StringFormatyyyy-MM-dd StringFormat{}{0:yyyy-MM-dd}转载于:https://www.cnblogs.com/changbaishan/p/9144584.html

130242014045 林承晖 第2次实验

软件体系结构的第二次实验&#xff08;解释器风格与管道过滤器风格&#xff09; 一、实验目的 1&#xff0e;熟悉体系结构的风格的概念 2&#xff0e;理解和应用管道过滤器型的风格。 3、理解解释器的原理 4、理解编译器模型 二、实验环境 硬件&#xff1a; 软件&#xff1a;P…

AnularJS1事件

在Web应用的组件是松耦合的情况下&#xff0c;比如需要用户验证然后处理授权&#xff0c;即时的通信不总是可行的&#xff0c;因为组件没有耦合在一起。 例如&#xff0c;如果后端对一个请求返回了状态码401&#xff08;表明一个未经授权的请求&#xff09;&#xff0c;我们期望…

Java基础学习总结(8)——super关键字

2019独角兽企业重金招聘Python工程师标准>>> 一、super关键字 在JAVA类中使用super来引用父类的成分&#xff0c;用this来引用当前对象&#xff0c;如果一个类从另外一个类继承&#xff0c;我们new这个子类的实例对象的时候&#xff0c;这个子类对象里面会有一个父类…

conda镜像

转自https://blog.csdn.net/guilutian0541/article/details/81004769 conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main conda config --set show…