2019独角兽企业重金招聘Python工程师标准>>>

参考如下文章整理的笔记:

  • Spring Security系列

#1 Authentication认证信息

认证前:用来表示用户输入的认证信息的对象

认证后:包含用户详细信息以及权限信息的对象

接口定义如下:

public interface Authentication extends Principal, Serializable {//获取用户权限Collection<? extends GrantedAuthority> getAuthorities();//获取密码信息Object getCredentials();Object getDetails();//获取用户唯一标示,如用户名Object getPrincipal();boolean isAuthenticated();void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

接口实现如下:

#2 SecurityContextHolder用来保存SecurityContext信息的

SecurityContextHolder默认使用ThreadLocalSecurityContextHolderStrategy策略来保存。

ThreadLocalSecurityContextHolderStrategy策略实现就是使用ThreadLocal来保存

final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>();
}

SecurityContext又是用来保存上述Authentication信息,接口定义如下:

public interface SecurityContext extends Serializable {Authentication getAuthentication();void setAuthentication(Authentication authentication);
}

#3 AuthenticationManager对用户输入信息进行认证

接口定义如下:

public interface AuthenticationManager {Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

接口实现如下:

默认实现ProviderManager,内部包含了一个List类型的AuthenticationProvider providers,也就是说ProviderManager委托内部的AuthenticationProvider来实现认证的

使用如下标签默认就是创建ProviderManager:

<security:authentication-manager alias="authenticationManager"><security:authentication-provider  user-service-ref="userDetailsService"/><security:authentication-provider  ref="xxxxx"/>
</security:authentication-manager>

#4 AuthenticationProvider对用户输入信息进行认证

接口的定义如下:

public interface AuthenticationProvider {Authentication authenticate(Authentication authentication)throws AuthenticationException;boolean supports(Class<?> authentication);
}

接口实现如下:

默认实现DaoAuthenticationProvider:

内部含有一个UserDetailsService userDetailsService对象,用来加载用户信息包含权限信息

  • 1 使用UserDetailsService userDetailsService来加载用户信息到UserDetails对象中

    UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
    

    因此在UserDetailsService上述加载过程实现中,我们就可以根据用户名来判断用户是否存在。

    UserDetails接口实现,默认就是org.springframework.security.core.userdetails.User,所以我们一般需要继承该对象

  • 2 检查上述用户信息是否过期、被锁定等等,分成preAuthenticationChecks,检查密码是否正确和postAuthenticationChecks

    private class DefaultPreAuthenticationChecks implements UserDetailsChecker {public void check(UserDetails user) {if (!user.isAccountNonLocked()) {logger.debug("User account is locked");throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked","User account is locked"), user);}if (!user.isEnabled()) {logger.debug("User account is disabled");throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled","User is disabled"), user);}if (!user.isAccountNonExpired()) {logger.debug("User account is expired");throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired","User account has expired"), user);}}
    }protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {Object salt = null;if (this.saltSource != null) {salt = this.saltSource.getSalt(userDetails);}if (authentication.getCredentials() == null) {logger.debug("Authentication failed: no credentials provided");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);}String presentedPassword = authentication.getCredentials().toString();if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {logger.debug("Authentication failed: password does not match stored value");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);}
    }
    

    其中上述密码检查,接口是PasswordEncoder,默认是PlaintextPasswordEncoder,没有加密过的

    private class DefaultPostAuthenticationChecks implements UserDetailsChecker {public void check(UserDetails user) {if (!user.isCredentialsNonExpired()) {logger.debug("User account credentials have expired");throw new CredentialsExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired","User credentials have expired"), user);}}
    }
    
  • 3 一旦认证通过,构建一个UsernamePasswordAuthenticationToken对象,使用上述UserDetails信息、用户输入的密码信息

    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,UserDetails user) {UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities()));result.setDetails(authentication.getDetails());return result;
    }
    

