您的位置:首页 > 健康 > 美食 > cms系统教程_网络直播公司_做网络推广怎么找客户_广州网络推广公司

cms系统教程_网络直播公司_做网络推广怎么找客户_广州网络推广公司

2025/7/12 22:04:42 来源:https://blog.csdn.net/daralisdan/article/details/146981815  浏览:    关键词:cms系统教程_网络直播公司_做网络推广怎么找客户_广州网络推广公司
cms系统教程_网络直播公司_做网络推广怎么找客户_广州网络推广公司

一、数据库模板设计

在本文中,我们使用RBAC(Role-Based Access Control,基于角色的访问控制)模型设计用户,角色和权限间的关系。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系

一个用户对应一个或者多个角色。
一个角色对应一个或者多个权限。
一个权限对应能够访问对应的API或url资源。

 

1 . RBAC基本实体关系,Permission类(权限资源) :


/*** Created by EalenXie on 2019/3/25 11:15.* <p>* 权限许可(Permission) 操作 及其能访问url 权限对应一个url地址*/
@Entity
@Table(name = "system_shiro_permission")
public class Permission extends BaseEntity {@Column(unique = true)private String name;                //权限名 唯一@Column(unique = true)private String url;                 //访问地址信息 唯一private String description;         //描述信息//省略getter/setter
}

 

2 . Role类(用户角色),一个角色拥有一个或者多个权限 :


/*** Created by EalenXie on 2019/3/25 11:18.* <p>* 角色(Role) 角色下面对应多个权限*/
@Entity
@Table(name = "system_shiro_role")
public class Role extends BaseEntity {@Column(unique = true)private String name;                    //角色名 唯一private String description;             //描述信息@ManyToMany(fetch= FetchType.EAGER)private List<Permission> permissions;   //一个用户角色对应多个权限//省略getter/setter
}

 

3 . User类(用户),一个用户拥有一个或者多个角色 :


/*** Created by EalenXie on 2019/3/25 11:01.* <p>* 用户表(User) 用户下面对应多个角色*/
@Entity
@Table(name = "system_shiro_user")
public class User extends BaseEntity {@Column(unique = true)private String username;//用户名 唯一private String password;//用户密码private String passwordSalt;//用户密码加密盐值@ManyToMany(fetch = FetchType.EAGER)private List<Role> roles;//用户角色  一个用户可能有一个角色,也可能有 多个角色//省略getter/setter
}

以上是对应关系的实体,数据库字段根据上述实体创建,这里就不写出来了。设计到的查询根据实际情况自己写,这里主要是讲shiro

二:Shiro整合实现思路

我们先来屡一下思路,实现认证权限功能主要可以归纳为3点:

1.定义一个ShiroConfig配置类,配置 SecurityManager Bean , SecurityManager为Shiro的安全管理器,管理着所有Subject;

2.在ShiroConfig中配置 ShiroFilterFactoryBean ,它是Shiro过滤器工厂类,依赖SecurityManager ;

3.自定义Realm实现类,包含 doGetAuthorizationInfo()doGetAuthenticationInfo()方法 

下面我们就来实现这些步骤:

 1、定义ShiroConfig配置类

