栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Shiro--整合jwt--通过url路径控制权限--使用/教程/实例

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Shiro--整合jwt--通过url路径控制权限--使用/教程/实例

原文网址:Shiro--整合jwt--通过url路径控制权限--使用/教程/实例_IT利刃出鞘的博客-CSDN博客

简介

说明

        本文用实例介绍shiro的使用。采用jwt+url权限的方式控制权限。

  1. 使用jwt替代默认的authc作为认证方式,通过url路径控制授权。
  2. 其他尽量使用原生的shiro配置,尽量少自定义配置。

我自己自测通过,代码可用。

关联文章

本文是在此文章基础上进行修改,修改点如下:

  • 数据库
    • t_permission表的name字段改为url字段
    • t_permission相应的插入的数据发生变化
  • Mapper
    • PermissionMapper
      • 原先:返回t_permission.name字段
      • 本文:返回t_permission.url字段
  • Controller
    • 原先:使用@RequiresPermissions、@RequiresRoles控制权限
    • 本文:去掉这两个注解,通过url控制权限
  • Shiro配置
    • 注册授权过滤器(url路径过滤器)
      • 提供url路径过滤器(UrlFiter类)
      • 将url路径过滤器注册进去(ShiroConfig#shiroFilterFactoryBean) 
    • 修改认证过滤器(JwtFilter)
      • 原先:只需通过返回true或者executeLogin即可
      • 本文:自定义响应数据。(若不这么写,响应数据会很不友好,因为我们已经不是shiro的标准用法了)

除了上述修改之外,其他的代码、逻辑一点都没变。

修改点数据库
DROp DATAbase IF EXISTS shiro;
CREATE DATAbase shiro DEFAULT CHARACTER SET utf8;
USE shiro;

DROP TABLE IF EXISTS t_user;
DROP TABLE IF EXISTS t_role;
DROP TABLE IF EXISTS t_permission;
DROP TABLE IF EXISTS t_user_role_mid;
DROP TABLE IF EXISTS t_role_permission_mid;

CREATE TABLE t_user (
  id bigint AUTO_INCREMENT,
  user_name VARCHAr(100),
  password VARCHAr(100),
  salt VARCHAr(100),
  PRIMARY KEY(id)
) charset=utf8 ENGINE=InnoDB;

CREATE TABLE t_role (
  id bigint AUTO_INCREMENT,
  name VARCHAr(100),
  description VARCHAr(100),
  PRIMARY KEY(id)
) charset=utf8 ENGINE=InnoDB;

CREATE TABLE t_permission (
  id bigint AUTO_INCREMENT,
  url VARCHAr(100),
  description VARCHAr(100),
  PRIMARY KEY(id)
) charset=utf8 ENGINE=InnoDB;

CREATE TABLE t_user_role_mid (
  id bigint AUTO_INCREMENT,
  user_id bigint,
  role_id bigint,
  PRIMARY KEY(id)
) charset=utf8 ENGINE=InnoDB;

CREATE TABLE t_role_permission_mid (
  id bigint AUTO_INCREMENT,
  role_id bigint,
  permission_id bigint,
  PRIMARY KEY(id)
) charset=utf8 ENGINE=InnoDB;

-- 密码:12345
INSERT INTO `t_user` VALUES (1,"zhang3","a7d59dfc5332749cb801f86a24f5f590","e5ykFiNwShfCXvBRPr3wXg==");
-- 密码:abcde
INSERT INTO `t_user` VALUES (2,"li4","43e28304197b9216e45ab1ce8dac831b","jPz19y7arvYIGhuUjsb6sQ==");
INSERT INTO `t_role` VALUES (1,"admin","超级管理员");
INSERT INTO `t_role` VALUES (2,"productManager","产品管理员");
INSERT INTO `t_role` VALUES (3,"orderManager","订单管理员");
INSERT INTO `t_permission` VALUES (1,"/product/add","增加产品");
INSERT INTO `t_permission` VALUES (2,"/product/delete","删除产品");
INSERT INTO `t_permission` VALUES (3,"/product/edit","编辑产品");
INSERT INTO `t_permission` VALUES (4,"/product/view","查看产品");
INSERT INTO `t_permission` VALUES (5,"/order/add","增加订单");
INSERT INTO `t_permission` VALUES (6,"/order/delete","删除订单");
INSERT INTO `t_permission` VALUES (7,"/order/edit","编辑订单");
INSERT INTO `t_permission` VALUES (8,"/order/view","查看订单");
INSERT INTO `t_user_role_mid` VALUES (1,2,2);
INSERT INTO `t_user_role_mid` VALUES (2,1,1);
INSERT INTO `t_role_permission_mid` VALUES (1,1,1);
INSERT INTO `t_role_permission_mid` VALUES (2,1,2);
INSERT INTO `t_role_permission_mid` VALUES (3,1,3);
INSERT INTO `t_role_permission_mid` VALUES (4,1,4);
INSERT INTO `t_role_permission_mid` VALUES (5,1,5);
INSERT INTO `t_role_permission_mid` VALUES (6,1,6);
INSERT INTO `t_role_permission_mid` VALUES (7,1,7);
INSERT INTO `t_role_permission_mid` VALUES (8,1,8);
INSERT INTO `t_role_permission_mid` VALUES (9,2,1);
INSERT INTO `t_role_permission_mid` VALUES (10,2,2);
INSERT INTO `t_role_permission_mid` VALUES (11,2,3);
INSERT INTO `t_role_permission_mid` VALUES (12,2,4);
INSERT INTO `t_role_permission_mid` VALUES (13,3,5);
INSERT INTO `t_role_permission_mid` VALUES (14,3,6);
INSERT INTO `t_role_permission_mid` VALUES (15,3,7);
INSERT INTO `t_role_permission_mid` VALUES (16,3,8);
修改点代码 Shiro配置

添加Url过滤器(继承PathMatchingFilter)

package com.example.demo.config.shiro.filter;

import com.example.demo.common.entity.Result;
import com.example.demo.common.util.ApplicationContextHolder;
import com.example.demo.common.util.ResponseUtil;
import com.example.demo.common.util.auth.JwtUtil;
import com.example.demo.rbac.permission.service.PermissionService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.PathMatchingFilter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Set;

@Slf4j
public class UrlFilter extends PathMatchingFilter {
    private PermissionService permissionService;

    @Override
    protected boolean onPreHandle(ServletRequest request,
                                  ServletResponse response,
                                  Object mappedValue) throws Exception {

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String uri = httpServletRequest.getRequestURI();
        String token = httpServletRequest.getHeader(HttpHeaders.cookie);
        String userIdStr = JwtUtil.getUserIdByToken(token);
        Long userId = Long.parseLong(userIdStr);

        if (permissionService == null) {
            permissionService = ApplicationContextHolder.getContext().getBean(PermissionService.class);
        }
        Set permissions = permissionService.getPermissionsByUserId(userId);

        // 实际应该从数据库或者redis里通过userId获得拥有权限的url
        if (permissions.contains(uri)) {
            return true;
        }

        // 构造无权限时的response
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        ResponseUtil.jsonResponse(httpResponse, HttpStatus.FORBIDDEN.value(),
                "用户(" + userId + ")无此url(" + uri + ")权限");

        return false;
    }
}

注册Url过滤器

package com.example.demo.config.shiro;

import com.example.demo.common.constant.WhiteList;
import com.example.demo.config.shiro.filter.JwtFilter;
import com.example.demo.config.shiro.filter.UrlFilter;
import com.example.demo.config.shiro.realm.AccountRealm;
import org.apache.shiro.mgt.DefaultSessionStorageevaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

import javax.servlet.Filter;
import java.util.linkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    @Lazy
    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果实现了AuthenticatingFilter.java要设置下边这个,因为shiro很多地方依据loginUrl进行判断
        // shiroFilterFactoryBean.setLoginUrl("/login");
        // // 登录成功后要跳转的链接
        // shiroFilterFactoryBean.setSuccessUrl("/index");
        // // 未授权界面;
        // shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

        Map filterChainDefinitionMap = new linkedHashMap<>();

        WhiteList.ALL.forEach(str -> {
            filterChainDefinitionMap.put(str, "anon");
        });

        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/logout", "jwtAuthc");

        // 下边这行不要打开。原因待确定
        // filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("
    @Bean
    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setUsePrefix(true);
        return defaultAdvisorAutoProxyCreator;
    }

    
    
}