#5 认证过程

  • 1、用户使用用户名和密码进行登录。
  • 2、Spring Security将获取到的用户名和密码封装成一个实现了Authentication接口的UsernamePasswordAuthenticationToken。
  • 3、将上述产生的token对象传递给AuthenticationManager进行登录认证。
  • 4、AuthenticationManager认证成功后将会返回一个封装了用户权限等信息的Authentication对象。
  • 5、通过调用SecurityContextHolder.getContext().setAuthentication(...)将AuthenticationManager返回的Authentication对象赋予给当前的SecurityContext

ExceptionTranslationFilter是用来处理来自AbstractSecurityInterceptor抛出的AuthenticationException和AccessDeniedException的

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;try {chain.doFilter(request, response);logger.debug("Chain processed normally");}catch (IOException ex) {throw ex;}catch (Exception ex) {// Try to extract a SpringSecurityException from the stacktraceThrowable[] causeChain = throwableAnalyzer.determineCauseChain(ex);RuntimeException ase = (AuthenticationException)throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);if (ase == null) {ase = (AccessDeniedException)throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);}if (ase != null) {handleSpringSecurityException(request, response, chain, ase);} else {// Rethrow ServletExceptions and RuntimeExceptions as-isif (ex instanceof ServletException) {throw (ServletException) ex;}else if (ex instanceof RuntimeException) {throw (RuntimeException) ex;}// Wrap other Exceptions. This shouldn't actually happen// as we've already covered all the possibilities for doFilterthrow new RuntimeException(ex);}}
}

AbstractSecurityInterceptor是Spring Security用于拦截请求进行权限鉴定的,其拥有两个具体的子类,拦截方法调用的MethodSecurityInterceptor和拦截URL请求的FilterSecurityInterceptor。

当ExceptionTranslationFilter捕获到的是AuthenticationException时将调用AuthenticationEntryPoint引导用户进行登录;

如果捕获的是AccessDeniedException,但是用户还没有通过认证,则调用AuthenticationEntryPoint引导用户进行登录认证,否则将返回一个表示不存在对应权限的403错误码

  • 1 AbstractSecurityInterceptor对用户访问资源进行检查,如果未认证或者没权限则抛出异常

  • 2 ExceptionTranslationFilter这个Filter捕获上述异常信息,引导重定向到登陆界面

  • 3 用户输入用户名和密码进行认证登陆

  • 4 Spring Security将获取到的用户名和密码封装成一个实现了Authentication接口的UsernamePasswordAuthenticationToken

  • 5 将上述产生的token对象传递给AuthenticationManager进行登录认证。

  • 6 AuthenticationManager认证成功后将会返回一个封装了用户权限等信息的Authentication对象

  • 7 将上述Authentication对象封装到SecurityContext中

  • 8 SecurityContextPersistentFilter将上述SecurityContext保存到对应的HttpSession属性中,key为:SPRING_SECURITY_CONTEXT

    SecurityContextPersistentFilter在Filter执行前,从HttpSession中先尝试获取保存的SecurityContext对象信息,如果没有则创建一个。如果HttpSession没有的话,看SecurityContextPersistentFilter的forceEagerSessionCreation属性,如果强制产生,则在此处调用request的getSession方法产生HttpSession

    Filter执行后,会将上述SecurityContext对象信息保存到HttpSession的属性中

    因此,同样用户再次访问的时候,就不需要进行登录认证了,只需要从HttpSession中就能取出对应的认证信息SecurityContext了

    也就是说,用户在某一次请求的过程中,SecurityContext是保存在ThreadLocal中的,但是请求结束后,就会被清除了。同时,SecurityContext也被保存在HttpSession中,这样的话,同一个用户不同请求即使是在不同的线程上,也都能根据session来获取到SecurityContext信息。

#6 Filter链