/*** Shiro配置*/
@Configuration
public class ShiroConfig {@Resourceprivate OAuth3Realm userAuthRealm;/*** 配置安全管理器* @param oAuth3Realm UserRealm* @return DefaultWebSecurityManager*/@Beanpublic SecurityManager securityManager(OAuth3Realm oAuth3Realm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(oAuth3Realm);return securityManager;}/*** 配置Shiro过滤器工厂* 配置 资源访问策略 . web应用程序 shiro核心过滤器配置*/@Bean("shiroFilter")public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager, UrlConfig urlConfig) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 注册安全管理器shiroFilterFactoryBean.setSecurityManager(securityManager);// 定义资源访问规则Map<String, String> filterMap = new LinkedHashMap<String, String>(16);/** 过滤器说明* anon:不需要认证就可以访问的资源* authc:需要登录认证才能访问的资源* perms:需要指定权限才能访问的资源*/// 需要登录认证才能访问的资源//oauth过滤,anon:不需要认证就可以访问的资源Map<String, Filter> filters = new LinkedHashMap<>();filters.put("oauth3", new OAuth3Filter());// TODO 增加新的B端用户拦截器shiroFilterFactoryBean.setFilters(filters);//排除配置if (urlConfig.getAnonUrl() != null && !urlConfig.getAnonUrl().isEmpty()) {for (String anonUrl : urlConfig.getAnonUrl()) {filterMap.put(anonUrl, "anon");}}//对所有用户认证 对B端访问路由进行拦截filterMap.put("/**", "oauth3");//配置 拦截过滤器链shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);return shiroFilterFactoryBean;}@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}/*** 开启shiro 注解支持. 使以下注解能够生效 :* 需要认证 {@link org.apache.shiro.authz.annotation.RequiresAuthentication RequiresAuthentication}* 需要用户 {@link org.apache.shiro.authz.annotation.RequiresUser RequiresUser}* 需要访客 {@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest}* 需要角色 {@link org.apache.shiro.authz.annotation.RequiresRoles RequiresRoles}* 需要权限 {@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions}* 启用shiro注解*加入注解的使用,不加入这个注解不生效*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}/*** 配置shiro中缓存管理器对象 (对象的名字不能写cacheManager,因为spring容器中已经存在一个名字为cacheManager的对象了)** 作用:缓存授权信息,当第一次访问授权时,会调用自定义Realm的获取授权数据的方法,从数据库中查询授权数据,并将其以登录者的Principal为键,存储在缓存中。* 以后的每次访问授权,就直接从缓存中获取,而不再从数据库中获取。*/@Beanpublic CacheManager shiroCacheManager() {return new MemoryConstrainedCacheManager();}/*** 配置记住我管理对象:底层同cookie对象将用户信息写到客户端*/@Beanpublic RememberMeManager rememberMeManager() {CookieRememberMeManager cManager = new CookieRememberMeManager();//配置cookieSimpleCookie cookie = new SimpleCookie("rememberMe");//设置cookie执行时长(7天)cookie.setMaxAge(1 * 24 * 60 * 60);cManager.setCookie(cookie);return cManager;}// @Bean//   public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
//        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();//     advisorAutoProxyCreator.setProxyTargetClass(true);//     return advisorAutoProxyCreator;// }
}

注意:(当时笔者遇到的一个小问题,贴出来给大家涨姿势)

注解无效,登录时不会执行验证角色和权限的方法,只会执行登录验证方法,遂查询资料,得知shiro在subject.login(token)方法时不会执行doGetAuthorizationInfo方法,只有在访问到有权限验证的接口时会调用查看权限,于是猜想注解无效,发现shiro的权限注解需要开启才能有用,添加在配置文件中加入advisorAutoProxyCreatorgetAuthorizationAttributeSourceAdvisor两个bean开启shiro注解,解决问题。

 

shiroFilterFactoryBean 的Url配置

可以配置在数据库,也可以配置在配置文件,作者这里配置在配置文件中的

 

@Data
@Configuration
@ConfigurationProperties(prefix = "amsh")
public class UrlConfig {/*** 不需要拦截的url*/private List<String> anonUrl;/*** openapi分页大小*/private Integer openPageSize;
}

 

通过这种方式排除配置

注意:

1.这里要用LinkedHashMap 保证有序

2.filterChain基于短路机制,即最先匹配原则,

3.像anon、authc等都是Shiro为我们实现的过滤器

附录:

