Spring Boot与Shiro实现权限管理03

1.实现登陆

1.1 创建Service

创建service包,并在该包下创建UserService及其实现类。

public interface UserService {


    /**
     * 通过用户名查询用户的主键
     * @param username
     * @return
     */
    Integer findUserByName(String username);
}
@Service
public class UserServiceImpl implements UserService {

	@Autowired
    private SysUserMapper sysUserMapper;

    @Override
    public Integer findUserByName(String username){
        SysUser user = sysUserMapper.findByUsername(username);
        if(user == null){
            return null;
        }
        return user.getId();
    }
}

1.2 完成Controller层

在LoginController.java类中添加登陆方法。

首先需添加一个session的工具类,创建util包,在该包下创建SessionUtils.java类。

public class SessionUtils {

    public static final String KEY_UID = "key_uid";

    public static Integer getUID() {
        Object uid = SecurityUtils.getSubject().getSession().getAttribute(KEY_UID);
        return ((Integer) uid);
    }

    public static void setAttribute(String key, Object value) {
        SecurityUtils.getSubject().getSession().setAttribute(key, value);
    }
}

添加登陆方法。

    @Autowired
    private UserService userService;

    @Data
    public static class LoginRequest implements Serializable {
        private String username;
        private String password;
    }


    @PostMapping("/api/login")
    @ApiOperation("登陆接口")
    public ResponseEntity login(@RequestBody LoginRequest req){
        if(StringUtils.isBlank(req.getUsername())){
            return Results.userInputError("请输入用户名");
        }
        if(StringUtils.isBlank(req.getPassword())){
            return Results.userInputError("请输入密码");
        }

        Subject subject = SecurityUtils.getSubject();
        if(subject.isAuthenticated()){
            return Results.success();
        }
        UsernamePasswordToken token = new UsernamePasswordToken(req.getUsername(), req.getPassword());
        try{
            subject.login(token);
            Integer id = userService.findUserByName(req.getUsername());
            SessionUtils.setAttribute(SessionUtils.KEY_UID, id);
            return Results.success();
        }catch (UnknownAccountException e){
            return Results.userInputError("用户不存在");
        }catch(IncorrectCredentialsException e){
            return Results.userInputError("密码错误");
        }catch(AuthenticationException e){
            e.printStackTrace();
            return Results.error("登陆失败");
        }

    }

SessionUtils.setAttribute(Sessions.KEY_UID, id) 将登录用户的 UID 保存到 Session 中,以便后续使用。

1.3 简单测试一下

在这里插入图片描述

2. 实现退出

1.1 完成Controller层

在LoginController.java类中添加退出方法。

    /**
     * 退出
     * @return
     */
    @PostMapping("/api/logout")
    @ApiOperation("退出接口")
    public ResponseEntity logout(){
        Subject subject = SecurityUtils.getSubject();
        if(!subject.isAuthenticated()){
            return Results.error("用户未登陆");
        }
        subject.logout();
        return Results.success();
    }

1.2 简单测试一下

先不登陆直接退出:会跳转到未登录请求。
在这里插入图片描述
登陆后在退出:
在这里插入图片描述

3.实现角色管理

Shiro 支持通过原生 API 对用户进行权限检查,例如:isPermitted,hasRole 方法等,但是在Shiro中使用的更多的时注解的方式 ,在 AuthorizationAttributeSourceAdvisor 中定义了两个方法用来检查某个 Spring Bean 是否使用了 Shiro 注解:

  • boolean isAuthzAnnotationPresent(Class<?> targetClazz)
  • boolean isAuthzAnnotationPresent(Method method)

一个用于检查类上是否使用了 Shiro 注解,另一个则检查方法,如果返回 true,就意味着当前类或方法需要进一步执行相应的权限控制策略。

Shiro 定义了如下五个注解,分别对应着不同的权限控制策略:

  • @RequiresAuthentication: 访问者需要通过身份认证,即 subject.isAuthenticated() 结果为 true。
  • @RequiresUser: 访问者通过身份认证或通过 RememberMe 的方式被认证为系统用户。
  • @RequiresGuest: 访问者不是系统用户。
  • @RequiresRoles: 访问者需要具有指定角色,subject.hasRole() 结果为 true。
  • @RequiresPermissions: 访问者需要具有指定权限,subject.isPermitted() 结果为 true。

