提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
SpringSecurity常见的几种实现方式
一、利用Ant表达式实现权限控制;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(“/admin/**”)
.hasRole(“ADMIN”)
.antMatchers(“/user/**”)
.hasRole(“USER”)
.antMatchers(“/visitor/**”)
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf()
.disable();
}
以上代码中,/admin/ 格式的路径需要 admin 角色才可以访问,/user/ 格式的路径需要 user 角色才可以访问,/visitor/** 格式的路径可以直接访问,其他接口路径则需要登录后才能访问。
二、利用授权注解结合SpEl表达式实现权限控制
1.授权注解
常用的授权注解有3个:
@PreAuthorize:方法执行前进行权限检查;
@PostAuthorize:方法执行后进行权限检查;
@Secured:类似于 @PreAuthorize。
2.代码实现
要想利用以上3个授权注解进行权限控制,我们首先需要利用@EnableGlobalMethodSecurity注解开启授权注解功能,代码如下:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
。。。
}
此方式可以自定义权限实现:
- @Service(“ss”)
public class PermissionService{
} - 使用方式:
@PreAuthorize(“@ss.hasPermi(‘system:dept:list’)”)
@GetMapping(“/list”)
public AjaxResult list(SysDept dept)
{
List depts = deptService.selectDeptList(dept);
return success(depts);
}
其中system:dept:list为权限标识,为什么不使用原生的hasRole等方法,
1)在实际业务场景中,有时会出现一个URL请求需要具备某个角色和其他业务逻辑,所以需要重写判断逻辑
2)有的项目中可能会使用URL路径的方式去匹配当前登录用户是否具备权限,但此方式在配置不确定请求路径时就无法实现,例如:
@PreAuthorize(“@ss.hasPermi(‘system:dept:query’)”)
@GetMapping(value = “/{deptId}”)
public AjaxResult getInfo(@PathVariable Long deptId)
{
deptService.checkDeptDataScope(deptId);
return success(deptService.selectDeptById(deptId));
}
此时若想要对这个接口进行权限控制,路径匹配就无法实现,所以用一个权限标识来标明此接口,判断权限时查看当前登录用户是否具备此权限标识即可。
三. 利用过滤器注解实现权限控制
1.过滤器注解简介
在Spring Security中还提供了另外的两个注解,即@PreFilter和@PostFilter,这两个注解可以对集合类型的参数或返回值进行过滤。使用@PreFilter和@PostFilter时,Spring Security将移除对应表达式结果为false的元素。
2.@PostFilter的用法
@PostFilter注解主要是用于对集合类型的返回值进行过滤,filterObject是@PostFilter中的一个内置表达式,表示集合中的元素对象。
@Slf4j
@RestController
public class FilterController {
/**
-
只返回结果中id为偶数的user元素。
-
filterObject是@PreFilter和@PostFilter中的一个内置表达式,表示集合中的当前对象。
*/
@PostFilter(“filterObject.id%2==0”)
@GetMapping(“/users”)
public List getAllUser() {
List users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add(new User(i, “yyg-” + i));
}
return users;
}
}
3.@PreFilter的用法
使用@PreFilter也可以对集合类型的参数进行过滤,当@PreFilter标注的方法内拥有多个集合类型的参数时,可以通过@PreFilter的filterTarget属性来指定当前是针对哪个参数进行过滤的;而filterObject是@PreFilter中的一个内置表达式,表示集合中的元素对象。
为了方便测试,我们在Service层中进行过滤操作,然后在Controller层中进行调用。
FilterService类中的方法定义:
@Slf4j
@Service
public class FilterService {
/**
-
当@PreFilter标注的方法内拥有多个集合类型的参数时,
-
可以通过@PreFilter的filterTarget属性来指定当前是针对哪个参数进行过滤的。
*/
@PreFilter(filterTarget = “ids”, value = “filterObject%2==0”)
public List doFilter(List ids, List users) {
log.warn(“ids=” + ids.toString());
log.warn(“users=” + users.toString());
return ids;
}
}
四.利用动态权限实现权限控制
本人在项目中使用的方式如下:
1. 数据库表结构
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
– Table structure for sys_menu
DROP TABLE IF EXISTS sys_menu
;
CREATE TABLE sys_menu
(
menu_id
bigint(0) NOT NULL AUTO_INCREMENT COMMENT ‘菜单ID’,
menu_name
varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT ‘菜单名称’,
parent_id
bigint(0) NULL DEFAULT 0 COMMENT ‘父菜单ID’,
order_num
int(0) NULL DEFAULT 0 COMMENT ‘显示顺序’,
path
varchar(200) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘路由地址’,
component
varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT ‘组件路径’,
query
varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT ‘路由参数’,
is_frame
int(0) NULL DEFAULT 1 COMMENT ‘是否为外链(0是 1否)’,
is_cache
int(0) NULL DEFAULT 0 COMMENT ‘是否缓存(0缓存 1不缓存)’,
menu_type
char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘菜单类型(M目录 C菜单 F按钮)’,
visible
char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘0’ COMMENT ‘菜单状态(0显示 1隐藏)’,
status
char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘0’ COMMENT ‘状态(0正常 1停用)’,
perms
varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT ‘权限标识’,
icon
varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘#’ COMMENT ‘菜单图标’,
create_by
varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘创建者’,
create_time
datetime(0) NULL DEFAULT NULL COMMENT ‘创建时间’,
update_by
varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘更新者’,
update_time
datetime(0) NULL DEFAULT NULL COMMENT ‘更新时间’,
remark
varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘备注’,
PRIMARY KEY (menu_id
) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2000 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = ‘菜单权限表’ ROW_FORMAT = Dynamic;
– Table structure for sys_role
DROP TABLE IF EXISTS sys_role
;
CREATE TABLE sys_role
(
role_id
bigint(0) NOT NULL AUTO_INCREMENT COMMENT ‘角色ID’,
role_name
varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT ‘角色名称’,
role_key
varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT ‘角色权限字符串’,
role_sort
int(0) NOT NULL COMMENT ‘显示顺序’,
data_scope
char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘1’ COMMENT ‘数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)’,
menu_check_strictly
tinyint(1) NULL DEFAULT 1 COMMENT ‘菜单树选择项是否关联显示’,
dept_check_strictly
tinyint(1) NULL DEFAULT 1 COMMENT ‘部门树选择项是否关联显示’,
status
char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT ‘角色状态(0正常 1停用)’,
del_flag
char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘0’ COMMENT ‘删除标志(0代表存在 2代表删除)’,
create_by
varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘创建者’,
create_time
datetime(0) NULL DEFAULT NULL COMMENT ‘创建时间’,
update_by
varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘更新者’,
update_time
datetime(0) NULL DEFAULT NULL COMMENT ‘更新时间’,
remark
varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT ‘备注’,
PRIMARY KEY (role_id
) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 100 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = ‘角色信息表’ ROW_FORMAT = Dynamic;
– Table structure for sys_role_menu
DROP TABLE IF EXISTS sys_role_menu
;
CREATE TABLE sys_role_menu
(
role_id
bigint(0) NOT NULL COMMENT ‘角色ID’,
menu_id
bigint(0) NOT NULL COMMENT ‘菜单ID’,
PRIMARY KEY (role_id
, menu_id
) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = ‘角色和菜单关联表’ ROW_FORMAT = Dynamic;
– Table structure for sys_user
DROP TABLE IF EXISTS sys_user
;
CREATE TABLE sys_user
(
user_id
bigint(0) NOT NULL AUTO_INCREMENT COMMENT ‘用户ID’,
dept_id
bigint(0) NULL DEFAULT NULL COMMENT ‘部门ID’,
user_name
varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT ‘用户账号’,
nick_name
varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT ‘用户昵称’,
user_type
varchar(2) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘00’ COMMENT ‘用户类型(00系统用户)’,
email
varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘用户邮箱’,
phonenumber
varchar(11) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘手机号码’,
sex
char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘0’ COMMENT ‘用户性别(0男 1女 2未知)’,
avatar
varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘头像地址’,
password
varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘密码’,
status
char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘0’ COMMENT ‘帐号状态(0正常 1停用)’,
del_flag
char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘0’ COMMENT ‘删除标志(0代表存在 2代表删除)’,
login_ip
varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘最后登录IP’,
login_date
datetime(0) NULL DEFAULT NULL COMMENT ‘最后登录时间’,
create_by
varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘创建者’,
create_time
datetime(0) NULL DEFAULT NULL COMMENT ‘创建时间’,
update_by
varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT ‘’ COMMENT ‘更新者’,
update_time
datetime(0) NULL DEFAULT NULL COMMENT ‘更新时间’,
remark
varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT ‘备注’,
PRIMARY KEY (user_id
) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 100 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = ‘用户信息表’ ROW_FORMAT = Dynamic;
– Table structure for sys_user_role
DROP TABLE IF EXISTS sys_user_role
;
CREATE TABLE sys_user_role
(
user_id
bigint(0) NOT NULL COMMENT ‘用户ID’,
role_id
bigint(0) NOT NULL COMMENT ‘角色ID’,
PRIMARY KEY (user_id
, role_id
) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = ‘用户和角色关联表’ ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
2.实现UserDetails接口
public class LoginUser implements UserDetails
{
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 部门ID
*/
private Long deptId;
/**
* 用户唯一标识
*/
private String token;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 权限列表
*/
private Set<String> permissions;
/**
* 用户信息
*/
private SysUser user;
public Long getUserId()
{
return userId;
}
public void setUserId(Long userId)
{
this.userId = userId;
}
public Long getDeptId()
{
return deptId;
}
public void setDeptId(Long deptId)
{
this.deptId = deptId;
}
public String getToken()
{
return token;
}
public void setToken(String token)
{
this.token = token;
}
public LoginUser()
{
}
public LoginUser(SysUser user, Set<String> permissions)
{
this.user = user;
this.permissions = permissions;
}
public LoginUser(Long userId, Long deptId, SysUser user, Set<String> permissions)
{
this.userId = userId;
this.deptId = deptId;
this.user = user;
this.permissions = permissions;
}
@JSONField(serialize = false)
@Override
public String getPassword()
{
return user.getPassword();
}
@Override
public String getUsername()
{
return user.getUserName();
}
/**
* 账户是否未过期,过期无法验证
*/
@JSONField(serialize = false)
@Override
public boolean isAccountNonExpired()
{
return true;
}
/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
*
* @return
*/
@JSONField(serialize = false)
@Override
public boolean isAccountNonLocked()
{
return true;
}
/**
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
*
* @return
*/
@JSONField(serialize = false)
@Override
public boolean isCredentialsNonExpired()
{
return true;
}
/**
* 是否可用 ,禁用的用户不能身份验证
*
* @return
*/
@JSONField(serialize = false)
@Override
public boolean isEnabled()
{
return true;
}
public Long getLoginTime()
{
return loginTime;
}
public void setLoginTime(Long loginTime)
{
this.loginTime = loginTime;
}
public String getIpaddr()
{
return ipaddr;
}
public void setIpaddr(String ipaddr)
{
this.ipaddr = ipaddr;
}
public String getLoginLocation()
{
return loginLocation;
}
public void setLoginLocation(String loginLocation)
{
this.loginLocation = loginLocation;
}
public String getBrowser()
{
return browser;
}
public void setBrowser(String browser)
{
this.browser = browser;
}
public String getOs()
{
return os;
}
public void setOs(String os)
{
this.os = os;
}
public Long getExpireTime()
{
return expireTime;
}
public void setExpireTime(Long expireTime)
{
this.expireTime = expireTime;
}
public Set<String> getPermissions()
{
return permissions;
}
public void setPermissions(Set<String> permissions)
{
this.permissions = permissions;
}
public SysUser getUser()
{
return user;
}
public void setUser(SysUser user)
{
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities()
{
return null;
}
}
getAuthorities()返回null是由于此类中含有Set permissions属性,此属性代表权限标识集合,即前文提到的@PreAuthorize(“@ss.hasPermi(‘system:dept:list’)”)中system:dept:list,getAuthorities()方法源码是通过角色标识来进行权限控制的,正常情况下要重写此方法,此处不再赘述,但前文说的运用权限标识来进行控制,所以return null;
3. 实现UserDetailsService接口
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Autowired
private ISysUserService userService;
@Autowired
private SysPasswordService passwordService;
@Autowired
private SysPermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
SysUser user = userService.selectUserByUserName(username);
if (StringUtils.isNull(user))
{
log.info("登录用户:{} 不存在.", username);
throw new ServiceException("登录用户:" + username + " 不存在");
}
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
log.info("登录用户:{} 已被删除.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
}
else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
log.info("登录用户:{} 已被停用.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
}
passwordService.validate(user);
return createLoginUser(user);
}
public UserDetails createLoginUser(SysUser user)
{
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
}
}
4. 编写PermissionService
@Service(“ss”)
public class PermissionService
{
/** 所有权限标识*/
private static final String ALL_PERMISSION = “*: *: *”;
/** 管理员角色权限标识*/
private static final String SUPER_ADMIN = "admin";
private static final String ROLE_DELIMETER = ",";
private static final String PERMISSION_DELIMETER = ",";
/**
*验证用户是否具备某权限
*@param permission 权限字符串
*@return 用户是否具备某权限
*/
public boolean hasPermi(String permission)
{
if (StringUtils.isEmpty(permission))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
{
return false;
}
PermissionContextHolder.setContext(permission);
return hasPermissions(loginUser.getPermissions(), permission);
}
/**
*验证用户是否不具备某权限,与 hasPermi逻辑相反
*@param permission 权限字符串
*@return 用户是否不具备某权限
*/
public boolean lacksPermi(String permission)
{
return hasPermi(permission) != true;
}
/**
*验证用户是否具有以下任意一个权限
*@param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
*@return 用户是否具有以下任意一个权限
*/
public boolean hasAnyPermi(String permissions)
{
if (StringUtils.isEmpty(permissions))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
{
return false;
}
PermissionContextHolder.setContext(permissions);
Set<String> authorities = loginUser.getPermissions();
for (String permission : permissions.split(PERMISSION_DELIMETER))
{
if (permission != null && hasPermissions(authorities, permission))
{
return true;
}
}
return false;
}
/**
*判断用户是否拥有某个角色
*@param role 角色字符串
*@return 用户是否具备某角色
*/
public boolean hasRole(String role)
{
if (StringUtils.isEmpty(role))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
{
return false;
}
for (SysRole sysRole : loginUser.getUser().getRoles())
{
String roleKey = sysRole.getRoleKey();
if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role)))
{
return true;
}
}
return false;
}
/**
*验证用户是否不具备某角色,与 isRole逻辑相反。
*@param role 角色名称
*@return 用户是否不具备某角色
*/
public boolean lacksRole(String role)
{
return hasRole(role) != true;
}
/**
*验证用户是否具有以下任意一个角色
*
*@param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
*@return 用户是否具有以下任意一个角色
*/
public boolean hasAnyRoles(String roles)
{
if (StringUtils.isEmpty(roles))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
{
return false;
}
for (String role : roles.split(ROLE_DELIMETER))
{
if (hasRole(role))
{
return true;
}
}
return false;
}
/**
*判断是否包含权限
*@param permissions 权限列表
*@param permission 权限字符串
*@return 用户是否具备某权限
*/
private boolean hasPermissions(Set<String> permissions, String permission)
{
return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
}
}
5.自定义配置类extend WebSecurityConfigurerAdapter,此处如何配置不在赘述,在类上添加注解@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
6.在Controller层使用注解判断标识方式进行权限控制
总结
本文简单介绍了SpringSecurity实现权限控制的几种方式,其中的代码仅作参考,欢迎大家对本文有错误的地方进行指正。