SpringSecurity概述
Spring Security是一个灵活、强大和可定制的身份验证和访问控制框架,以确保基于Spring的Java Web应用程序的安全。
Spring Security是基于Spring AOP和Servlet过滤器的安全框架。通过若干个过滤器,它们能够拦截Servlet请求,并将这些请求转给认证和访问决策管理器处理,增强应用的安全性。
相关特点:
- 对身份验证和授权提供全面可拓展的支持
- 可防御会话固定、点击劫持、csrf跨站请求伪造等攻击
- Servlet API集成
- 方便集成,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以零配置使用Spring Security
- …
工作方式
spring security内部其实是通过一个过滤器链来实现认证流程的:
常见的例如AuthenticationProcessingFilter处理用户登陆相关的操作,ExceptionTranslationFilter用于处理异常并返回对应的页面或者状态码,SessionFixationProtectionFilter用于防止csrf攻击,还有主要用户权限控制的FilterSecurityInterceptor等。
简单的过滤器链如下:
以认证授权流程为例,UsernamePasswordAuthenticationFilter用于拦截我们通过表单提交接口提交的用户名和密码,然后在AuthenticationProcessingFilter处理用户登陆认证相关的操作,最后的FilterSecurityInterceptor是首先判断我们当前请求的url是否需要认证,如果需要认证,那么就看当前请求是否已经认证,是的话就放行到我们要访问的接口,否则重定向到认证页面。
相关依赖
要使用Spring Security需要引入spring-security-web和spring-security-config:
在Spring Boot中使用Spring Security非常容易,引入spring-boot-starter-security依赖即可。
用户认证与授权
Spring Security中进行身份验证的是AuthenticationManager接口,ProviderManager是它的一个默认实现,但它并不用来处理身份认证,而是委托给配置好的AuthenticationProvider,每个AuthenticationProvider会轮流检查身份认证。检查后或者返回Authentication对象或者抛出异常。
Spring Security其支持在内存中设置身份验证,可以直接在配置文件中配置:
注解形式:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication().withUser("test").password("123456").roles("USER");
}
}
实际开发中用于验证的用户一般都存储在数据库中,上述方式明显不太合适,那么就需要在AuthenticationProvider中创建基于数据库自定义UserDetailsService进行相关的认证操作。下面是相应实现的流程:
验证身份就是加载响应的UserDetails,看看是否和用户输入的账号、密码、权限等信息匹配。此步骤由实现AuthenticationProvider的DaoAuthenticationProvider(它利用UserDetailsService验证用户名、密码和授权)处理。包含 GrantedAuthority 的 UserDetails对象在构建 Authentication对象时填入数据。
举例如下,因为Spring Security是通过委托AuthenticationProvider进行认证的,那么首先需要在其配置中注入userDetailsService():
若使用注解方式的话通过auth.userDetailsService()方法即可。
根据上述配置,可以直接定位到userDetailsService的具体实现在com.work.security.UserDetailsServiceImpl。
在loadUserByUsername()方法,根据username查询用户实体,可以实现该接口覆盖该方法,实现自定义获取用户过程。
public class UserDetailService implements UserDetailsService{
@Autowired
private IUserService userService;
@Overrid
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
UserDetails userDetails=null;
try{
com.security.entity.user user = this.userService.Login(username);
Collection<GrantedAuthority> authList = getAuthorities(user.getRole());
userDetails = new User(username,user.getPassword,true,true,true,true,authList);
}catch(Exception e){
throw new UsernameNotFoundException(username);
}
return userDetails;
}
private Collection<GrantedAuthority> getAuthorities(String role){
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
if(role.equals("ROLE_ADMIN")){
authList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}else{
authList.add(new SimpleGrantedAuthority("ROLE_STATIC));
}
return authList;
}
}
其他配置
除了认证授权以外,还有很多强大的过滤器可以进行相关的安全防护,例如csrf防护、单点登录限制等,均可以通过相关的标签或者方法进行实现:
权限控制方式
通过UserDetailsService完成用户认证以及权限角色赋予后,可以结合以下方式完成相关的权限控制:
通过HttpSecurity对象
Spring Security的配置文件命名空间配置中的<http>元素。它允许对特定的http请求基于安全考虑进行配置。
例如如下配置,pattern代表匹配的路径,security="none"表示放行,不进行拦截及权限检查:
<http pattern="/static/**" security="none"></http>
通过其子元素<intercept-url>可以对相关接口进行权限控制。例如/system下的所有接口仅仅只有ROLE_ADMIN,ROLE_USER权限的用户才可以访问,login接口支持匿名访问:
<intercept-url pattern="/system/**" access="ROLE_ADMIN,ROLE_USER" />
<intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
此外,还可以通过SpEL表达式进行配置,可以通过<http>元素的use-s来设置(默认为true,开启),例如下面的例子,用户匹配ROLE_ADMIN,ROLE_USER其中的任何一个均可放行:
<http auto-config="true" use-s="true">
<intercept-url pattern="/system/**" access="hasRole('ROLE_USER','ROLE_ADMIN')"/>
</http>
常用的有:
表达式 |
功能 |
hasRole([role]) |
用户拥有指定的角色role时候返回true |
hasAnyRole([role1,role2]) |
用户拥有role1,role2任意一个指定的角色时返回true |
permitAll |
任何用户均可访问 |
hasIpAddress (ipAddress) |
匹配一个请求的IP地址 |
denyAll |
任何用户都不可以访问 |
authenticated |
检查用户是否认证通过 |
anonymous |
匿名用户即可访问 |
除此之外,也可以使用@EnableWebSecurity注解,然后继承适配器WebSecurityConfigurerAdapter,通过HttpSecurity对象的相关配置进行权限控制。主要是通过http.authorizeRequests()
方法,通过每个子匹配器进行相关的权限声明。例如下面的例子:
protected void configure(HttpSecurity http ) throws Exception {
http.authorizeRequests()
.antMatchers( "/resources/**", "/signup" , "/about").permitAll()
.antMatchers( "/admin/**").hasRole("ADMIN" )
.antMatchers( "/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().authenticated()
.formLogin();
}
通过antMatchers
方法即可定义什么样的请求可以放过,什么样的请求需要验证。也可以通过regexMatchers()
方法通过正则表达式来定义请求路径。
通过相关标签
在实际业务场景中,对于已登陆的用户角色,需要根据不同的权限对view层进行保护,显示/隐藏Web应用程序的JSP/View。例如查看所有用户信息的页面仅仅只有管理员角色才可以查看。对于普通用户来说需要隐藏。
在jsp页面中我们可以使用spring security提供的权限标签来进行权限控制,需要引入spring security-taglibs标记库的依赖库:
<dependency>
<groupId>org.springwork.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
然后通过在jsp页面中引入标签库,通过对应的标签来实现:
<%@taglib uri="http://www.springwork.org/security/tags" prefix="security"%>
常见的标签如下:
标签 |
功能 |
<security:authenticatio> |
获取当前认证对象信息,例如用户名 |
<security:authorize> |
若当前用户满足特定权限,则显示标签范围的内容 |
<security:accesscontrollist> |
若认证用户具有权限列表中的某一个权限,那这个标签范围的内容将显示(一般用于鉴定ACL权限) |
例如下面的例子:
只有拥有ADMIN角色的用户才能查看相关的页面:
<%@ taglib prefix="sec" uri="http://www.springwork.org/security/tags"%>
<div>
<sec:authorize access="hasRole("ADMIN)">
......
<form name="contact-form" class="contact-form" method="post" action="./deleteUser/ByUserId">
......
</sec:authorize>
</div>
通过权限注解
Spring Security默认是禁用注解的,要想开启注解,有以下方式:
- 继承WebSecurityConfigurerAdapter并且加上@EnableGlobalMethodSecurity注解,并在该类中将AuthenticationManager定义为Bean:
例如下面是开启了secured的注解支持:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled=true)
public class CasSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
AuthenticationManager authenticationManager;
}
<security:global-method-security pre-post-annotations="enabled">
<security:global-method-security jsr250-annotations="enabled">
<security:global-method-security secured-annotations="enabled">
常见的注解:
开启@Secured 注解过滤权限,可以在需要安全[角色/权限等]的方法上指定 @Secured,并且只有那些角色/权限的用户才可以调用该方法。如果有人不具备要求的角色/权限但试图调用此方法,将会抛出AccessDenied异常。但是有一个缺点是,无法满足&&的关系(例如角色同时为admin和system时才能调用该接口)。
例如下面的例子:
import org.springwork.security.access.annotation.Secured;
public interface UserService{
@Secured("ROLE_ADMIN")
List<User> findAllUsers();
@Secured({"ROLE_ADMIN","ROLE_USER"})
User getUserByName(String name);
......
}
主要用于提供角色的权限约束,常用注解有:
注解 |
功能 |
@DenyAll |
拒绝所有访问 |
@PermitAll |
允许所有访问 |
@RolesAllowed({“USER”, “ADMIN”}) |
具有"USER", "ADMIN"任意一种权限就可以访问。 |
基于表达式的注解,可以自定义扩展。主要包括以下注解:
注解 |
功能 |
@PreAuthorize |
在方法调用之前,基于表达式的结果来限制对方法的访问 |
@PostAuthorize |
在方法执行后再给予表达式的结果进行权限验证,适合验证带有返回值的权限 |
@PostFilter |
在方法执行之后执行,这里可以调用方法的返回值,然后对返回值进行过滤或处理或修改并返回。 |
@PreFilter |
在方法执行之前执行,而且这里可以调用方法的参数,然后对参数值进行过滤或处理或修改。 |
比较常用的是@PreAuthorize和@PostAuthorize。
例如下面的例子:
通过@PostAuthorize注解确保登录用户只能获取他自己的用户对象防止平行越权,还有在调用删除用户的方法前先检查是否具有ADMIN角色权限:
import org.springwork.security.access.prepost.PostAuthorize;
import org.springwork.security.access.prepost.PreAuthorize;
......
public interface UserService{
@PostAuthorize("returnObject.type == authentication.name ")
User findById(int id);
@PreAuthorize("hasRole('ADMIN')")
boolean deleteUser(int id)
......
}
审计要点
权限控制相关的审计要点有:
平行越权
SpringSecurity可以通过如下方法获取当前用户:
SecurityContextHolder.getContext().getAuthentication()
一般来说,使用类似@PostAuthorize注解可以比较好的解决平行越权的问题,但是在多表关联的场景下很难进行覆盖。所以针对类似的业务接口还是需要进行相关的检查。
垂直越权
一般使用标签库对普通用户与管理员用户的在view层页面上进行了区分,但是没有细粒度覆盖接口,认为只要在界面上没法操作就可以防止垂直越权了。
例如下面的例子,通过<sec:authorize>标签,在view层进行了限制,仅仅拥有ADMIN角色的用户才能查看到删除用户对应的功能模块:
<div>
<sec:authorize access="hasRole('ADMIN')">
<form action="${pageContext.request.contextPath}/system/delete" method="POST">
.........
</form>
</sec:authorize>
</div>
可以直接定位/system/delete接口,结合配置文件查看对应的权限细粒度覆盖.
在安全配置时仅仅考虑到了未授权访问的问题:
<http use-="false">
<intercept-url pattern="login" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/system/**" access="ROLE_USER"/>
......
再查看实际的接口鉴权,可以看到接口处也没有任何的权限注解,也就是说只要通过登陆认证,以普通用户的角色也可以访问管理员删除用户的接口:
@RequestMapping("/delete")
public Boolean deleteUser(String userId){
Boolean result = userservice.deleteById(userId);
return result;
}
Tips:在进行审计时,可以先在view层搜索对应的SpringSecurity标签关键字(例如<sec:authorize>),然后查看对应隐藏/显示的接口,通过检查相关的权限配置以及接口具体实现,快速寻找垂直越权缺陷。
静态资源
一般静态资源和登陆接口可以非登陆状态下匿名使用,一般在对应的权限配置中会做如下配置:
<http pattern="/common/**" security="none"/>
<http pattern="/*.pdf" security="none"/>
或者是如下方式:
antMatchers("/","/login**", "/webjars*", "/static*").permitAll()
但是部分静态文件可能存在未授权访问,例如网站部分业务的模版文件、内部资料,或者是通过相关接口道出的统计Excel表、PDF单据等,都可能存在匿名下载的风险。
所以可以定位相关的目录,审计目录下的内容以及可能关联的接口。
权限绕过
同时还需要进行版本检查,避免相关的权限绕过:
- CVE-2010-3700: Spring Security bypass of security constraints
- 影响范围:(需部署在IBM WebSphere Application Server (WAS) 6.1 and 7.0容器中)
- Spring Security
- 3.0.0 to 3.0.3
- 2.0.0 to 2.0.5
- CVE-2016-5007 Spring Security / MVC Path Matching Inconsistency
- 影响范围
- Spring Security
- 具体分袖参考CVE-2016-5007,跟之前的shiro绕过权限控制原理类似。
- CVE-2016-9879 Encoded “/” in path variables
- 影响范围(需部署在IBMWebSphereApplication Server 8.5.x 的容器中)
- SpringSecurity
- 3.2.0 - 3.2.9
- 4.0.x - 4.1.3
- 4.2.0
- 旧的不维护的版本也会受到影响
- CVE-2018-1199: Security bypass with static resources
- 影响范围
- Spring Security
- 4.1.0 - 4.1.4
- 4.2.0 - 4.2.3
- 5.0.0
参考资料
https://docs.spring.io/spring-security/site/docs/current/reference/html5/
<h1><a id="SpringSecurity_0"></a>SpringSecurity概述</h1>
<p> Spring Security是一个灵活、强大和可定制的身份验证和访问控制框架,以确保基于Spring的Java Web应用程序的安全。</p>
<p> Spring Security是基于Spring AOP和Servlet过滤器的安全框架。通过若干个过滤器,它们能够拦截Servlet请求,并将这些请求转给认证和访问决策管理器处理,增强应用的安全性。</p>
<p> 相关特点:</p>
<ul>
<li>对身份验证和授权提供全面可拓展的支持</li>
<li>可防御会话固定、点击劫持、csrf跨站请求伪造等攻击</li>
<li>Servlet API集成</li>
<li>方便集成,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以零配置使用Spring Security</li>
<li>…</li>
</ul>
<h1><a id="_14"></a>工作方式</h1>
<p> spring security内部其实是通过一个过滤器链来实现认证流程的:<br />
<img src="https://www.redhatzone.com/img/sin/M00/00/2D/wKg0C17J5LSAKb8iAADlox5A1dU828.png" alt="permission_Audit14.png" /><br />
常见的例如AuthenticationProcessingFilter处理用户登陆相关的操作,ExceptionTranslationFilter用于处理异常并返回对应的页面或者状态码,SessionFixationProtectionFilter用于防止csrf攻击,还有主要用户权限控制的FilterSecurityInterceptor等。<br />
简单的过滤器链如下:<br />
以认证授权流程为例,UsernamePasswordAuthenticationFilter用于拦截我们通过表单提交接口提交的用户名和密码,然后在AuthenticationProcessingFilter处理用户登陆认证相关的操作,最后的FilterSecurityInterceptor是首先判断我们当前请求的url是否需要认证,如果需要认证,那么就看当前请求是否已经认证,是的话就放行到我们要访问的接口,否则重定向到认证页面。<br />
<img src="https://www.redhatzone.com/img/sin/M00/00/2D/wKg0C17J5SSAKk9EAAB4UpAWWOI086.png" alt="permission_Audit15.png" /></p>
<h1><a id="_21"></a>相关依赖</h1>
<p> 要使用Spring Security需要引入spring-security-web和spring-security-config:<br />
<img src="https://www.redhatzone.com/img/sin/M00/00/2D/wKg0C17J5ZaAMUwvAADKYEdXpgw980.png" alt="图片.png" /><br />
在Spring Boot中使用Spring Security非常容易,引入spring-boot-starter-security依赖即可。</p>
<h1><a id="_25"></a>用户认证与授权</h1>
<p> Spring Security中进行身份验证的是AuthenticationManager接口,ProviderManager是它的一个默认实现,但它并不用来处理身份认证,而是委托给配置好的AuthenticationProvider,每个AuthenticationProvider会轮流检查身份认证。检查后或者返回Authentication对象或者抛出异常。<br />
Spring Security其支持在内存中设置身份验证,可以直接在配置文件中配置:<br />
<img src="https://www.redhatzone.com/img/sin/M00/00/2D/wKg0C17J5deAWJ2sAABUSuulBQQ161.png" alt="图片.png" /><br />
注解形式:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-">@Configuration</span>
<span class="hljs-">@EnableWebSecurity</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-">SecurityConfiguration</span> <span class="hljs-keyword">extends</span> <span class="hljs-">WebSecurityConfigurerAdapter</span></span>{
<span class="hljs-">@Autowired</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-">configureGlobalSecurity</span><span class="hljs-params">(AuthenticationManagerBuilder auth)</span> <span class="hljs-keyword">throws</span> Exception</span>{
auth.inMemoryAuthentication().withUser(<span class="hljs-string">"test"</span>).password(<span class="hljs-string">"123456"</span>).roles(<span class="hljs-string">"USER"</span>);
}
}
</code></div></pre>
<p> 实际开发中用于验证的用户一般都存储在数据库中,上述方式明显不太合适,那么就需要在AuthenticationProvider中创建基于数据库自定义UserDetailsService进行相关的认证操作。下面是相应实现的流程:<br />
验证身份就是加载响应的UserDetails,看看是否和用户输入的账号、密码、权限等信息匹配。此步骤由实现AuthenticationProvider的DaoAuthenticationProvider(它利用UserDetailsService验证用户名、密码和授权)处理。包含 GrantedAuthority 的 UserDetails对象在构建 Authentication对象时填入数据。<br />
<img src="https://www.redhatzone.com/img/sin/M00/00/2D/wKg0C17J5e-AQ79dAACHiuAoRhs328.png" alt="图片.png" /><br />
举例如下,因为Spring Security是通过委托AuthenticationProvider进行认证的,那么首先需要在其配置中注入userDetailsService():<br />
<img src="https://www.redhatzone.com/img/sin/M00/00/2D/wKg0C17J5gGAH9k2AABy8Tfl71I982.png" alt="图片.png" /><br />
若使用注解方式的话通过auth.userDetailsService()方法即可。<br />
根据上述配置,可以直接定位到userDetailsService的具体实现在com.work.security.UserDetailsServiceImpl。<br />
在loadUserByUsername()方法,根据username查询用户实体,可以实现该接口覆盖该方法,实现自定义获取用户过程。</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-">UserDetailService</span> <span class="hljs-keyword">implements</span> <span class="hljs-">UserDetailsService</span></span>{
<span class="hljs-">@Autowired</span>
<span class="hljs-keyword">private</span> IUserService userService;
<span class="hljs-">@Overrid</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> UserDetails <span class="hljs-">loadUserByUsername</span><span class="hljs-params">(String username)</span> <span class="hljs-keyword">throws</span> UsernameNotFoundException</span>{
UserDetails userDetails=<span class="hljs-keyword">null</span>;
<span class="hljs-keyword">try</span>{
com.security.entity.user user = <span class="hljs-keyword">this</span>.userService.Login(username);
Collection<GrantedAuthority> authList = getAuthorities(user.getRole());
userDetails = <span class="hljs-keyword">new</span> User(username,user.getPassword,<span class="hljs-keyword">true</span>,<span class="hljs-keyword">true</span>,<span class="hljs-keyword">true</span>,<span class="hljs-keyword">true</span>,authList);
}<span class="hljs-keyword">catch</span>(Exception e){
<span class="hljs-comment">//<span class="hljs-doctag">TODO:</span>handle exception</span>
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UsernameNotFoundException(username);
}
<span class="hljs-keyword">return</span> userDetails;
}
<span class="hljs-comment">//获取当前登陆用户的角色权限</span>
<span class="hljs-function"><span class="hljs-keyword">private</span> Collection<GrantedAuthority> <span class="hljs-">getAuthorities</span><span class="hljs-params">(String role)</span></span>{
List<GrantedAuthority> authList = <span class="hljs-keyword">new</span> ArrayList<GrantedAuthority>();
<span class="hljs-keyword">if</span>(role.equals(<span class="hljs-string">"ROLE_ADMIN"</span>)){
authList.add(<span class="hljs-keyword">new</span> SimpleGrantedAuthority(<span class="hljs-string">"ROLE_ADMIN"</span>));
}<span class="hljs-keyword">else</span>{
authList.add(<span class="hljs-keyword">new</span> SimpleGrantedAuthority(<span class="hljs-string">"ROLE_STATIC));
}
return authList;
}
}
</span></code></div></pre>
<h1><a id="_81"></a>其他配置</h1>
<p> 除了认证授权以外,还有很多强大的过滤器可以进行相关的安全防护,例如csrf防护、单点登录限制等,均可以通过相关的标签或者方法进行实现:<br />
<img src="https://www.redhatzone.com/img/sin/M00/00/2D/wKg0C17J5jSAR5ywAABVF-psd30729.png" alt="permission_Audit17.png" /></p>
<h1><a id="_84"></a>权限控制方式</h1>
<p> 通过UserDetailsService完成用户认证以及权限角色赋予后,可以结合以下方式完成相关的权限控制:</p>
<h2><a id="HttpSecurity_86"></a>通过HttpSecurity对象</h2>
<p> Spring Security的配置文件命名空间配置中的<http>元素。它允许对特定的http请求基于安全考虑进行配置。<br />
例如如下配置,pattern代表匹配的路径,security="none"表示放行,不进行拦截及权限检查:</p>
<pre><div class="hljs"><code class="lang-"><span class="hljs-tag"><<span class="hljs-name">http</span> <span class="hljs-attr">pattern</span>=<span class="hljs-string">"/static/**"</span> <span class="hljs-attr">security</span>=<span class="hljs-string">"none"</span>></span><span class="hljs-tag"></<span class="hljs-name">http</span>></span>
</code></div></pre>
<p> 通过其子元素<intercept-url>可以对相关接口进行权限控制。例如/system下的所有接口仅仅只有ROLE_ADMIN,ROLE_USER权限的用户才可以访问,login接口支持匿名访问:</p>
<pre><div class="hljs"><code class="lang-"><span class="hljs-tag"><<span class="hljs-name">intercept-url</span> <span class="hljs-attr">pattern</span>=<span class="hljs-string">"/system/**"</span> <span class="hljs-attr">access</span>=<span class="hljs-string">"ROLE_ADMIN,ROLE_USER"</span> /></span>
<span class="hljs-tag"><<span class="hljs-name">intercept-url</span> <span class="hljs-attr">pattern</span>=<span class="hljs-string">"/login"</span> <span class="hljs-attr">access</span>=<span class="hljs-string">"IS_AUTHENTICATED_ANONYMOUSLY"</span>/></span>
</code></div></pre>
<p> 此外,还可以通过SpEL表达式进行配置,可以通过<http>元素的use-s来设置(默认为true,开启),例如下面的例子,用户匹配ROLE_ADMIN,ROLE_USER其中的任何一个均可放行:</p>
<pre><div class="hljs"><code class="lang-"><span class="hljs-tag"><<span class="hljs-name">http</span> <span class="hljs-attr">auto-config</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">use-s</span>=<span class="hljs-string">"true"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">intercept-url</span> <span class="hljs-attr">pattern</span>=<span class="hljs-string">"/system/**"</span> <span class="hljs-attr">access</span>=<span class="hljs-string">"hasRole('ROLE_USER','ROLE_ADMIN')"</span>/></span>
<span class="hljs-tag"></<span class="hljs-name">http</span>></span>
</code></div></pre>
<p> 常用的有:</p>
<table>
<thead>
<tr>
<th>表达式</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td>hasRole([role])</td>
<td>用户拥有指定的角色role时候返回true</td>
</tr>
<tr>
<td>hasAnyRole([role1,role2])</td>
<td>用户拥有role1,role2任意一个指定的角色时返回true</td>
</tr>
<tr>
<td>permitAll</td>
<td>任何用户均可访问</td>
</tr>
<tr>
<td>hasIpAddress (ipAddress)</td>
<td>匹配一个请求的IP地址</td>
</tr>
<tr>
<td>denyAll</td>
<td>任何用户都不可以访问</td>
</tr>
<tr>
<td>authenticated</td>
<td>检查用户是否认证通过</td>
</tr>
<tr>
<td>anonymous</td>
<td>匿名用户即可访问</td>
</tr>
</tbody>
</table>
<p> 除此之外,也可以使用@EnableWebSecurity注解,然后继承适配器WebSecurityConfigurerAdapter,通过HttpSecurity对象的相关配置进行权限控制。主要是通过<code>http.authorizeRequests()</code>方法,通过每个子匹配器进行相关的权限声明。例如下面的例子:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-">configure</span><span class="hljs-params">(HttpSecurity http )</span> <span class="hljs-keyword">throws</span> Exception </span>{
http.authorizeRequests()
.antMatchers( <span class="hljs-string">"/resources/**"</span>, <span class="hljs-string">"/signup"</span> , <span class="hljs-string">"/about"</span>).permitAll()<span class="hljs-comment">//permitAll代表这类资源任何用户均可访问</span>
.antMatchers( <span class="hljs-string">"/admin/**"</span>).hasRole(<span class="hljs-string">"ADMIN"</span> )<span class="hljs-comment">//只有具有ROLE_ADMIN角色的用户才能访问/admin开头的接口 </span>
.antMatchers( <span class="hljs-string">"/db/**"</span>).access(<span class="hljs-string">"hasRole('ADMIN') and hasRole('DBA')"</span>) <span class="hljs-comment">//同时拥有ROLE_ADMIN和ROLE_DBA角色的用户才能访问/db开头的接口</span>
.anyRequest().authenticated()<span class="hljs-comment">//没有匹配的请求只需要用户认证成功即可访问</span>
<span class="hljs-comment">// ...</span>
.formLogin();
}
</code></div></pre>
<p> 通过<code>antMatchers</code>方法即可定义什么样的请求可以放过,什么样的请求需要验证。也可以通过<code>regexMatchers()</code>方法通过正则表达式来定义请求路径。</p>
<h2><a id="_136"></a>通过相关标签</h2>
<p> 在实际业务场景中,对于已登陆的用户角色,需要根据不同的权限对view层进行保护,显示/隐藏Web应用程序的JSP/View。例如查看所有用户信息的页面仅仅只有管理员角色才可以查看。对于普通用户来说需要隐藏。</p>
<p> 在jsp页面中我们可以使用spring security提供的权限标签来进行权限控制,需要引入spring security-taglibs标记库的依赖库:</p>
<pre><div class="hljs"><code class="lang-"><span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springwork.security<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-security-taglibs<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>5.0.1.RELEASE<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
</code></div></pre>
<p> 然后通过在jsp页面中引入标签库,通过对应的标签来实现:</p>
<pre><div class="hljs"><code class="lang-java"><%<span class="hljs-">@taglib</span> uri=<span class="hljs-string">"http://www.springwork.org/security/tags"</span> prefix=<span class="hljs-string">"security"</span>%>
</code></div></pre>
<p> 常见的标签如下:</p>
<table>
<thead>
<tr>
<th>标签</th>
<th style="text-align:left">功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><security:authenticatio></td>
<td style="text-align:left">获取当前认证对象信息,例如用户名</td>
</tr>
<tr>
<td><security:authorize></td>
<td style="text-align:left">若当前用户满足特定权限,则显示标签范围的内容</td>
</tr>
<tr>
<td><security:accesscontrollist></td>
<td style="text-align:left">若认证用户具有权限列表中的某一个权限,那这个标签范围的内容将显示(一般用于鉴定ACL权限)</td>
</tr>
</tbody>
</table>
<p> 例如下面的例子:</p>
<p> 只有拥有ADMIN角色的用户才能查看相关的页面:</p>
<pre><div class="hljs"><code class="lang-java"><%@ taglib prefix=<span class="hljs-string">"sec"</span> uri=<span class="hljs-string">"http://www.springwork.org/security/tags"</span>%>
<div>
<sec:authorize access=<span class="hljs-string">"hasRole("</span>ADMIN)<span class="hljs-string">">
......
<form name="</span>contact-form<span class="hljs-string">" class="</span>contact-form<span class="hljs-string">" method="</span>post<span class="hljs-string">" action="</span>./deleteUser/ByUserId<span class="hljs-string">">
......
</sec:authorize>
</div>
</span></code></div></pre>
<h2><a id="_179"></a>通过权限注解</h2>
<p> Spring Security默认是禁用注解的,要想开启注解,有以下方式:</p>
<ul>
<li>继承WebSecurityConfigurerAdapter并且加上@EnableGlobalMethodSecurity注解,并在该类中将AuthenticationManager定义为Bean:</li>
</ul>
<p> 例如下面是开启了secured的注解支持:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-">@Configuration</span>
<span class="hljs-">@EnableWebSecurity</span>
<span class="hljs-">@EnableGlobalMethodSecurity</span>(securedEnabled=<span class="hljs-keyword">true</span>)
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-">CasSecurityConfiguration</span> <span class="hljs-keyword">extends</span> <span class="hljs-">WebSecurityConfigurerAdapter</span> </span>{
<span class="hljs-">@Autowired</span>
AuthenticationManager authenticationManager;
}
</code></div></pre>
<ul>
<li>在配置文件中开启注解支持:</li>
</ul>
<pre><div class="hljs"><code class="lang-java"><security:global-method-security pre-post-annotations=<span class="hljs-string">"enabled"</span>>
<security:global-method-security jsr250-annotations=<span class="hljs-string">"enabled"</span>>
<security:global-method-security secured-annotations=<span class="hljs-string">"enabled"</span>>
</code></div></pre>
<p> 常见的注解:</p>
<ul>
<li>securedEnabled</li>
</ul>
<p> 开启@Secured 注解过滤权限,可以在需要安全[角色/权限等]的方法上指定 @Secured,并且只有那些角色/权限的用户才可以调用该方法。如果有人不具备要求的角色/权限但试图调用此方法,将会抛出AccessDenied异常。但是有一个缺点是,无法满足&&的关系(例如角色同时为admin和system时才能调用该接口)。</p>
<p> 例如下面的例子:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-keyword">import</span> org.springwork.security.access.annotation.Secured;
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-">UserService</span></span>{
<span class="hljs-">@Secured</span>(<span class="hljs-string">"ROLE_ADMIN"</span>)<span class="hljs-comment">//具备ROLE_ADMIN角色的用户才可以访问</span>
<span class="hljs-function">List<User> <span class="hljs-">findAllUsers</span><span class="hljs-params">()</span></span>;
<span class="hljs-">@Secured</span>({<span class="hljs-string">"ROLE_ADMIN"</span>,<span class="hljs-string">"ROLE_USER"</span>})<span class="hljs-comment">//具备ROLE_ADMIN或者ROLE_USER角色的用户均可以访问。</span>
<span class="hljs-function">User <span class="hljs-">getUserByName</span><span class="hljs-params">(String name)</span></span>;
......
}
</code></div></pre>
<ul>
<li>jsr250Enabled</li>
</ul>
<p> 主要用于提供角色的权限约束,常用注解有:</p>
<table>
<thead>
<tr>
<th>注解</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td>@DenyAll</td>
<td>拒绝所有访问</td>
</tr>
<tr>
<td>@PermitAll</td>
<td>允许所有访问</td>
</tr>
<tr>
<td>@RolesAllowed({“USER”, “ADMIN”})</td>
<td>具有"USER", "ADMIN"任意一种权限就可以访问。</td>
</tr>
</tbody>
</table>
<ul>
<li>prePostEnabled</li>
</ul>
<p> 基于表达式的注解,可以自定义扩展。主要包括以下注解:</p>
<table>
<thead>
<tr>
<th>注解</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td>@PreAuthorize</td>
<td>在方法调用之前,基于表达式的结果来限制对方法的访问</td>
</tr>
<tr>
<td>@PostAuthorize</td>
<td>在方法执行后再给予表达式的结果进行权限验证,适合验证带有返回值的权限</td>
</tr>
<tr>
<td>@PostFilter</td>
<td>在方法执行之后执行,这里可以调用方法的返回值,然后对返回值进行过滤或处理或修改并返回。</td>
</tr>
<tr>
<td>@PreFilter</td>
<td>在方法执行之前执行,而且这里可以调用方法的参数,然后对参数值进行过滤或处理或修改。</td>
</tr>
</tbody>
</table>
<p> 比较常用的是@PreAuthorize和@PostAuthorize。</p>
<p> 例如下面的例子:</p>
<p> 通过@PostAuthorize注解确保登录用户只能获取他自己的用户对象防止平行越权,还有在调用删除用户的方法前先检查是否具有ADMIN角色权限:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-keyword">import</span> org.springwork.security.access.prepost.PostAuthorize;
<span class="hljs-keyword">import</span> org.springwork.security.access.prepost.PreAuthorize;
......
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-">UserService</span></span>{
<span class="hljs-">@PostAuthorize</span>(<span class="hljs-string">"returnObject.type == authentication.name "</span>)
<span class="hljs-function">User <span class="hljs-">findById</span><span class="hljs-params">(<span class="hljs-keyword">int</span> id)</span></span>;
<span class="hljs-">@PreAuthorize</span>(<span class="hljs-string">"hasRole('ADMIN')"</span>)
<span class="hljs-function"><span class="hljs-keyword">boolean</span> <span class="hljs-">deleteUser</span><span class="hljs-params">(<span class="hljs-keyword">int</span> id)</span>
......
}
</span></code></div></pre>
<h1><a id="_274"></a>审计要点</h1>
<p> 权限控制相关的审计要点有:</p>
<h2><a id="_276"></a>平行越权</h2>
<p> SpringSecurity可以通过如下方法获取当前用户:</p>
<pre><div class="hljs"><code class="lang-java">SecurityContextHolder.getContext().getAuthentication()
</code></div></pre>
<p> 一般来说,使用类似@PostAuthorize注解可以比较好的解决平行越权的问题,但是在多表关联的场景下很难进行覆盖。所以针对类似的业务接口还是需要进行相关的检查。</p>
<h2><a id="_283"></a>垂直越权</h2>
<p> 一般使用标签库对普通用户与管理员用户的在view层页面上进行了区分,但是没有细粒度覆盖接口,认为只要在界面上没法操作就可以防止垂直越权了。<br />
例如下面的例子,通过<sec:authorize>标签,在view层进行了限制,仅仅拥有ADMIN角色的用户才能查看到删除用户对应的功能模块:</p>
<pre><div class="hljs"><code class="lang-java"><div>
<sec:authorize access=<span class="hljs-string">"hasRole('ADMIN')"</span>>
<form action=<span class="hljs-string">"${pageContext.request.contextPath}/system/delete"</span> method=<span class="hljs-string">"POST"</span>>
.........
</form>
</sec:authorize>
</div>
</code></div></pre>
<p> 可以直接定位/system/delete接口,结合配置文件查看对应的权限细粒度覆盖.<br />
在安全配置时仅仅考虑到了未授权访问的问题:</p>
<pre><div class="hljs"><code class="lang-"><span class="hljs-tag"><<span class="hljs-name">http</span> <span class="hljs-attr">use-</span>=<span class="hljs-string">"false"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">intercept-url</span> <span class="hljs-attr">pattern</span>=<span class="hljs-string">"login"</span> <span class="hljs-attr">access</span>=<span class="hljs-string">"IS_AUTHENTICATED_ANONYMOUSLY"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">intercept-url</span> <span class="hljs-attr">pattern</span>=<span class="hljs-string">"/system/**"</span> <span class="hljs-attr">access</span>=<span class="hljs-string">"ROLE_USER"</span>/></span>
......
</code></div></pre>
<p> 再查看实际的接口鉴权,可以看到接口处也没有任何的权限注解,也就是说只要通过登陆认证,以普通用户的角色也可以访问管理员删除用户的接口:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-">@RequestMapping</span>(<span class="hljs-string">"/delete"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> Boolean <span class="hljs-">deleteUser</span><span class="hljs-params">(String userId)</span></span>{
Boolean result = userservice.deleteById(userId);
<span class="hljs-keyword">return</span> result;
}
</code></div></pre>
<p><strong>Tips:在进行审计时,可以先在view层搜索对应的SpringSecurity标签关键字(例如<sec:authorize>),然后查看对应隐藏/显示的接口,通过检查相关的权限配置以及接口具体实现,快速寻找垂直越权缺陷</strong>。</p>
<h2><a id="_314"></a>静态资源</h2>
<p> 一般静态资源和登陆接口可以非登陆状态下匿名使用,一般在对应的权限配置中会做如下配置:</p>
<pre><div class="hljs"><code class="lang-"><span class="hljs-tag"><<span class="hljs-name">http</span> <span class="hljs-attr">pattern</span>=<span class="hljs-string">"/common/**"</span> <span class="hljs-attr">security</span>=<span class="hljs-string">"none"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">http</span> <span class="hljs-attr">pattern</span>=<span class="hljs-string">"/*.pdf"</span> <span class="hljs-attr">security</span>=<span class="hljs-string">"none"</span>/></span>
</code></div></pre>
<p> 或者是如下方式:</p>
<pre><div class="hljs"><code class="lang-java">antMatchers(<span class="hljs-string">"/"</span>,<span class="hljs-string">"/login**"</span>, <span class="hljs-string">"/webjars*"</span>, <span class="hljs-string">"/static*"</span>).permitAll()
</code></div></pre>
<p> 但是部分静态文件可能存在未授权访问,例如网站部分业务的模版文件、内部资料,或者是通过相关接口道出的统计Excel表、PDF单据等,都可能存在匿名下载的风险。</p>
<p> 所以可以定位相关的目录,审计目录下的内容以及可能关联的接口。</p>
<h2><a id="_329"></a>权限绕过</h2>
<p> 同时还需要进行版本检查,避免相关的权限绕过:</p>
<ul>
<li>CVE-2010-3700: Spring Security bypass of security constraints
<ul>
<li>影响范围:(需部署在IBM WebSphere Application Server (WAS) 6.1 and 7.0容器中)
<ul>
<li>Spring Security
<ul>
<li>3.0.0 to 3.0.3</li>
<li>2.0.0 to 2.0.5</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>CVE-2016-5007 Spring Security / MVC Path Matching Inconsistency
<ul>
<li>影响范围
<ul>
<li>Spring Security
<ul>
<li>3.2.x,4.0.x,4.1.0</li>
</ul>
</li>
<li>具体分袖参考<a href="https://www.anquanke.com/post/id/84926" target="_blank">CVE-2016-5007</a>,跟之前的shiro绕过权限控制原理类似。</li>
</ul>
</li>
</ul>
</li>
<li>CVE-2016-9879 Encoded “/” in path variables
<ul>
<li>影响范围(需部署在IBMWebSphereApplication Server 8.5.x 的容器中)
<ul>
<li>SpringSecurity
<ul>
<li>3.2.0 - 3.2.9</li>
<li>4.0.x - 4.1.3</li>
<li>4.2.0</li>
<li>旧的不维护的版本也会受到影响</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>CVE-2018-1199: Security bypass with static resources
<ul>
<li>影响范围
<ul>
<li>Spring Security
<ul>
<li>4.1.0 - 4.1.4</li>
<li>4.2.0 - 4.2.3</li>
<li>5.0.0</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1><a id="_356"></a>参考资料</h1>
<p><a href="https://docs.spring.io/spring-security/site/docs/current/reference/html5/" target="_blank">https://docs.spring.io/spring-security/site/docs/current/reference/html5/</a></p>
责任编辑:
声明:本平台发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。