3.1 添加角色

首先创建dto,用于请求与响应数据的传输。在common包下创建dto包,在该包下创建RoleDto.java类。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RoleDto implements Serializable {

    private Integer id;
    private String roleName;
}
创建Controller层

在controller包下创建RoleController.java类。

@RestController
@RequestMapping("/api")
public class RoleController {

    @Autowired
    private RoleService roleService;
    
    @RequiresPermissions("role:add")
    @ApiOperation("创建角色")
    @PostMapping("/role")
    public ResponseEntity<RoleDto> add(@RequestBody RoleDto dto) {
        if (StringUtils.isBlank(dto.getRoleName())) {
            return Results.userInputError("角色名不能为空");
        }

        dto = roleService.create(dto.getRoleName());
        return Results.success(dto);
    }
}

@RequiresPermissions 注解的 value 为 “role:add”,这里指的是“创建角色”权限。当接口被调用时 Shiro 首先会通过 ACSRealm.java 的 doGetAuthorizationInfo 方法拿到访问者的所有权限信息,然后判断其中是否包括了 “role:add” 权限,没有包括时将抛出 AuthorizationException 异常,在ExceptionController.java 中添加相应的方法统一处理这个异常,给调用者返回 403(Forbidden)错误。

创建Service层

在service包下创建RoleService.java类。

public interface RoleService {

    /**
     * 创建角色
     * @param roleName
     * @return
     */
    RoleDto create(String roleName);
}
@Service
public class RoleServiceImpl implements RoleService {

    @Autowired
    private SysRoleMapper sysRoleMapper;


    @Transactional
    @Override
    public RoleDto create(String roleName) {
        SysRole sr = new SysRole();
        sr.setRoleName(roleName);
        if (1 != sysRoleMapper.insert(sr)) {
            throw new ServiceException("无法新增角色记录到数据库");
        }

        return DataConverter.map(sr, RoleDto.class);
    }
}

数据类型转换工具类

public class DataConverter {
    private static final ModelMapper mapper = new ModelMapper();

    public DataConverter() {
    }

    public static <D> D map(Object source, Class<D> destinationType) {
        return source == null ? null : mapper.map(source, destinationType);
    }
}
创建Mapper层