1.Shiro拦截机制表
Filter NameClassDescription
anonorg.apache.shiro.web.filter.authc.AnonymousFilter匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例/static/**=anon
authcorg.apache.shiro.web.filter.authc.FormAuthenticationFilter基于表单的拦截器;如/**=authc,如果没有登录会跳到相应的登录页面登录
authcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilterBasic HTTP身份验证拦截器
logoutorg.apache.shiro.web.filter.authc.LogoutFilter退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/),示例/logout=logout
noSessionCreationorg.apache.shiro.web.filter.session.NoSessionCreationFilter不创建会话拦截器,调用subject.getSession(false)不会有什么问题,但是如果subject.getSession(true)将抛出DisabledSessionException异常
permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例/user/**=perms["user:create"]
portorg.apache.shiro.web.filter.authz.PortFilter端口拦截器,主要属性port(80):可以通过的端口;示例/test= port[80],如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样
restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilterrest风格拦截器,自动根据请求方法构建权限字符串;示例/users=rest[user],会自动拼出user:read,user:create,user:update,user:delete权限字符串进行权限匹配(所有都得匹配,isPermittedAll)
rolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFilter角色授权拦截器,验证用户是否拥有所有角色;示例/admin/**=roles[admin]
sslorg.apache.shiro.web.filter.authz.SslFilterSSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口443;其他和port拦截器一样;
userorg.apache.shiro.web.filter.authc.UserFilter用户拦截器,用户已经身份验证/记住我登录的都可;示例/**=user
oauth2过滤器

-上面配置文件中加入的类- filters.put("oauth3", new OAuth3Filter());


/*** oauth2过滤器*/
@Slf4j
public class OAuth3Filter extends AuthenticatingFilter {@Overrideprotected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {//获取请求tokenString token = getRequestToken((HttpServletRequest) request);if (StringUtils.isEmpty(token)) {//清理线程变量ThreadLocalUserInfo.remove();return null;}return new OAuth3Token(token);}@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {return true;}return false;}@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {//获取请求token,如果token不存在,直接返回401HttpServletRequest httpRequest = (HttpServletRequest) request;String token = getRequestToken(httpRequest);if (StringUtils.isEmpty(token)) {log.info("OAuth3Filter过滤器,onAccessDenied获取token:{}", token);HttpServletResponse httpResponse = (HttpServletResponse) response;httpResponse.setContentType("application/json;charset=UTF-8");UnifiedResult result = UnifiedResult.fail(HttpStatus.UNAUTHORIZED.value(), "无效的token");String json = JSON.toJSONString(result);httpResponse.getWriter().print(json);return false;}return executeLogin(request, response);}@Overrideprotected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {HttpServletRequest httpRequest = (HttpServletRequest) request;HttpServletResponse httpResponse = (HttpServletResponse) response;httpResponse.setContentType("application/json;charset=UTF-8");try {//清理线程变量ThreadLocalUserInfo.remove();//处理登录失败的异常Throwable throwable = e.getCause() == null ? e : e.getCause();UnifiedResult result = UnifiedResult.fail(HttpStatus.UNAUTHORIZED.value(), throwable.getMessage());String json = JSON.toJSONString(result);httpResponse.getWriter().print(json);} catch (IOException e1) {e1.printStackTrace();}return false;}/*** 获取请求的token*/private String getRequestToken(HttpServletRequest httpRequest) {//从header中获取tokenString token = httpRequest.getHeader("auth");//如果header中不存在token,则从参数中获取tokenif (StringUtils.isEmpty(token)) {token = httpRequest.getParameter("auth");}return token;}}

 

2、实现自定义Realm类

自定义Realm类需要继承 AuthorizingRealm 类,实现 doGetAuthorizationInfo()和doGetAuthenticationInfo()方法即可 ,

doGetAuthorizationInfo() 方法是进行授权的方法,获取角色的权限信息

doGetAuthenticationInfo()方法是进行用户认证的方法,验证用户名和密码

/*** @ClassName MyShiroRealm* @Description TODO* @Author fqCoder* @Date 2020/2/29 3:08* @Version 1.0*/
@Service
public class MyShiroRealm  extends AuthorizingRealm {@Autowiredprivate UserMapper userMapper;@Autowiredprivate UserRoleMapper userRoleMapper;@Autowiredprivate RolePermissionMapper rolePermissionMapper;/*** 获取用户角色和权限* @param principal* 授权(验证权限时调用)* 获取授权信息(把数据库中shiroID加入到shiro中管理,然后如果ctronler层方法加上 @RequiresPermissions注解才会调用这个方法和数组进行比对)* 只有当需要检测用户权限的时候才会调用此方法* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {if(principal == null){throw new AuthorizationException("principals should not be null");}User userInfo= (User) SecurityUtils.getSubject().getPrincipal();System.out.println("用户-->"+userInfo.getUsername()+"获取权限中");SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();//用户获取角色集List<Role> roleList=userRoleMapper.findByUserName(userInfo.getUsername());Set<String> roleSet=new HashSet<>();for (Role r:roleList){Integer roleId=r.getId();//获取角色idsimpleAuthorizationInfo.addRole(r.getName());//添加角色名字List<Permission> permissionList=rolePermissionMapper.findByRoleId(roleId);for (Permission p:permissionList){//添加权限simpleAuthorizationInfo.addStringPermission(p.getName());}}System.out.println("角色为-> " + simpleAuthorizationInfo.getRoles());System.out.println("权限为-> " + simpleAuthorizationInfo.getStringPermissions());return simpleAuthorizationInfo;}/*** 登录认证* @param token* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//获取用户输入的用户名密码String username= (String) token.getPrincipal();String password=new String((char[])token.getCredentials());System.out.println("用户输入--->username:"+username+"-->password:"+password);//在数据库中查询User userInfo=userMapper.selectByName(username);if (userInfo == null) {throw new UnknownAccountException("用户名或密码错误!");}if (!password.equals(userInfo.getPassword())) {throw new IncorrectCredentialsException("用户名或密码错误!");}SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, // 用户名userInfo.getPassword(), // 密码getName() // realm name);return authenticationInfo;}
}

其中UnknownAccountException等异常为Shiro自带异常,Shiro具有丰富的运行时AuthenticationException层次结构,可以准确指出尝试失败的原因 

创建一个UserController.class类

用于处理User类的访问请求,并使用Shiro权限注解控制权限:

 

/*** @ClassName UserController* @Description TODO* @Author fqCoder* @Date 2020/3/3 15:14* @Version 1.0*/
@RestController
@RequestMapping("/user")
public class UserController {@RequiresPermissions("user:queryAll")@GetMapping("/queryAll")public String queryAll(){//只演示框架...功能不实现return "查询列表";}@RequiresPermissions("user:add")@GetMapping("/add")public String userAdd(){return "添加用户";}@RequiresPermissions("user:delete")@GetMapping("/delete")public String userDelete(){return "删除用户";}
}

