SpringSecurity流程控制实现

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

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 {
。。。
}
此方式可以自定义权限实现:

  1. @Service(“ss”)
    public class PermissionService{
    此图片中的方法为用户自定义实现
    }
  2. 使用方式:
    @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实现权限控制的几种方式,其中的代码仅作参考,欢迎大家对本文有错误的地方进行指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值