【SpringSecurity】SpringSecurity基础

2022年6月3日08:09:40

框架简介

概要

一般来说,Web应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是Spring Security重要核心功能。

(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录

(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。

一般来说,常见的安全管理技术栈的组合是这样的:
SSM + Shiro
Spring Boot/Spring Cloud + Spring Security

权限管理相关概念

主体
英文单词:principal
使用系统的用户或设备或从其他系统远程登录的用户等等。简单说就是谁使用系统谁就是主体。

认证
英文单词:authentication
权限管理系统确认一个主体的身份,允许主体进入系统。简单说就是“主体”证明自己是谁。
笼统的认为就是以前所做的登录操作。

授权
英文单词:authorization
将操作系统的“权力”“授予”“主体”,这样主体就具备了操作系统中特定功能的能力。
所以简单来说,授权就是给用户分配权限。
完成权限管理需要三个对象
用户:主要包含用户名、密码和当前用户的角色信息,可实现认证操作。
角色:主要包含角色名称,角色描述和当前角色拥有的权限信息,可实现授权操作。
权限:权限也可以称为菜单,主要包含当前权限名称,url地址等信息,可实现动态展示菜单。
注:这三个对象中,用户与角色是多对多的关系,角色与权限是多对多的关系,用户与权限没有直接关系,二者是通过角色来建立关联关系的。

二、初识SpringSecurity

2.1 SpringSecurity概念

Spring Security是spring采用AOP思想,基于servlet过滤器实现的安全框架。它提供了完善的认证机制和方法级的授权功能。

2.2 入门案例

1、Spring Security主要jar包功能介绍
spring-security-core.jar
核心包,任何Spring Security功能都需要此包。
spring-security-web.jar
web工程必备,包含过滤器和相关的Web安全基础结构代码。
spring-security-config.jar
用于解析xml配置文件,用到Spring Security的xml配置文件的就要用到此包。
spring-security-taglibs.jar
Spring Security提供的动态标签库,jsp页面可以用。
2、配置web.xml<!--Spring Security过滤器链,注意过滤器名称必须叫springSecurityFilterChain--><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>
3、spring-security.xml<!--设置可以用spring的el表达式配置Spring Security并自动生成对应配置组件(过滤器)
	auto-config="true"  表示自动加载springsecurity的配置文件
    use-expressions="true" 表示使用spring的el表达式来配置springsecurity
--><security:httpauto-config="true"use-expressions="true"><!--使用spring的el表达式来指定项目所有资源访问都必须有ROLE_USER或ROLE_ADMIN角色--><security:intercept-urlpattern="/**"access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/></security:http><!--设置Spring Security认证用户信息的来源--><security:authentication-manager><security:authentication-provider><security:user-service><security:username="user"password="{noop}user"authorities="ROLE_USER"/><security:username="admin"password="{noop}admin"authorities="ROLE_ADMIN"/></security:user-service></security:authentication-provider></security:authentication-manager>

三、过滤器链

3.1 常用过滤器介绍

1. context.SecurityContextPersistenceFilter
SecurityContextPersistenceFilter主要是使用SecurityContextRepository在session中保存或更新一个SecurityContext,并将SecurityContext给以后的过滤器使用,来为后续filter建立所需的上下文。SecurityContext中存储了当前用户的认证以及权限信息。
2. context.request.async.WebAsyncManagerIntegrationFilter
此过滤器用于集成SecurityContext到Spring异步执行机制中的WebAsyncManager

3. header.HeaderWriterFilter
向请求的Header中添加相应的信息,可在http标签内部使用security:headers来控制

4. csrf.CsrfFilter
csrf又称跨域请求伪造,SpringSecurity会对所有post请求验证是否包含系统生成的csrf的token信息,如果不包含,则报错。起到防止csrf攻击的效果。

5. authentication.logout.LogoutFilter
匹配URL为/logout的请求,实现用户退出,清除认证信息。

6. authentication.UsernamePasswordAuthenticationFilter
认证操作全靠这个过滤器,默认匹配URL为/login且必须为POST请求。

7. authentication.ui.DefaultLoginPageGeneratingFilter
如果没有在配置文件中指定认证页面,则由该过滤器生成一个默认认证页面。

8. authentication.ui.DefaultLogoutPageGeneratingFilter
由此过滤器可以生产一个默认的退出登录页面

9. authentication.www.BasicAuthenticationFilter
此过滤器会自动解析HTTP请求中头部名字为Authentication,且以Basic开头的头信息。

10. savedrequest.RequestCacheAwareFilter
通过HttpSessionRequestCache内部维护了一个RequestCache,用于缓存HttpServletRequest

11. servletapi.SecurityContextHolderAwareRequestFilter
针对ServletRequest进行了一次包装,使得request具有更加丰富的API

12. authentication.AnonymousAuthenticationFilter
当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder中。
spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。

13. session.SessionManagementFilter
SecurityContextRepository限制同一用户开启多个会话的数量

14. access.ExceptionTranslationFilter
异常转换过滤器位于整个springSecurityFilterChain的后方,用来转换整个链路中出现的异常

15. access.intercept.FilterSecurityInterceptor
获取所配置资源访问的授权信息,根据SecurityContextHolder中存储的用户信息来决定其是否有权限。

3.2 过滤器链加载原理

1、DelegatingFilterProxy
我们在web.xml中配置了一个名称为springSecurityFilterChain的过滤器DelegatingFilterProxy,接下我直接对DelegatingFilterProxy源码里重要代码进行说明,其中删减掉了一些不重要的代码,大家注意我写的注释就行了!
publicclassDelegatingFilterProxyextendsGenericFilterBean{@NullableprivateString contextAttribute;@NullableprivateWebApplicationContext webApplicationContext;@NullableprivateString targetBeanName;privateboolean targetFilterLifecycle;@NullableprivatevolatileFilter delegate;//注:这个过滤器才是真正加载的过滤器privatefinalObject delegateMonitor;//注:doFilter才是过滤器的入口,直接从这看!publicvoiddoFilter(ServletRequest request,ServletResponse response,FilterChain filterChain)throwsServletException,IOException{Filter delegateToUse=this.delegate;if(delegateToUse==null){synchronized(this.delegateMonitor){
                delegateToUse=this.delegate;if(delegateToUse==null){WebApplicationContext wac=this.findWebApplicationContext();if(wac==null){thrownewIllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");}//第一步:doFilter中最重要的一步,初始化上面私有过滤器属性
                    delegate delegateToUse=this.initDelegate(wac);}this.delegate= delegateToUse;}}//第三步:执行FilterChainProxy过滤器this.invokeDelegate(delegateToUse, request, response, filterChain);}//第二步:直接看最终加载的过滤器到底是谁protectedFilterinitDelegate(WebApplicationContext wac)throwsServletException{//debug得知targetBeanName为:springSecurityFilterChainString targetBeanName=this.getTargetBeanName();Assert.state(targetBeanName!=null,"No target bean name set");//debug得知delegate对象为:FilterChainProxyFilter delegate=(Filter) wac.getBean(targetBeanName,Filter.class);if(this.isTargetFilterLifecycle()){
            delegate.init(this.getFilterConfig());}return delegate;}protectedvoidinvokeDelegate(Filter delegate,ServletRequest request,ServletResponse response,FilterChain filterChain)throwsServletException,IOException{
        delegate.doFilter(request, response, filterChain);}}