修改JwtFilter

package com.example.demo.config.shiro.filter;

import com.example.demo.common.util.ResponseUtil;
import com.example.demo.common.util.auth.JwtUtil;
import com.example.demo.config.shiro.entity.JwtToken;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class JwtFilter extends AuthenticatingFilter {
    
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest,
                                     ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String token = request.getHeader(HttpHeaders.cookie);
        // 自定义Header也可以,但浏览器不会存自定义的Header,需要前端自己去存
        // String token = request.getHeader("Authentication");

        if (!StringUtils.hasText(token)) {
            return true;
        } else {
            boolean verified = JwtUtil.verifyToken(token);
            if (!verified) {
                HttpServletResponse response = (HttpServletResponse) servletResponse;
                ResponseUtil.jsonResponse(response, HttpStatus.UNAUTHORIZED.value(), "认证失败");
                return false;
            }
        }

        // 此登录并非调用login接口,而是shiro层面的登录。
        // 里边会调用下边的createToken方法
        return executeLogin(servletRequest, servletResponse);
    }

    
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest,
                                              ServletResponse servletResponse) {

        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String token = request.getHeader(HttpHeaders.cookie);
        // 自定义Header也可以,但浏览器不会存自定义的Header,需要前端自己去存
        // String token = request.getHeader("Authentication");
        if (!StringUtils.hasText(token)) {
            return null;
        }
        return new JwtToken(token);
    }
}

工具类ResponseUtil

package com.example.demo.common.util;



import com.example.demo.common.entity.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;

import javax.servlet.http.HttpServletResponse;

