Spring Security授权管理
授权是Spring Security的核心功能之一,是根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则可正常访问,没有访问的权限时则会被拒绝访问。认证是为了保证用户身份的合法性,而授权则是为了更细粒度地对隐私数据进行划分,授权是在认证通过后发生的,以控制不同的用户访问不同的资源。Spring Security提供了授权方法,开发者通过这些方法进行用户访问控制.
Spring Security授权流程
实现授权需要对用户的访问进行拦截校验,校验用户的权限是否可以操作指定的资源。Spring Security使用标准Filter建立了对Web请求的拦截,最终实现对资源的授权访问。
①拦截请求。已认证用户访问受保护的Web资源将被SecurityFilterChain中FilterSecurityInterceptor实例对象拦截。
②获取资源访问策略。FilterSecurityInterceptor实例对象会通过SecurityMetadataSource的子类DefaultFilterInvocationSecurityMetadataSource实例对象中获取要访问当前资源所需要的权限,权限封装在Collection实例对象中。 SecurityMetadataSource是读取访问策略的抽象,具体读取的内容,就是开发者配置的访问规则。
③FilterSecurityInterceptor通过AccessDecisionManager进行授权决策,若决策通过,则允许访问资 源,否则将禁止访问。AccessDecisionManager中包含一系列AccessDecisionVoter,可对当前认证过的身份是否有权访问对应的资源进行投票,AccessDecisionManager根据投票结果做出最终决策。
Spring Security自定义授权
根据授权的位置和形式,通常可以将授权的方式分为Web授权和方法授权,这两种授权方式都会调用AccessDecisionManager进行授权决策。下面分别对这两种自定义授权的方式进行讲解。
1.Web授权
Spring Security的底层实现本质是通过多个Filter形成的过滤器链完成,过滤器链中提供了默认的安全拦截机制,设置安全拦截规则,以控制用户的访问。HttpSecurity是SecurityBuilder接口的实现类,是HTTP安全相关的构建器,Spring Security中可以通过HttpSecurity对象设置安全拦截规则,并通过该对象构建过滤器链。
HttpSecurity可以根据不同的业务场景,对不同的URL采用不同的权限处理策略。当开发者需要配置项目的安全拦截规则时,可以调用HttpSecurity对象对应的方法实现。
HttpSecurity类的常用方法
方法 | 作用 |
authorizeRequests() | 开启基于HttpServletRequest请求访问的限制 |
formLogin() | 开启基于表单的用户登录 |
httpBasic() | 开启基于HTTP请求的Basic认证登录 |
logout() | 开启退出登录的支持 |
sessionManagement() | 开启Session管理配置 |
rememberMe() | 开启“记住我”功能 |
csrf() | 配置CSRF跨站请求伪造防护功能 |
通过authorizeRequests()方法可以添加用户请求控制的规则,这些规则通过用户请求控制的相关方法指定。
用户请求控制的常用方法
方法 | 作用 |
antMatchers(String... antPatterns) | 开启Ant风格的路径匹配 |
mvcMatchers(String... patterns) | 开启MVC风格的路径匹配,与Ant风格类似 |
regexMatchers(String... regexPatterns) | 开启正则表达式的路径匹配 |
and() | 功能连接符 |
anyRequest() | 匹配任何请求 |
rememberMe() | 开启“记住我”功能 |
access(String attribute) | 使用基于SpEL表达式的角色进行匹配 |
方法 | 作用 |
hasAnyRole(String... roles) | 匹配用户是否有参数中的任意角色 |
hasRole(String role) | 匹配用户是否有某一个角色 |
hasAnyAuthority(String... authorities) | 匹配用户是否有参数中的任意权限 |
hasAuthority(String authority) | 匹配用户是否有某一个权限 |
authenticated() | 匹配已经登录认证的用户 |
fullyAuthenticated() | 匹配完整登录认证的用户(非rememberMe登录用户) |
hasIpAddress(String ipaddressExpression) | 匹配某IP地址的访问请求 |
permitAll() | 无条件对请求进行放行 |
通过HttpSecurity类的formLogin()方法开启基于表单的用户登录后,可以指定表单认证的相关设置。
基于表单的身份验证的常见方法
方法 | 作用 |
loginPage(String loginPage) | 指定自定义登录界面,不使用SpringSecurity默认登录界面 |
loginProcessingUrl(String loginProcessingUrl) | 指定处理登录的请求url,为表单提交用户信息的Action |
successForwardUrl(String forwardUrl) | 指定登录成功后默认跳转的路径 |
下面通过案例演示在Spring Boot项目中使用Spring Security的Web授权方式进行权限管理。
(1)导入登录页面
在项目的resources目录的templates文件夹中导入自定义的登录页面login.html。
<html lang="en" xmlns:th="http://www.thymeleaf.org"><head><title>图书管理系统</title><link rel="stylesheet" type="text/css" th:href="@{/css/webbase.css}"><link rel="stylesheet" type="text/css" th:href="@{/css/pages-login-manage.css}"></head><body><div class="loginmanage"><div class="py-container"><h4 class="manage-title">图书管理系统</h4><div class="loginform"><ul class="sui-nav nav-tabs tab-wraped"><li class="active"><h3>账户登录</h3></li></ul><div class="tab-content tab-wraped"><div id="profile" class="tab-pane active"><form id="loginform" class="sui-form" th:action="@{/doLogin}" method="post"><div class="input-prepend"><span class="add-on loginname">用户名</span><input type="text" placeholder="用户名" class="span2 input-xfat" name="username"></div><div class="input-prepend"><span class="add-on loginpwd">密码</span><input type="password" placeholder="请输入密码" class="span2 input-xfat" name="password"></div><div class="logined"><a class="sui-btn btn-block btn-xlarge btn-danger"href='javascript:document:loginform.submit();' target="_self">登 录</a></div></form></div></div></div></div></div></body></html>
(2)编辑配置类
在项目的WebSecurityConfig配置类中使用HttpSecurity对象设置安全拦截规则,并创建SecurityFilterChain对象交由Spring管理
@Configuration
public class WebSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
http.authorizeRequests() // 定义哪些URL需要被保护、哪些不需要被保护
.mvcMatchers("/loginview","/css/**","/img/**").permitAll()
.mvcMatchers("/book/admin/**").hasRole("ADMIN")
.anyRequest().authenticated() // 任何请求,登录后可以访问
.and()
.formLogin()
.loginPage("/loginview")
.loginProcessingUrl("/doLogin")
.and()
.csrf().disable()//禁止csrf 跨站请求保护;
.headers().frameOptions().sameOrigin();
return http.build();
}
}
在WebMvcConfig配置类中添加loginview的视图映射
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("main");
registry.addViewController("/loginview").setViewName("login");
}
}
(3)测试效果
启动项目,在浏览器中通过“http://localhost:8080/”访问项目首页。
在登录页面使用zhangsan的用户信息进行登录。
图书管理需要角色为ROLE_ADMIN的用户才可以访问,用户zhangsan的角色为ROLE_COMMON,在后台首页单击“图书管理”链接。
使用用户lisi进行登录,lisi对应的角色为ROLE_ADMIN,登录成功再次访问“图书管理”。
2.方法授权
Spring Security除了可以在配置类中通过创建过滤器链设置安全拦截规则外,还可以使用@Secured、@RolesAllowed和@PreAuthorize注解控制类中所有方法或者单独某个方法的访问权限,以实现对访问进行授权管理。
使用@Secured和@RolesAllowed注解时,只需在注解中指定访问当前注解标注的类或方法所需要具有的角色,允许多个角色访问时,使用大括号对角色信息进行包裹,角色信息之间使用分号分隔即可。
@RequestMapping("list")
@Secured({"ROLE_ADMIN","ROLE_COMMON"})
public String findList() {return "book_list";
}
@RequestMapping("admin/manag")
@RolesAllowed("ROLE_ADMIN")
public String findManagList() {return "book_manag";
}
@PreAuthorize注解会在方法执行前进行权限验证,支持SpEL表达式。
@RequestMapping("list")
@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_COMMON')")
public String findList() {return "book_list";
}
@RequestMapping("admin/manag")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String findManagList() {return "book_manag";
}
@Secured、@RolesAllowed和@PreAuthorize注解都可以对方法的访问进行权限控制。 @Secured为Spring Security提供的注解。
@RolesAllowed为基于JSR 250规范的注解。
@PreAuthorize支持SpEL表达式。
Spring Security默认是禁用方法级别的安全控制注解,要想使用注解进行方法授权,可以使用@EnableGlobalMethodSecurity注解开启基于方法的安全认证机制,该注解可以标注在任意配置类上。
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,jsr250Enabled = true,prePostEnabled = true)
public class WebSecurityConfig {……}
(1)开启基于方法的安全认证机制 在项目的WebSecurityConfig配置类中开启基于方法的安全认证机制,并将类中HttpSecurity对象设置的访问“图书管理”拦截规则删除,以确保对资源授权地为方法授权,修改后的代码如下所示。
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true ,prePostEnabled = true )
public class WebSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests() // 定义哪些URL需要被保护、哪些不需要被保护
.mvcMatchers("/loginview","/css/**","/img/**").permitAll()
.anyRequest().authenticated() // 任何请求,登录后才可以访问
.and()
.formLogin()
.loginPage("/loginview")
.loginProcessingUrl("/doLogin")
.permitAll()
.and()
.csrf().disable()//禁止csrf 跨站请求保护;
.headers().frameOptions().sameOrigin();
return http.build();
}
}
(2)方法授权
在BookController类的findManagList()方法上使用注解指定访问该方法所需的角色
@Controller
@RequestMapping("book")public class BookController {
@RequestMapping("list")
public String findList() {
return "book_list";
}
@RequestMapping("admin/manag")
@Secured("ROLE_ADMIN")
public String findManagList() {
return "book_manag";
}
}
(3)测试效果
启动项目,在浏览器中通过“http://localhost:8080/”访问项目首页后,使用用户zhangsan登录系统。
单击左侧的“图书管理”链接。
在用户登录页面使用用户lisi进行登录,登录成功后再次访问“图书管理”。
动态展示菜单
掌握动态展示菜单,能够通过Spring Security的授权管理实现动态展示菜单。
在前面的讲解中,只是通过Spring Security对后台资源的访问根据角色进行权限控制,前端页面并没有做任何处理,不同角色能看到的前端页面是一样的,即使当前用户没有对应的访问权限,依然能看到对应的菜单,用户体验较差。下面在前面案例的基础上,讲解如何使用Spring Security与Thymeleaf整合实现前端页面根据登录用户的角色动态展示菜单。
1.添加依赖
添加Thymeleaf与Spring Security 5的集成包:thymeleaf-extras-springsecurity5
<dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
2.修改页面代码
打开后台首页main.html,引入Spring Security安全标签,并在页面中根据需求使用Spring Security标签指定为不同角色显示不同的页面内容,实现动态展示控制
<html xmlns:th="http://www.thymeleaf.org"xmlns:sec="http://www.thymeleaf.org/extras/spring-security"><head><meta charset="utf-8"><title>网上图书馆</title><headers><frame-options policy="SAMEORIGIN" /></headers><link rel="stylesheet" th:href="@{/css/bootstrap.css}"><link rel="stylesheet" th:href="@{/css/AdminLTE.css}"><link rel="stylesheet" th:href="@{/css/_all-skins.min.css}"><script th:src="@{/js/jquery.min.js}"></script><script th:src="@{/js/bootstrap.js}"></script><script type="text/javascript">function SetIFrameHeight() {var iframeid = document.getElementById("iframe");if (document.getElementById) {/*设置 内容展示区的高度等于页面可视区的高度*/iframeid.height = document.documentElement.clientHeight;}}</script></head><body class="hold-transition skin-green sidebar-mini"><div class="wrapper"><!-- 页面头部 --><header class="main-header"><!-- Logo --><a th:href="@{/}" class="logo"><span class="logo-lg"><b>网上图书馆</b></span></a><!-- 头部导航 --><nav class="navbar navbar-static-top"><div class="navbar-custom-menu"><ul class="nav navbar-nav"><li class="dropdown user user-menu"><a th:if="${session.user !=null}"><img th:src="@{/img/user.jpg}" class="user-image"alt="User Image"><span class="hidden-xs" th:text="${session.user.name}"></span>
</a></li><li class="dropdown user user-menu"><a><span class="hidden-xs">注销</span></a></li></ul></div></nav></header><!-- 页面头部 /--><!-- 导航侧栏 --><aside class="main-sidebar"><section class="sidebar"><ul class="sidebar-menu"><li sec:authorize="hasAnyAuthority('ROLE_COMMON','ROLE_ADMIN')"><a th:href="@{/book/list}" target="iframe"><i class="fa fa-circle-o"></i>图书阅读</a></li><li sec:authorize="hasAuthority('ROLE_ADMIN')"><a th:href="@{/book/admin/manag}" target="iframe"><i class="fa fa-circle-o"></i>图书管理</a></li></ul></section><!-- /.sidebar --></aside><!-- 导航侧栏 /--><!-- 内容展示区域 --><div class="content-wrapper"><iframe width="100%" id="iframe" name="iframe" onload="SetIFrameHeight()"frameborder="0" ></iframe></div></div></body></html>
3.效果测试
重启项目。
使用用户zhangsan的信息进行登录,登录后展示后台首页。
使用用户lisi的信息进行登录,登录后展示后台首页。