Spring Security已经定义了一些Filter,不管实际应用中你用到了哪些,它们应当保持如下顺序。

  • (1)ChannelProcessingFilter,如果你访问的channel错了,那首先就会在channel之间进行跳转,如http变为https。
  • (2)SecurityContextPersistenceFilter,这样的话在一开始进行request的时候就可以在SecurityContextHolder中建立一个SecurityContext,然后在请求结束的时候,任何对SecurityContext的改变都可以被copy到HttpSession。
  • (3)ConcurrentSessionFilter,因为它需要使用SecurityContextHolder的功能,而且更新对应session的最后更新时间,以及通过SessionRegistry获取当前的SessionInformation以检查当前的session是否已经过期,过期则会调用LogoutHandler。
  • (4)认证处理机制,如UsernamePasswordAuthenticationFilter,CasAuthenticationFilter,BasicAuthenticationFilter等,以至于SecurityContextHolder可以被更新为包含一个有效的Authentication请求。
  • (5)SecurityContextHolderAwareRequestFilter,它将会把HttpServletRequest封装成一个继承自HttpServletRequestWrapper的SecurityContextHolderAwareRequestWrapper,同时使用SecurityContext实现了HttpServletRequest中与安全相关的方法。
  • (6)JaasApiIntegrationFilter,如果SecurityContextHolder中拥有的Authentication是一个JaasAuthenticationToken,那么该Filter将使用包含在JaasAuthenticationToken中的Subject继续执行FilterChain。
  • (7)RememberMeAuthenticationFilter,如果之前的认证处理机制没有更新SecurityContextHolder,并且用户请求包含了一个Remember-Me对应的cookie,那么一个对应的Authentication将会设给SecurityContextHolder。
  • (8)AnonymousAuthenticationFilter,如果之前的认证机制都没有更新SecurityContextHolder拥有的Authentication,那么一个AnonymousAuthenticationToken将会设给SecurityContextHolder。
  • (9)ExceptionTransactionFilter,用于处理在FilterChain范围内抛出的AccessDeniedException和AuthenticationException,并把它们转换为对应的Http错误码返回或者对应的页面。
  • (10)FilterSecurityInterceptor,保护Web URI,并且在访问被拒绝时抛出异常