之前通过mybatis反向工程创建的模版代码直接包含基本的curd代码,所以这里不需要再编写了。

  <insert id="insert" parameterType="com.picacho.springbootshiro.entity.SysRole" >
    insert into sys_role (id, insert_time, update_time, 
      role_name)
    values (#{id,jdbcType=INTEGER}, #{insertTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}, 
      #{roleName,jdbcType=VARCHAR})
  </insert>
测试一下

启动项目后,先需要访问登陆接口,完成登陆操作后才能访问添加角色接口。
在这里插入图片描述
在这里插入图片描述
接着查看数据库的情况。
在这里插入图片描述
后面对角色的修改,删除和列表查询等功能,其实现方式和流程与上面的流程基本一致,这里就不一一详细介绍了。

4.实现角色分配权限管理

前面已经创建了一个新的角色:“角色分配管理员A”,但到目前为止,这个角色还没有任何权限,接着需要对其进行权限分配。

4.1 角色权限列表

首先创建dto,用于请求与响应数据的传输。在common包下创建dto包,在该包下创建PermissionDto.java类。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PermissionDto implements Serializable {

    private Integer id;
    private String permissionName;
}
完成Controller层
    @RequiresPermissions("role:permission:list")
    @ApiOperation("角色权限列表")
    @GetMapping("/role/{id}/permissions")
    public ResponseEntity<PageModel<PermissionDto>> listPermissions(@PathVariable Integer id,
                                                                    @RequestParam(required = false) Integer pageSize,
                                                                    @RequestParam(required = false) Integer pageNum) {
        PageCondition pc = new PageCondition(pageNum, pageSize);
        PageModel<PermissionDto> pageModel = roleService.listRolePermissions(id, pc);
        return Results.success(pageModel);
    }
完成Service层
    /**
     * 角色权限列表
     * @param roleId
     * @param condition
     * @return
     */
    PageModel<PermissionDto> listRolePermissions(Integer roleId, PageCondition condition);
   @Override
    public PageModel<PermissionDto> listRolePermissions(Integer roleId, PageCondition condition) {
        PageHelper.startPage(condition.getPageNum(), condition.getPageSize());
        PageInfo<PermissionDto> pageInfo = new PageInfo<>(sysRoleMapper.findRolePermissions(roleId));
        return Results.pageModel(pageInfo);
    }
完成mapper层
 List<PermissionDto> findRolePermissions(Integer roleId);
  <select id="findRolePermissions" resultType="com.picacho.springbootshiro.common.dto.PermissionDto">
    select sp.id,
           sp.permission_name as permissionName
    from sys_role_permission srp
           left join sys_permission sp on sp.id = srp.permission_id
    where srp.role_id = #{roleId}
  </select>
测试一下

目前超级管理员还没有 “permission:list”,“role:assign-permission” 和 “role:permission:list” 这三个权限,通过执行下面的 SQL 为超级管理员添加这三个权限:

INSERT INTO `sys_permission`(id, permission_code, permission_name)
VALUES (601, 'role:permission:list', '角色权限列表'),
       (602, 'role:assign-permission', '角色权限分配'),
       (801, 'permission:list', '权限列表');

INSERT INTO `sys_role_permission`(role_id, permission_id)
VALUES (1, 601),
       (1, 602),
       (1, 801);

在这里插入图片描述
可以看到超级管理员拥有很多权限。

4.2 为角色分配权限

完成Controller层
    @RequiresPermissions("role:assign-permission")
    @ApiOperation("分配角色权限")
    @PutMapping("/role/{id}/permission")
    public ResponseEntity assignPermissions(@RequestBody List<Integer> permisIdList, @PathVariable Integer id) {
        roleService.assignPermissions(id, permisIdList);
        return Results.success();
    }
完成Service层
    /**
     * 为角色分配权限
     * @param roleId
     * @param permisIdList
     */
    void assignPermissions(Integer roleId, List<Integer> permisIdList);
    @Transactional
    @Override
    public void assignPermissions(Integer roleId, List<Integer> permisIdList) {
        if (sysRoleMapper.find(roleId) == null) {
            throw Errors.badRequest("角色不存在");
        }
        if (!CollectionUtils.isEmpty(permisIdList)) {
            int count = sysPermissionMapper.countByIds(permisIdList);
            if (count != permisIdList.size()) {
                throw Errors.badRequest("权限不存在");
            }
        }
        sysRolePermissionMapper.deleteByRoleId(roleId);
        if (!CollectionUtils.isEmpty(permisIdList)) {
            List<SysRolePermission> rps = permisIdList.stream().map(p -> {
                SysRolePermission rp = new SysRolePermission();
                rp.setRoleId(roleId);
                rp.setPermissionId(p);
                return rp;
            }).collect(Collectors.toList());
            if (sysRolePermissionMapper.insertBatch(rps) != permisIdList.size()) {
                throw Errors.db();
            }
        }
    }
  • 1.首先需要对入参进行验证,确保相关的角色和权限都是存在的。
  • 2.清空角色权限,如果用户传递了空的权限数组,将其视为需要删除角色的所有权限。
  • 3.将新权限分配给角色。
完成mapper层

SysPermissionMapper

int countByIds(@Param("permisIdList") List<Integer> permisIdList);
  <select id="countByIds" resultType="java.lang.Integer">
    select COUNT(*)
    FROM sys_permission sp
    WHERE sp.id IN
    <foreach collection="permisIdList" item="k" open="(" separator="," close=")">#{k}</foreach>
  </select>

SysRolePermissionMapper

    void deleteByRoleId(Integer roleId);
    int insertBatch(@Param("rps") List<SysRolePermission> rps);
  <delete id="deleteByRoleId">
    delete
    from sys_role_permission
    where role_id = #{roleId}
  </delete>

  <insert id="insertBatch">
    insert into sys_role_permission (role_id, permission_id)
    values
    <foreach collection="rps" item="t" separator=",">
      (#{t.roleId},#{t.permissionId})
    </foreach>
  </insert>
测试一下

目前角色分配管理员A还没有任何权限。
在这里插入图片描述
接着利用超级管理员使用角色权限分配接口为角色分配管理员A添加三个权限。
在这里插入图片描述
接着继续查看角色分配管理员A的权限,可以看到现在这个角色有三个权限了。
在这里插入图片描述
源码下载地址:源码下载

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

picacho_pkq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值