第二步debug结果如下:
由此可知,DelegatingFilterProxy通过springSecurityFilterChain这个名称,得到了一个FilterChainProxy过滤器,最终在第三步执行了这个过滤器。

【SpringSecurity】SpringSecurity基础

2、 FilterChainProxy
publicclassFilterChainProxyextendsGenericFilterBean{privatestaticfinalLog logger=LogFactory.getLog(FilterChainProxy.class);privatestaticfinalString FILTER_APPLIED=FilterChainProxy.class.getName().concat(".APPLIED");privateList<SecurityFilterChain> filterChains;privateFilterChainProxy.FilterChainValidator filterChainValidator;privateHttpFirewall firewall;//咿!?可以通过一个叫SecurityFilterChain的对象实例化出一个FilterChainProxy对象//这FilterChainProxy又是何方神圣?会不会是真正的过滤器链对象呢?先留着这个疑问!publicFilterChainProxy(SecurityFilterChain chain){this(Arrays.asList(chain));}//又是SecurityFilterChain这家伙!嫌疑更大了!publicFilterChainProxy(List<SecurityFilterChain> filterChains){this.filterChainValidator=newFilterChainProxy.NullFilterChainValidator();this.firewall=newStrictHttpFirewall();this.filterChains= filterChains;}//注:直接从doFilter看publicvoiddoFilter(ServletRequest request,ServletResponse response,FilterChain chain)throwsIOException,ServletException{boolean clearContext= request.getAttribute(FILTER_APPLIED)==null;if(clearContext){try{
                request.setAttribute(FILTER_APPLIED,Boolean.TRUE);this.doFilterInternal(request, response, chain);}finally{SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);}}else{//第一步:具体操作调用下面的doFilterInternal方法了this.doFilterInternal(request, response, chain);}}privatevoiddoFilterInternal(ServletRequest request,ServletResponse response,FilterChain chain)throwsIOException,ServletException{FirewalledRequest fwRequest=this.firewall.getFirewalledRequest((HttpServletRequest) request);HttpServletResponse fwResponse=this.firewall.getFirewalledResponse((HttpServletResponse) response);//第二步:封装要执行的过滤器链,那么多过滤器就在这里被封装进去了!List<Filter> filters=this.getFilters((HttpServletRequest) fwRequest);if(filters!=null&& filters.size()!=0){FilterChainProxy.VirtualFilterChain vfc=newFilterChainProxy.VirtualFilterChain(
                fwRequest,
                chain,
                filters);//第四步:加载过滤器链
            vfc.doFilter(fwRequest, fwResponse);}else{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);}}privateList<Filter>getFilters(HttpServletRequest request){Iterator var2=this.filterChains.iterator();//第三步:封装过滤器链到SecurityFilterChain中!SecurityFilterChain chain;do{if(!var2.hasNext()){returnnull;}
            chain=(SecurityFilterChain) var2.next();}while(!chain.matches(request));return chain.getFilters();}}