 这只是前后端分离时的shiro验证,不涉及页面的代码

以上就是shiro 整合的操作步骤

三、下面是拓展场景——在项目启动的时候进行全局扫描自动生成权限资源

 假如最开始框架设计时,并没有引入权限框架,中途是安测扫描出越权问题,进行补漏。

这时已经实现了很多接口,设计到加权限问题,如果是注解的方式,一个一个加不太现实,那得累死,这时就可以考虑在项目启动的时候进行全局扫描自动生成权限资源

步骤:

1、定义需要进行权限控制的接口-自动扫描生成的资源注解 

/*** 权限注解,用于标识需要权限处理的接口** @author*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Auth {/*** 权限id,模块id + 方法id需要唯一*/long id();/*** 权限名称*/String name();int type() default 1;/*** 权限菜单路径*/String perssionMenus() default "";
}

 定义组件,项目启动时扫描

/*** @author*/
@Slf4j
@Component
public class ApplicationStartup implements ApplicationRunner {@Resourceprivate IBackendRabcResourceService resourceService;@Overridepublic void run(ApplicationArguments args) {log.info("--------------->【ApplicationStartup接口初始化】-------> 开始初始化扫描所有需要权限的接口资源");resourceService.initRoleResource();log.info("--------------->【ApplicationStartup接口初始化完成】------->");}}

service层

