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的权限,可以看到现在这个角色有三个权限了。
源码下载地址:源码下载