public class ResponseUtil {
    public static void jsonResponse(HttpServletResponse response, int status, String message) throws Exception {
        //让浏览器用utf8来解析返回的数据
        response.setHeader("Content-type", "application/json;charset=UTF-8");
        //告诉servlet用UTF-8转码,而不是用默认的ISO8859
        response.setCharacterEncoding("UTF-8");
        response.setStatus(status);

        Result result = new Result().failure().message(message);
        String json = new ObjectMapper().writevalueAsString(result);
        response.getWriter().print(json);
    }
}
Mapper

修改SQL

package com.example.demo.rbac.permission.mapper;

import com.baomidou.mybatisplus.core.mapper.baseMapper;
import com.example.demo.rbac.permission.entity.Permission;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.Set;

@Repository
public interface PermissionMapper extends baseMapper {
    @Select("SELECT " +
            "    t_permission.`url`  " +
            "FROM " +
            "    t_user, " +
            "    t_user_role_mid, " +
            "    t_role, " +
            "    t_role_permission_mid, " +
            "    t_permission " +
            "WHERe " +
            "    t_user.`id` = #{userId}  " +
            "    AND t_user.id = t_user_role_mid.user_id  " +
            "    AND t_user_role_mid.role_id = t_role.id " +
            "    AND t_role.id = t_role_permission_mid.role_id " +
            "    AND t_role_permission_mid.permission_id = t_permission.id")
    Set getPermissionsByUserId(@Param("userId") Long userId);
}
Controller

产品

package com.example.demo.business.product;

import com.example.demo.common.entity.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Api(tags = "产品")
@RestController
@RequestMapping("product")
public class ProductController {
    @ApiOperation(value="增加产品")
    @PostMapping("add")
    public Result add() {
        return new Result<>().message("product:add success");
    }

    @ApiOperation(value="删除产品")
    @PostMapping("delete")
    public Result delete() {
        return new Result<>().message("product:delete success");
    }

    @ApiOperation(value="编辑产品")
    @PostMapping("edit")
    public Result edit() {
        return new Result<>().message("product:edit success");
    }

    @ApiOperation(value="查看产品")
    @GetMapping("view")
    public Result view() {
        return new Result<>().message("product:view success");
    }
}

订单

package com.example.demo.business.order;

import com.example.demo.common.entity.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Api(tags = "订单")
@RestController
@RequestMapping("order")
public class OrderController {
    @ApiOperation(value="增加订单")
    @PostMapping("add")
    public Result add() {
        return new Result<>().message("order:add success");
    }

    @ApiOperation(value="删除订单")
    @PostMapping("delete")
    public Result delete() {
        return new Result<>().message("order:delete success");
    }

    @ApiOperation(value="编辑订单")
    @PostMapping("edit")
    public Result edit() {
        return new Result<>().message("order:edit success");
    }

    @ApiOperation(value="查看订单")
    @GetMapping("view")
    public Result view() {
        return new Result<>().message("order:view success");
    }
}
测试 测试超级管理员(admin)

启动项目,访问:http://localhost:8080/doc.html

1.测试登录

  1. 登录成功
  2. 可以看到,会返回来一个Set-cookie头,值是token。

2.测试有资源权限的接口

本处测试增加产品接口。

  1. 成功访问。
  2. 在请求时会传递cookie

我使用标准的:Set-cookie,cookie来做认证的。若是自定义的header,需要手动写入:

3.测试登出

4.再次访问接口

  1. 访问成功。
  2.  因为token还没过期,浏览器也还会将其发给服务端,所以成功。

测试产品管理员(productManager)

启动项目,访问:http://localhost:8080/doc.html

1.测试登录

  1. 登录成功
  2. 可以看到,会返回来一个Set-cookie头,值是token。

2.测试有资源权限的接口

本处测试增加产品接口。

  1. 成功访问。
  2. 在请求时会传递cookie

3.测试无资源权限的接口

本处测试增加订单接口。

  1. 访问失败。
  2. 在请求时会传递cookie
  3. 有一处细节:提示是红色的。

点进去看,可以看到状态码是我指定的:403

重启服务再请求

1.登录

登录成功 

2.重启服务器

重启Idea启动的应用。

3.访问有权限的接口

本处访问产品增加接口。

  1. 可以看到,访问成功。

超时后再请求

1.修改配置文件,暂时将token过期时间改短(本处改为10秒)

application.yml

2.登录

3.等待大于10秒之后再请求

请求失败。

我代码里指定这种错误为401,点进去验证下:

其他网址

Shiro实例系列

Shiro--SpringBoot--Session--使用/用法/实例/示例_IT利刃出鞘的博客-CSDN博客
Shiro--SpringBoot--shiro-redis--使用/用法/实例/示例_IT利刃出鞘的博客-CSDN博客
Shiro--SpringBoot--jwt--使用/用法/实例/示例_IT利刃出鞘的博客-CSDN博客
Shiro--SpringBoot--jwt--url路径(PathMatchingFilter)/通过url路径控制权限--使用/用法/实例/示例_IT利刃出鞘的博客-CSDN博客

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/332984.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号