在web.xml中如下配置:

  <filter><filter-name>springSecurityFilterChain</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><filter-mapping><filter-name>springSecurityFilterChain</filter-name><url-pattern>/*</url-pattern></filter-mapping>

DelegatingFilterProxy其实是委托给Spring容器中的Filter来执行,如何指定哪一个呢?采用DelegatingFilterProxy自身的targetBeanName这个参数

上述就是默认取值spring容器中的这个springSecurityFilterChain Filter,采用的是FilterChainProxy类

当我们使用基于Spring Security的NameSpace进行配置时,系统会自动为我们注册一个名为springSecurityFilterChain类型为FilterChainProxy的bean。该FilterChainProxy含有一个List<SecurityFilterChain> filterChains属性,我们如下配置

Spring security允许我们在配置文件中配置多个http元素,以针对不同形式的URL使用不同的安全控制。Spring Security将会为每一个http元素创建对应的FilterChain,同时按照它们的声明顺序加入到FilterChainProxy。所以当我们同时定义多个http元素时要确保将更具有特性的URL配置在前。

   <security:http pattern="/login*.jsp*" security="none"/><!-- http元素的pattern属性指定当前的http对应的FilterChain将匹配哪些URL,如未指定将匹配所有的请求 --><security:http pattern="/admin/**"><security:intercept-url pattern="/**" access="ROLE_ADMIN"/></security:http><security:http><security:intercept-url pattern="/**" access="ROLE_USER"/></security:http>

每一个security:http都将对应一个SecurityFilterChain。每一个SecurityFilterChain中都包含各自的Filter

private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);List<Filter> filters = getFilters(fwRequest);if (filters == null || filters.size() == 0) {if (logger.isDebugEnabled()) {logger.debug(UrlUtils.buildRequestUrl(fwRequest) +(filters == null ? " has no matching filters" : " has an empty filter list"));}fwRequest.reset();chain.doFilter(fwRequest, fwResponse);return;}VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);vfc.doFilter(fwRequest, fwResponse);
}
  • 1 获取该request访问的url获取匹配的security:http,即获取匹配的SecurityFilterChain,然后返回该SecurityFilterChain的所有Filter
  • 2 上述Filter构建成一个FilterChain链
  • 3 执行上述FilterChain链

默认注册了如下Filter

  • SecurityContextPersistenceFilter

    在一开始进行request的时候就可以在SecurityContextHolder中建立一个SecurityContext,然后在请求结束的时候,任何对SecurityContext的改变都可以被copy到HttpSession

  • LogoutFilter

    检查是否是登出地址

  • UsernamePasswordAuthenticationFilter

    UsernamePasswordAuthenticationFilter会检查请求地址是不是配置的登陆地址,如果是则进行认证检查,从request中获取username和password构建成UsernamePasswordAuthenticationToken,然后使用AuthenticationManager authenticationManager来进行认证过程,认证成功之后重定向到target地址

  • BasicAuthenticationFilter

  • RequestCacheAwareFilter

    session中还可以保存着key为SPRING_SECURITY_SAVED_REQUEST的类型为DefaultSavedRequest的request信息,每次请求都要从request中获取session,如果没有则不会创建session,如果有session,则从session中获取DefaultSavedRequest的request信息,如果没有保存的话,则仍然为null,默认情况下,这个filter不起作用,因为没有向session中保存上述信息

  • SecurityContextHolderAwareRequestFilter

  • AnonymousAuthenticationFilter

    如果当前用户的SecurityContext中的认证信息为null的话,创建一个默认的角色

  • SessionManagementFilter

  • ExceptionTranslationFilter

    捕获之后程序执行中的AuthenticationException和AccessDeniedException。如果用户没有认证信息,则引导用户重定向到登陆界面,如果已经有登陆信息,则认为是权限不足,重定向到权限不足界面

  • FilterSecurityInterceptor

    保护每一个受访问的uri,一旦用户权限不足,则抛出AccessDeniedException异常

我们可以看到整个过程与session打交道的时候仅仅是在SecurityContextPersistenceFilter中,请求完成之后,会将SecurityContext信息保存到session中而已。供下次使用的时候,直接从session中获取SecurityContext信息,此时的SecurityContext信息中含有用户的认证信息,因此访问一个资源的话,上述filter都不会拦截,在FilterSecurityInterceptor中也会被正常通过。

一旦session失效,不能从session中获取SecurityContext信息了,就会在FilterSecurityInterceptor抛出AccessDeniedException异常,然后被ExceptionTranslationFilter捕获,引导用户重定向到登陆界面

转载于:https://my.oschina.net/pingpangkuangmo/blog/530381

SpringSecurity半成品笔记相关推荐

  1. 狂神SpringSecurity学习笔记(基础)

    文章目录 前言 一.为什么使用SpringSecurity 一.关于SpringSecurity 导入模板素材 定制首页 前言 笔记整理来源于狂神视频https://www.bilibili.com/ ...

  2. SpringSecurity 学习笔记分享 记录历程开篇

    基础翻译篇 官方文档地址 GitHub demo 代码 简介 Spring Security 是一个提供身份验证,授权,保护以及防止常见攻击的框架,由于同时支持响应式和命令式,是spring框架的安全 ...

  3. SpringSecurity学习笔记

    参考慕课网视频 文章目录 初识Spring Security 认证和授权的基本概念 过滤器和过滤器链 http请求的结构 http basic 认证 http响应的结构 开启security的debu ...

  4. B站三更草堂SpringSecurity学习笔记

    简介 Spring Security 是 Spring 家族中的一个安全管理框架.相比与另外一个安全框架 Shiro,它提供了更丰富的功能,社区资源也比 Shiro 丰富. 一般来说中大型的项目都是使 ...

  5. spring-security学习笔记--配置文件

    来源:http://blog.sina.com.cn/s/blog_7c3328d701013fct.html <?xml version="1.0" encoding=&q ...

  6. SpringSecurity学习笔记(三)自定义资源拦截规则以及登录界面跳转

    参考视频,编程不良人 由前面的学习可以知道,SS的默认的拦截规则很简单,我们在项目中实际使用的时候往往需要更加复杂的拦截规则,这个时候就需要自定义一些拦截规则. 自定义拦截规则 在我们的项目中,资源往 ...

  7. Java -考研 学习路线(笔记链接汇总)-个人用

    文章目录 1. Java 学习路线 1.1 JavaSE 1.1.1 C 语言基础 1.1.2 面对对象程序设计C++ 1.1.3 Java 基础 1) 基础 2) GUI 3) 网络编程 4) 多线 ...

  8. 项目1在线交流平台-7.构建安全高效的企业服务-2.使用Security自定义社区网页认证与授权

    文章目录 功能需求 一. 废弃登录检查的拦截器 二.授权配置 1. 导包 2. Security配置 2.1 `WebSecurity` 2.2`HttpSecurity` ` http.author ...

  9. 项目1在线交流平台-7.构建安全高效的企业服务-3. Security整合Kafka,ES,Thymeleaf实例-对帖子置顶、加精、删除

    文章目录 功能需求 一.置顶.加精.删除帖子功能的实现 1. dao层处理数据 接口定义 sal语句定义 2. service层业务处理 3. Controller层处理按钮事件异步请求 异步请求及k ...

  10. Spring Boot 框架学习笔记(五)( SpringSecurity安全框架 )

    Spring Boot 框架学习笔记(五) SpringSecurity安全框架 概述 作用 开发示例: 1. 新建项目 2. 引入依赖 3. 编写`SecurityConfig`类,实现认证,授权, ...

最新文章

  1. Java锁优化思路及JVM实现
  2. 北京python培训班价格-Python培训班多少钱?
  3. 分析开源项目源码,我们该如何入手分析?(授人以渔)
  4. css 盒模型的属性
  5. 汇量科技收购热云数据,加速SaaS工具生态布局
  6. linux安装ant环境变量,CentOS下Ant环境配置
  7. rk3288_Android7.1长按recovery按键5s之后恢复出厂设置
  8. VS2017IIS注册
  9. pytorch实现 求协方差、皮尔森相关系数(Pearson product-moment correlation coefficient)
  10. 干货资源共享之阿里云大学的学习路线和免费课程
  11. 开源中国正式进军软件开发众包领域
  12. 2022年湖南省中医执业医师考试第三单元医学针灸学模拟题
  13. 神武3登录显示未能成功连接服务器,T3安装成功后,点击运行显示登录界面,但提示“检测公共组件Protal。exe时未能通过,公共组件可能被破坏”,和”无法连接服务器“。应该怎么处理?求助!...
  14. 特种浓缩分离:染料纳滤膜脱盐浓缩技术
  15. 皮卡智能联手全球最大贸易服务商PingPong,共推AIGC应用落地服务
  16. Guava学习笔记:Google Guava 类库简介
  17. 树莓派 Pico Clion开发
  18. UnicodeDecodeError: ‘gbk‘ codec can‘t decode byte 0x9a in position 174: illegal multibyte sequence
  19. python — cnn+opencv 识别车牌
  20. 用Delphi做浏览器的经验

热门文章

  1. 8.15 SNAIL:神经注意力元学习
  2. 「三分钟系列03」3分钟看懂什么是三次握手/四次挥手
  3. 海量数据挖掘MMDS week3:流算法Stream Algorithms
  4. C语言断言assert详解
  5. Linux复制到home后自动删除,[rm] Linux 防止rm -rf / 误删除
  6. 提交失败重连java_RxJava出错重连
  7. 显卡算力排行2020_AMD正式发布RX6000系列显卡
  8. boost安装_Boost编译与使用
  9. IDEA 使用和问题总结
  10. Java学习之路 之 提问及解决篇