第二步debug结果如下图所示,惊不惊喜?十五个过滤器都在这里了!
再看第三步,怀疑这么久!原来这些过滤器还真是都被封装进SecurityFilterChain中了。
3、SecurityFilterChain
最后看SecurityFilterChain,这是个接口,实现类也只有一个,这才是web.xml中配置的过滤器链对象!
//接口publicinterfaceSecurityFilterChain{booleanmatches(HttpServletRequest var1);List<Filter>getFilters();}
//实现类publicfinalclassDefaultSecurityFilterChainimplementsSecurityFilterChain{privatestaticfinalLog logger=LogFactory.getLog(DefaultSecurityFilterChain.class);privatefinalRequestMatcher requestMatcher;privatefinalList<Filter> filters;publicDefaultSecurityFilterChain(RequestMatcher requestMatcher,Filter... filters){this(requestMatcher,Arrays.asList(filters));}publicDefaultSecurityFilterChain(RequestMatcher requestMatcher,List<Filter> filters){
        logger.info("Creating filter chain: "+ requestMatcher+", "+ filters);this.requestMatcher= requestMatcher;this.filters=newArrayList(filters);}publicRequestMatchergetRequestMatcher(){returnthis.requestMatcher;}publicList<Filter>getFilters(){returnthis.filters;}publicbooleanmatches(HttpServletRequest request){returnthis.requestMatcher.matches(request);}publicStringtoString(){return"[ "+this.requestMatcher+", "+this.filters+"]";}}

四、自定义认证页面

4.1 配置信息

<!--释放静态资源,不让它们被拦截,不同于login.jsp的拦截,因为这些资源不用认证--><security:httppattern="/css/**"security="none"/><security:httppattern="/img/**"security="none"/><security:httppattern="/plugins/**"security="none"/><security:httppattern="/failer.jsp"security="none"/><!--设置可以用spring的el表达式配置Spring Security并自动生成对应配置组件(过滤器)--><security:httpauto-config="true"use-expressions="true"><!--指定login.jsp页面可以被匿名访问,即放行该页面--><security:intercept-urlpattern="/login.jsp"access="permitAll()"/><!--使用spring的el表达式来指定项目所有资源访问都必须有ROLE_USER或ROLE_ADMIN角色--><security:intercept-urlpattern="/**"access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/><!--指定自定义的认证页面
		login-processing-url:处理器地址
		default-target-url:通过后默认要去的地址
		authentication-failure-url:认证失败
	--><security:form-loginlogin-page="/login.jsp"login-processing-url="/login"default-target-url="/index.jsp"authentication-failure-url="/failer.jsp"/><!--指定退出登录后跳转的页面--><security:logoutlogout-url="/logout"logout-success-url="/login.jsp"/></security:http>

4.2 csrf防护机制

CSRF(Cross-site request forgery)跨站请求伪造,是一种难以防范的网络攻击方式。
1、CsrfFilter过滤器说明
通过源码分析,我们明白了,自己的认证页面,请求方式为POST,但却没有携带token,所以才出现了403权限不足的异常。那么如何处理这个问题呢?
方式一:直接禁用csrf,不推荐。
方式二:在认证页面携带token请求。
publicfinalclassCsrfFilterextendsOncePerRequestFilter{publicstaticfinalRequestMatcher DEFAULT_CSRF_MATCHER=newCsrfFilter.DefaultRequiresCsrfMatcher();privatefinalLog logger=LogFactory.getLog(this.getClass());privatefinalCsrfTokenRepository tokenRepository;privateRequestMatcher requireCsrfProtectionMatcher;privateAccessDeniedHandler accessDeniedHandler;publicCsrfFilter(CsrfTokenRepository csrfTokenRepository){this.requireCsrfProtectionMatcher= DEFAULT_CSRF_MATCHER;this.accessDeniedHandler=newAccessDeniedHandlerImpl();Assert.notNull(csrfTokenRepository,"csrfTokenRepository cannot be null");this.tokenRepository= csrfTokenRepository;}//通过这里可以看出SpringSecurity的csrf机制把请求方式分成两类来处理protectedvoiddoFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain)throwsServletException,IOException{
        request.setAttribute(HttpServletResponse.class.getName(), response);CsrfToken csrfToken=this.tokenRepository.
  • 作者:_青昔_
  • 原文链接:https://blog.csdn.net/qq_44530108/article/details/123758428
    更新时间:2022年6月3日08:09:40 ,共 11330 字。