Springboot2 + Shiro + Redis + Jwt 前后端分离整合(3)

继上一篇 Springboot2 + Shiro + Redis + Jwt 前后端分离整合(2)继续补充

码云项目地址 : Springboot + shiro + redis + jwt + jpa

Shiro权限设置

shiro的权限设置在AuthRealm中doGetAuthorizationInfo设置,具体的设置权限和角色根据自己的业务逻辑设置,我这边只用了权限

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.debug("开始执行授权操作.......");

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //如果身份认证的时候没有传入User对象,这里只能取到userName
        //也就是SimpleAuthenticationInfo构造的时候第一个参数传递需要User对象
        UserEntity user = (UserEntity) principalCollection.getPrimaryPrincipal();
        Long userId = user.getId();

        //获取权限并设置
        List<PermissionEntity> list = permissionRepsitory.findByUserId(userId);
        if(!list.isEmpty()){
            list.forEach(o ->{
                authorizationInfo.addStringPermission(o.getRoleId().toString());
            });
        }

        return authorizationInfo;
    }

shiro权限注解,根据自己的需要进行使用,可多个一起使用
(1)@RequiresAuthentication :表示当前Subject已经通过login 进行了身份验证;即Subject. isAuthenticated()返回true
(2)@RequiresUse:表示当前Subject已经身份验证或者通过记住我登录的
(3)@RequiresGuest :表示当前Subject没有身份验证或通过记住我登录过,即是游客身份
(4)@RequiresRoles:需要相应的角色
(5)@RequiresPermissions:需要相应的权限

项目权限
在这里插入图片描述

    //删除用户 必须拥有权限2的用户才能使用该操作
    @PostMapping(value = "/del")
    @RequiresPermissions({"2"})
    public Map<String,String> deleteUser(Long userId){

        Map<String,String> result = new HashMap<>();

        Optional<UserEntity> o = userRepository.findById(userId);
        if(o.isPresent()){
            userRepository.deleteById(userId);
            RedisUtil.del(ShiroConstant.ROLE_SHIRO_CACHE +userId,ShiroConstant.LOGIN_SHIRO_CACHE + userId);
            result.put("code","200");
            result.put("msg","用户删除成功");
        }else{
            result.put("code","400");
            result.put("msg","没有这个用户");
        }

        return result;
    }

在这里插入图片描述
在这里插入图片描述
可以看处用户1拥有删除用户的权限而用户2没有,所以给出的响应不同且权限异常我们可以用@ControllerAdvice来捕获

Shiro认证时的异常捕获

Shiro的认证其实时一个Filter,在Filter中的异常用@ControllerAdvice是没用的,@ControllerAdvice具体是捕获controller层后的异常无法捕获Filter层的异常,具体我们看下例子吧。

加入GlobalExceptionHandler类用来捕获全局异常

@Slf4j
@ControllerAdvice
@ResponseBody
@Component
public class GlobalExceptionHandler {

    /**
     * @param  ex
     * @Description: 捕获CustomException全局异常
     */
    @ExceptionHandler(CustomException.class)
    public Map<String,String> CustomExceptionHandler(CustomException ex) {
        log.error(ex.getMessage(), ex);
        Map<String,String> result = new HashMap<>();
        result.put("msg",ex.getMessage());
        result.put("code","400");
        return result;
    }

    /**
     * @param  ex
     * @Description: 权限认证异常
     */
    @ExceptionHandler(UnauthorizedException.class)
    @ResponseBody
    public Map<String,String> unauthorizedExceptionHandler(Exception ex) {
        log.error(ex.getMessage(), ex);
        Map<String,String> result = new HashMap<>();
        result.put("msg",ex.getMessage());
        result.put("code","400");
        result.put("obj","权限不够");
        return result;
    }
}

自定义异常CustomException类

public class CustomException extends RuntimeException {

    public CustomException() {
    }

    public CustomException(String message) {
        super(message);
    }

}

发送请求我们看下结果
在这里插入图片描述
在这里插入图片描述
很明显抛出了CustomException异常但没有捕获到且输出的内容为**“Typical or expected login exceptions should extend from AuthenticationException”,** CustomException异常属于RuntimeException,被重新生成成AuthenticationException异常了
在这里插入图片描述
将CustomException继承RuntimeException类后
在这里插入图片描述
并没有输出我们想要的格式,可见@ControlllerAdvice没有捕获到异常。

  1. 第一种方式是直接捕获异常进行处理
    AuthFilter类加入**customResponse()方法,并修改isAccessAllowed()**方法

	//输出方法
    private void customResponse(String msg,ServletResponse response){
        try {
            Map<String,Object> map = new HashMap<>();
            response.setContentType(MediaType.APPLICATION_JSON_UTF8.toString());
            map.put("code",405);
            map.put("msg",msg);
            String resultJson= JSON.toJSONString(map);
            OutputStream out=response.getOutputStream();
            out.write(resultJson.getBytes());
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

	//修改捕获到异常的时候直接输出响应并返回false进入onAccessDenied方法
  @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

        AuthToken jwtToken = (AuthToken)this.createToken(request,response);
        if(jwtToken != null){
            String token = jwtToken.getToken();

            try {
                // 提交给realm进行登入,如果错误他会抛出异常并被捕获
                // 如果没有抛出异常则代表登入成功,返回true
                getSubject(request, response).login(jwtToken);
            }catch (AuthenticationException e){
                //获取异常并输出
                this.customResponse(e.getMessage(),response);
                return false;
            }

            //判断是否要更新token
            String refreshToken = this.refreshToken(token);
            if(!StringUtils.isEmpty(refreshToken)){
                log.info("更新token时间!!!!!");
                UserEntity user = (UserEntity) SecurityUtils.getSubject().getPrincipal();
                user.setToken(refreshToken);
                RedisUtil.set(ShiroConstant.LOGIN_SHIRO_CACHE + user.getId(),user);
                HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
                httpServletResponse.setHeader("token", refreshToken);
                httpServletResponse.setHeader("Access-Control-Expose-Headers", "token");
            }
            return true;
        }else{
            this.customResponse("token不能为空", response);
            return false;
        }
    }

测试结果符合预期
在这里插入图片描述

  1. 继承BasicErrorController
    BasicErrorController是SpringBoot默认的全局错误controller,根据请求的类型来区分调用里面的errorHtml还是error方法,我们是前后端分离的就继承该类并重写他的error的方法就好!
    创建ErrorController
@RestController
public class ErrorController extends BasicErrorController {

    /**
     * 必须实现的一个构造方法
     **/
    public ErrorController() {
        super(new DefaultErrorAttributes(), new ErrorProperties());
    }

    /**
     * produces 设置返回的数据类型:application/json
     * @param request 请求
     * @return 自定义的返回实体类
     */
    @Override
    @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        // 获取错误信息
        String message = body.get("message").toString();
        return new ResponseEntity<>(new ResultMap(400,message), status);
    }
}

//该类用于ResponseEntity回参设置
class ResultMap extends HashMap<String, Object> {

    /**
     * 状态码
     */
    private String status;
    /**
     * 返回内容
     */
    private String message;

    public ResultMap() {
    }

    /**
     * 初始化一个新创建的 Result 对象
     * @param status
     * @param message
     */
    public ResultMap(Integer status, Object message) {
        this.put("status", status);
        this.put("message", message);
    }
}

测试结果为我们想要的
在这里插入图片描述

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值