    @Overridepublic void initRoleResource() {List<RabcResource> listAll = new LinkedList<>();List<RabcResource> moduleResourceslist = getAuthModuleResources();// 扫描并获取所有需要权限处理的接口资源(该方法逻辑写在下面)List<RabcResource> methodResourceslist = getAuthMethodResources();// 如果权限资源为空,就不用走后续数据插入步骤if (CollUtil.isEmpty(moduleResourceslist) && CollUtil.isEmpty(methodResourceslist)) {return;}// 先删除所有操作权限类型的权限资源,待会再新增资源,以实现全量更新(注意哦,数据库中不要设置外键,否则会删除失败)if (CollUtil.isNotEmpty(moduleResourceslist)) {log.info("--------------->开始删除已存在的模块资源,进行全量更新");this.deleteResourceByType(0);}if (CollUtil.isNotEmpty(methodResourceslist)) {log.info("--------------->开始删除已存在的接口资源,进行全量更新");this.deleteResourceByType(1);}listAll.addAll(moduleResourceslist);listAll.addAll(methodResourceslist);// 将资源数据批量添加到数据库this.insertResources(listAll);List<RabcRoleResource> roleResources = this.retrieveItemChild();if (CollUtil.isNotEmpty(roleResources)) {log.info("--------------->开始删除已存在的角色资源,进行全量更新");this.deleteRoleResourceByType(0);}this.insertRoleResource(roleResources);log.info("--------------->【ApplicationStartup接口初始化完成】------->");}/*** 扫描并返回所有需要权限处理的模块资源*/private List<RabcResource> getAuthModuleResources() {// 接下来要添加到数据库的资源List<RabcResource> list = new LinkedList<>();// 拿到所有接口信息,并开始遍历Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingInfoHandlerMapping.getHandlerMethods();handlerMethods.forEach((info, handlerMethod) -> {// 拿到类(模块)上的权限注解Auth moduleAuth = handlerMethod.getBeanType().getAnnotation(Auth.class);if (moduleAuth == null) {return;}if (0 == moduleAuth.type()) {String path = info.getPatternsCondition().getPatterns().toArray()[0].toString();String moduleAuthName = "/" + path.split("/")[1];RabcResource resource = list.stream().filter(item -> moduleAuthName.equals(item.getPath())).findFirst().orElse(null);if (ObjectUtil.isEmpty(resource)) {// 将权限名、资源路径、资源类型组装成资源对象,并添加集合中RabcResource resourceEntity = new RabcResource();resourceEntity.setType(0).setPath(moduleAuthName).setId(moduleAuth.id()).setPerssionmenus(moduleAuth.perssionMenus()).setName(moduleAuth.name());list.add(resourceEntity);}}});log.info("【getAuthModuleResources方法】-------> 获取到所有需要权限控制的模块资源 模块数量:{}", list.size());return list;}/*** 扫描并返回所有需要权限处理的接口资源*/private List<RabcResource> getAuthMethodResources() {// 接下来要添加到数据库的资源List<RabcResource> list = new LinkedList<>();// 拿到所有接口信息,并开始遍历Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingInfoHandlerMapping.getHandlerMethods();handlerMethods.forEach((info, handlerMethod) -> {// 拿到类(模块)上的权限注解Auth moduleAuth = handlerMethod.getBeanType().getAnnotation(Auth.class);// 拿到接口方法上的权限注解Auth methodAuth = handlerMethod.getMethod().getAnnotation(Auth.class);// 模块注解和方法注解缺一个都代表不进行权限处理if (moduleAuth == null || methodAuth == null) {return;}// 拿到该接口方法的请求方式(GET、POST等)Set<RequestMethod> methods = info.getMethodsCondition().getMethods();// 如果一个接口方法标记了多个请求方式,权限id是无法识别的,不进行处理if (methods.size() != 1) {return;}// 将请求方式和路径用`:`拼接起来,以区分接口。比如:GET:/user/{id}、POST:/user/{id}String path = methods.toArray()[0] + ":" + info.getPatternsCondition().getPatterns().toArray()[0];String methodPath = info.getPatternsCondition().getPatterns().toArray()[0].toString();// 将权限名、资源路径、资源类型组装成资源对象,并添加集合中RabcResource resourceEntity = new RabcResource();resourceEntity.setType(1).setPath(path).setPerssionmenus(moduleAuth.perssionMenus()).setName(methodAuth.name()).setId(moduleAuth.id() + methodAuth.id()).setMethod(methodPath);list.add(resourceEntity);});log.info("【getAuthMethodResources方法】-------> 获取到所有需要权限控制的模块下的具体方法接口资源 模块下方法数量{}", list.size());return list;}

 

@Auth(id=1000,name = "附件文件上传下载接口", type = 0)
public class AnnexFileController {@Autowiredprivate AnnexFileService annexFileService;@PostMapping("/upload")@ApiOperation("文件上传")@Auth(id=1,name = "附件文件上传")public Result<File> upload(@RequestPart("file") MultipartFile file) throws Exception {Member user = LoginUtils.getCurrentMember();AmshFile upload = annexFileService.upload(file, user.getUsername());return Result.ok(upload);}@GetMapping("/download")@ApiOperation(value = "下载文件")@Auth(id=2,name = "附件文件下载")public Result<?> download(@ApiParam(value = "文件ID", required = true) String fileId, HttpServletResponse response) {annexFileService.downloadFile(fileId, response);return Result.ok();}

注意只有添加了该注解@Auth(id=2,name = "附件文件下载") ,类型才会进行接口资源扫描,才会加入到权限资源的数据库中,加入到资源数据库中,需要自己修改逻辑。只有同时实现了下面两个注解才会实现控制访问,一个是生成接口权限资源的,一个是接口访问的时候进行权限验证的

 以上是开发过程中遇到问题并解决之后的总结

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com