原文网址:Shiro--整合jwt--通过url路径控制权限--使用/教程/实例_IT利刃出鞘的博客-CSDN博客
简介说明
本文用实例介绍shiro的使用。采用jwt+url权限的方式控制权限。
- 使用jwt替代默认的authc作为认证方式,通过url路径控制授权。
- 其他尽量使用原生的shiro配置,尽量少自定义配置。
我自己自测通过,代码可用。
关联文章
本文是在此文章基础上进行修改,修改点如下:
- 数据库
- t_permission表的name字段改为url字段
- t_permission相应的插入的数据发生变化
- Mapper
- PermissionMapper
- 原先:返回t_permission.name字段
- 本文:返回t_permission.url字段
- PermissionMapper
- Controller
- 原先:使用@RequiresPermissions、@RequiresRoles控制权限
- 本文:去掉这两个注解,通过url控制权限
- Shiro配置
- 注册授权过滤器(url路径过滤器)
- 提供url路径过滤器(UrlFiter类)
- 将url路径过滤器注册进去(ShiroConfig#shiroFilterFactoryBean)
- 修改认证过滤器(JwtFilter)
- 原先:只需通过返回true或者executeLogin即可
- 本文:自定义响应数据。(若不这么写,响应数据会很不友好,因为我们已经不是shiro的标准用法了)
- 注册授权过滤器(url路径过滤器)
除了上述修改之外,其他的代码、逻辑一点都没变。
修改点数据库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 baseMapperController{ @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); }
产品
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.测试登录
- 登录成功
- 可以看到,会返回来一个Set-cookie头,值是token。
2.测试有资源权限的接口
本处测试增加产品接口。
- 成功访问。
- 在请求时会传递cookie
我使用标准的:Set-cookie,cookie来做认证的。若是自定义的header,需要手动写入:
3.测试登出
4.再次访问接口
- 访问成功。
- 因为token还没过期,浏览器也还会将其发给服务端,所以成功。
启动项目,访问:http://localhost:8080/doc.html
1.测试登录
- 登录成功
- 可以看到,会返回来一个Set-cookie头,值是token。
2.测试有资源权限的接口
本处测试增加产品接口。
- 成功访问。
- 在请求时会传递cookie
3.测试无资源权限的接口
本处测试增加订单接口。
- 访问失败。
- 在请求时会传递cookie
- 有一处细节:提示是红色的。
点进去看,可以看到状态码是我指定的:403
重启服务再请求1.登录
登录成功
2.重启服务器
重启Idea启动的应用。
3.访问有权限的接口
本处访问产品增加接口。
- 可以看到,访问成功。
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博客



