一、Spring Security简介
Spring Security是一个功能强大且高度可定制的身份验证 和访问控制 框架。Spring Security致力于为Java应用程序提供身份验证和授权的能力。像所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足定制需求的能力。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DDJjGp6V-1645512921586)(image\角色和权限时许.jpg)]
Spring Security两大重要核心功能:用户认证(Authentication)和用户授权(Authorization)。
- 用户认证:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
- 用户授权:验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,有的用户既能读取,又能修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
二、快速开始
使用Springboot工程搭建Spring Security项目。
1.引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/>
</parent>
<groupId>com.qf</groupId>
<artifactId>spring-security-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
在pom中新增了Spring Security的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.创建测试访问接口
用于访问接口时触发Spring Security登陆页面
@RestController
public class SecurityController {
@RequestMapping("/hello")
public String hello(){
return "hello security!";
}
}
3.Security登陆页面
访问hello接口,将自动跳转至Security的登陆页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3H59z5h1-1645512921587)(image\login.png)]
默认账号是: user
默认密码是:启动项目的控制台中输出的密码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0lVPfdSR-1645512921587)(image\log.png)]
三、原理剖析
访问hello接口,发现被Spring Security的登陆页面拦截,可以猜到这是触发了Security框架的过滤器。Spring Security本质上就是一个过滤器链。下面讲介绍Security框架的过滤器链。
1.过滤器链
几个主要的过滤器
- FilterSecurityInterceptor:是一个方法级的权限过滤器,位于过滤器链的最底部。
- ExceptionTranslationFilter: 异常过滤器,用来处理在认证授权过程中抛出异常。
- UsernamePasswordAuthenticationFilter: 用于对/login的POST请求做拦截,校验表单中的用户名和密码。
2.过滤器加载过程
Springboot在整合Spring Security项目时会自动配置DelegatingFilterProxy 过滤器,若非Springboot工程,则需要手动配置该过滤器。
过滤器如何进行加载的?
Security在DelegatingFilterProxy 的doFilter() 调用了initDelegat() 方法,在该方法中调用了WebApplicationContext 的getBean() 方法,该方法触发FilterChainProxy 的doFilterInternal 方法,用于获取过滤链中的所有过滤器并进行加载。
SpringSecurity过滤器链 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pq0Ah3t7-1645512922139)(image\image-20210909100026482.png)] |
3.Security的两个关键接口
在快速开始中发现Spring Security使用了默认的用户名和密码,实际用户名和密码需要自定义,因此会用到以下两个接口。下述两个接口的具体实现将在之后的例子中体现。
3.1 UserDetailsService接口
若需要从数据库中获取用户名和密码,则需要把查询数据库的过程写在这个接口里。完成自定义登录逻辑
3.2 PasswordEncoder接口
在密码的处理上,需要进行编解码器,该接口实现对密码进行加密。
BCryptPasswordEncoder是PasswordEncoder最常用的实现类,是官方推荐使用的
@Test
public void testBCryptPasswordEncoder() {
BCryptPasswordEncoder bcrypt = new BCryptPasswordEncoder();
String encode = bcrypt.encode("123456");
System.out.println(encode);
boolean matches = bcrypt.matches("123456", encode);
System.out.println(matches);
}
四、SpringSecurity认证功能
4.1 自定义用户名和密码
在application.yml文件中配置用户名和密码
spring:
security:
user:
name: admin
password: 123456
由于Springboot版本的原因,在当前版本下,必须要注入PasswordEncoder对象
@Bean
public PasswordEncoder getPasswordEncode(){
return new BCryptPasswordEncoder();
}
4.2 基于内存定义用户名密码
自定义配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder.encode("admin"))
.authorities("admin");
auth.inMemoryAuthentication()
.withUser("zhangsan")
.password(passwordEncoder.encode("zhangsan"))
.authorities("user");
auth.inMemoryAuthentication()
.withUser("lisi")
.password(passwordEncoder.encode("lisi"))
.authorities("user");
}
}
4.4 基于数据库定义用户名密码
1、准备数据表
- 数据表中添加id、username、password字段
2、使用Mybatis-plus定义Mapper接口
public interface TbUserMapper extends BaseMapper<TbUser> {
}
3、编写UserService接口以及实现类
public interface UserService {
TbUser findUserByName(String username);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private TbUserMapper userMapper;
@Override
public TbUser findUserByName(String username) {
QueryWrapper<TbUser> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",username);
return userMapper.selectOne(queryWrapper);
}
}
4、自定义实现UserDetailService接口
@Component("myUserDetailService")
public class MyUserDetailService implements UserDetailsService {
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
TbUser tbUser = userService.findUserByName(s);
if(tbUser == null){
throw new UsernameNotFoundException("用户名或密码错误");
}
List<GrantedAuthority> authorityList = AuthorityUtils
.commaSeparatedStringToAuthorityList("admin,user");
User user = new User(tbUser.getUsername(),tbUser.getPassword() ,authorityList );
return user;
}
}
5、修改Security配置类使用数据库认证
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
@Qualifier("myUserDetailService")
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
4.5 自定义登录页面
修改Security配置类
- 自定义成功跳转页面
- 自定义失败跳转页面
- 授权访问
- 关闭csrf防护
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
@Qualifier("myUserDetailService")
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.successForwardUrl("/index")
.failureForwardUrl("/errorPage");
http.authorizeRequests()
.antMatchers("/login.html","/login").permitAll()
.antMatchers("/index").permitAll()
.antMatchers("/error.html","/errorPage").permitAll()
.anyRequest().authenticated();
http.csrf().disable();
}
}
页面跳转必须要转发,定义Controller转发页面
@Controller
public class PageController {
@RequestMapping("/index")
public String index(){
return "redirect:/index.html";
}
@RequestMapping("/errorPage")
public String error(){
return "redirect:/error.html";
}
}
4.6 退出登录
设置退出登录的请求地址
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
@Qualifier("myUserDetailService")
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/logoutPage").permitAll();
}
}
定义跳转页面
@RequestMapping("/logoutPage")
public String logout(){
return "redirect:/login.html";
}
前端定义退出登录按钮
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录成功 <a href="/logout">退出登录</a></h1>
</body>
</html>
4.7 csrf防护
为了测试顺利,这里临时关闭csrf防护。所谓csrf防护,全称为跨站请求伪造(Cross-site request forgery),是一种网络攻击方式,CSRF攻击利用网站对于用户网页浏览器的信任,挟持用户当前已登陆的Web应用程序,去执行并非用户本意的操作。简而言之,用户通过盗取目标网站保存的cookie中的用户信息,实现非法使用。
五、基于角色和权限进行访问控制
5.1 基于内存定义用户角色和权限
1、自定义配置类中添加角色
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder.encode("admin"))
.roles("admin","user")
.authorities("user_select","user_add","user_del","user_update");
auth.inMemoryAuthentication()
.withUser("zhangsan")
.password(passwordEncoder.encode("zhangsan"))
.roles("user")
.authorities("user_select");
auth.inMemoryAuthentication()
.withUser("wangwu")
.password(passwordEncoder.encode("wangwu"))
.roles("user")
.authorities("user_select");
}
}
5.2 配置类中通过角色和权限进行访问控制
2、在配置类中通过角色来校验用户具有某个角色,才可以访问
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
@Qualifier("myUserDetailService")
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder.encode("admin"))
.roles("admin","user")
.authorities("user_select","user_add","user_del","user_update");
auth.inMemoryAuthentication()
.withUser("zhangsan")
.password(passwordEncoder.encode("zhangsan"))
.roles("user")
.authorities("user_select");
auth.inMemoryAuthentication()
.withUser("wangwu")
.password(passwordEncoder.encode("wangwu"))
.roles("user")
.authorities("user_select");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.successForwardUrl("/index")
.failureForwardUrl("/errorPage");
http.authorizeRequests()
.antMatchers("/login.html","/login").permitAll()
.antMatchers("/index").permitAll()
.antMatchers("/error.html","/errorPage").permitAll()
.antMatchers("/product/**").hasAnyAuthority("user_add","user_del")
.antMatchers("/order/**").hasAnyAuthority("user_add","user_del")
.antMatchers("/user/**").hasAnyAuthority("user_select")
.anyRequest().authenticated();
http.csrf().disable();
}
}
定义前端页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录成功</h1>
<a href="/user">用户操作</a>
<a href="/product">商品操作</a>
<a href="/order">订单操作</a>
</body>
</html>
定义页面转发
@Controller
public class PageController {
@RequestMapping("/index")
public String index(){
return "redirect:/index.html";
}
@RequestMapping("/errorPage")
public String error(){
return "redirect:/error.html";
}
@RequestMapping("/product")
public String product(){
return "redirect:/success.html";
}
@RequestMapping("/user")
public String user(){
return "redirect:/success.html";
}
@RequestMapping("/order")
public String order(){
return "redirect:/success.html";
}
}
5.3 使用@PreAuthorize注解进行访问控制
2、在Controller中添加@PreAuthorize注解来校验用户具有某个角色,才可以访问方法
@RestController
public class SecurityController {
@RequestMapping("/hiUser")
@PreAuthorize("hasAnyRole('admin','user')")
public String hiUser(){
return "Hello,user!!(需要拥有admin或者user角色)";
}
@RequestMapping("/hiAdmin")
@PreAuthorize("hasAnyRole('admin')")
public String hiAdmin(){
return "Hello,admin!!(需要拥有admin角色)";
}
}
3、开启全局方法权限校验
@EnableGlobalMethodSecurity(prePostEnabled=true)
4、测试
目前有两个角色,admin以及user角色
- 访问hiUser方法需要admin或者user角色
- 访问hiAdmin方法需要admin角色
5.4 配置403权限错误页面
方式1:在配置类中添加权限错误页面
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
@Qualifier("myUserDetailService")
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder.encode("admin"))
.roles("admin","user")
.authorities("user_select","user_add","user_del","user_update");
auth.inMemoryAuthentication()
.withUser("zhangsan")
.password(passwordEncoder.encode("zhangsan"))
.roles("user")
.authorities("user_select");
auth.inMemoryAuthentication()
.withUser("wangwu")
.password(passwordEncoder.encode("wangwu"))
.roles("user")
.authorities("user_select");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().accessDeniedPage("/403.html");
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/logoutPage").permitAll();
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.successForwardUrl("/index")
.failureForwardUrl("/errorPage");
http.authorizeRequests()
.antMatchers("/login.html","/login").permitAll()
.antMatchers("/index").permitAll()
.antMatchers("/error.html","/errorPage").permitAll()
.antMatchers("/product/**").hasAnyAuthority("user_add","user_del")
.antMatchers("/order/**").hasAnyAuthority("user_add","user_del")
.antMatchers("/user/**").hasAnyAuthority("user_select")
.anyRequest().authenticated();
http.csrf().disable();
}
}
方式2:自定义异常处理器 判断异常类型为:AccessDeniedException 表示没有权限
5.5 其他注解介绍
- @Secured注解
- @PreAuthorize
- @PostAuthorize
- @PostFilter
- 权限验证之后对数据进行过滤,只能获取满足条件的数据
六、RBAC权限管理
6.1 数据库设计
RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。
RBAC数据库模型 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-icbUcAXg-1645512922140)(image\image-20210908123402205.png)] |
6.2 连接数据查询用户拥有角色和权限
security配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("myUserDetailService")
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.successForwardUrl("/index")
.failureForwardUrl("/errorPage");
http.authorizeRequests()
.antMatchers("/login.html","/login").permitAll()
.antMatchers("/index").permitAll()
.antMatchers("/errorPage").permitAll()
.anyRequest().authenticated();
http.csrf().disable();
}
}
UserDetailService自定义登录逻辑类
@Component("myUserDetailService")
public class MyUserDetailService implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
@Autowired
TbPermissionMapper permissionMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
TbUser tbUser = userService.findUserByName(username);
if(tbUser == null){
throw new UsernameNotFoundException("用户名或密码错误");
}
List<String> roleList = permissionService.selectRole(username);
String authorityStr = "";
if(roleList != null && roleList.size() > 0){
for (int i = 0; i < roleList.size(); i++) {
authorityStr+= "ROLE_"+roleList.get(i) +",";
}
}
List<String> permissionList = permissionMapper.selectAllPermission(username);
if(permissionList != null && permissionList.size() >0){
for (int i = 0; i < permissionList.size(); i++) {
authorityStr+= permissionList.get(i)+",";
}
}
if(authorityStr.endsWith(","))
authorityStr = authorityStr.substring(0,authorityStr.length()-1);
List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList(authorityStr);
User user = new User(tbUser.getUsername(),tbUser.getPassword() ,authorityList );
return user;
}
}
定义mapper接口以及自定义映射文件
public interface TbPermissionMapper extends BaseMapper<TbPermission> {
List<String> selectAllPermission(String username);
}
public interface TbRoleMapper extends BaseMapper<TbRole> {
List<String> selectRole(String username);
}
<select id="selectAllPermission" resultType="String">
select distinct tb_permission.permission_name
from tb_user
left join tb_user_role on tb_user_role.uid = tb_user.id
left join tb_role on tb_role.role_id = tb_user_role.rid
left join tb_role_permission on tb_role_permission.rid = tb_role.role_id
left join tb_permission on tb_permission.permission_id = tb_role_permission.pid
where tb_user.username = #{username}
</select>
<select id="selectRole" resultType="string">
select tb_role.role_name
from tb_user
left join tb_user_role on tb_user_role.uid = tb_user.id
left join tb_role on tb_role.role_id = tb_user_role.rid
where tb_user.username = #{username}
</select>
定义Controller测试权限
@PreAuthorize("hasAnyRole('admin')")
@RequestMapping("user/list")
@ResponseBody
public String toUser(){
return "拥有权限可以访问";
}
七、权限管理系统
7.1 基于SpringSecurty实现权限管理
Controller处理器
@Controller
public class IndexController {
@Autowired
private PermissionService permissionService;
@RequestMapping("home")
public String index(Model model){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
List<TbMenu> permissionMenu =
permissionService.selectPermissionMenu(user.getUsername());
model.addAttribute("permissionMenu",permissionMenu);
return "index";
}
@RequestMapping("index")
public String redirect(){
return "redirect:/home";
}
}
根据用户查询角色和权限Service
@Service
public class PermissionServiceImpl implements PermissionService {
@Autowired
private TbPermissionMapper permissionMapper;
@Autowired
private TbRoleMapper roleMapper;
@Override
public List<TbMenu> selectPermissionMenu(String username) {
List<TbMenu> menuList = new ArrayList<>();
List<TbPermission> permissionList1 = permissionMapper.selectPermission(username);
if(permissionList1 != null && permissionList1.size() > 0){
for (TbPermission tbPermission : permissionList1) {
QueryWrapper<TbPermission> queryWrapper = new QueryWrapper<>();
queryWrapper.ne("permission_show","0");
queryWrapper.eq("permission_parent_id",tbPermission.getPermissionId());
List<TbPermission> permissionList2 = permissionMapper.selectList(queryWrapper);
menuList.add(new TbMenu(tbPermission,permissionList2));
}
}
return menuList;
}
@Override
public List<String> selectRole(String username) {
return roleMapper.selectRole(username);
}
}
Mapper及映射文件
public interface TbPermissionMapper extends BaseMapper<TbPermission> {
List<TbPermission> selectPermission(String username);
}
<select id="selectPermission" resultType="TbPermission">
select distinct tb_permission.*
from tb_user
left join tb_user_role on tb_user_role.uid = tb_user.id
left join tb_role on tb_role.role_id = tb_user_role.rid
left join tb_role_permission on tb_role_permission.rid = tb_role.role_id
left join tb_permission on tb_permission.permission_id = tb_role_permission.pid
where tb_user.username = #{username} and permission_parent_id = '10'
</select>
前端这里选择的是Thymeleaf+layui实现
<ul class="layui-nav layui-nav-tree" lay-filter="test">
<li class="layui-nav-item layui-nav-itemed" th:each="menu:${permissionMenu}" >
<a href="javascript:;" th:text="${menu.permission.permissionDesc}"></a>
<dl class="layui-nav-child">
<dd th:each="m:${menu.menuList}">
<a th:href="@{${m.permissionUrl}}"
target="content" th:text="${m.permissionDesc}"></a>
</dd>
</dl>
</li>
</ul>
<div style="padding: 15px;" >
<iframe name="content" width="100%" height="600px"></iframe>
</div>
解决不允许显示在iframe的问题
http.headers().frameOptions().disable();
http.headers().cacheControl();
7.2 Thymeleaf中支持的Security标签
导入依赖
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
常见的标签
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5" >
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div sec:authorize="isAuthenticated()">
<h2><span sec:authentication="name"></span>,您好 您的身份是
<span sec:authentication="principal.authorities"></span>
</h2>
</div>
<button sec:authorize="hasAnyRole('ROLE_admin')">admin角色可以操作</button>
<button sec:authorize="hasAnyRole('ROLE_user')">user角色可以操作</button>
<button sec:authorize="hasAnyRole('ROLE_admin','ROLE_user')">admin或者user角色可以操作</button>
<button sec:authorize="hasAnyAuthority('product_list')">list查看权限</button>
<button sec:authorize="hasAnyAuthority('product_add')">添加</button>
<button sec:authorize="hasAnyAuthority('product_del')">删除</button>
<button sec:authorize="hasAnyAuthority('product_edit')">修改</button>
</body>
</html>
d> Title
,您好 您的身份是
<!--基于角色的判断-->
<button sec:authorize="hasAnyRole('ROLE_admin')">admin角色可以操作</button>
<button sec:authorize="hasAnyRole('ROLE_user')">user角色可以操作</button>
<button sec:authorize="hasAnyRole('ROLE_admin','ROLE_user')">admin或者user角色可以操作</button>
<!--基于权限的判断-->
<button sec:authorize="hasAnyAuthority('product_list')">list查看权限</button>
<button sec:authorize="hasAnyAuthority('product_add')">添加</button>
<button sec:authorize="hasAnyAuthority('product_del')">删除</button>
<button sec:authorize="hasAnyAuthority('product_edit')">修改</button>
</body>
```
|