ApacheShiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。 三个核心组件:Subject,SecurityManager和Realms。Subject:即当前操作用户。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(DaemonAccount)或其他类似事物。它仅仅意味着当前跟软件交互的东西。 Subject:代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。 Realm:Realm充当了Shiro与应用安全数据间的桥梁或者连接器。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。1、新建表 主要有下列5个表,分别为:用户、角色、菜单、用户角色关联表、角色菜单关联表是比较符合我们基本的业务需求的 然后这里提供测试的sql结构和数据SETNAMESutf8mb4;SETFOREIGNKEYCHECKS0;TablestructureforsysmenuDROPTABLEIFEXISTSsysmenu;CREATETABLEsysmenu(idint(0)NOTNULLAUTOINCREMENTCOMMENTid,namevarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT名称,pathvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT路径,permvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT权限,createbyvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT创建人,createtimedatetime(0)NULLDEFAULTNULLCOMMENT创建时间,updatebyvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT更新人,updatetimedatetime(0)NULLDEFAULTNULLCOMMENT更新时间,PRIMARYKEY(id)USINGBTREE)ENGINEInnoDBAUTOINCREMENT3CHARACTERSETutf8mb4COLLATEutf8mb40900aiciCOMMENT菜单ROWFORMATDynamic;RecordsofsysmenuINSERTINTOsysmenuVALUES(1,用户查询,NULL,user:list,NULL,NULL,NULL,NULL);INSERTINTOsysmenuVALUES(2,用户新增,NULL,user:add,NULL,NULL,NULL,NULL);TablestructureforsysroleDROPTABLEIFEXISTSsysrole;CREATETABLEsysrole(idint(0)NOTNULLAUTOINCREMENTCOMMENTid,keyvarchar(255)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT唯一标识,namevarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT名称,createbyvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT创建人,createtimedatetime(0)NULLDEFAULTNULLCOMMENT创建时间,updatebyvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT更新人,updatetimedatetime(0)NULLDEFAULTNULLCOMMENT更新时间,PRIMARYKEY(id)USINGBTREE)ENGINEInnoDBAUTOINCREMENT3CHARACTERSETutf8mb4COLLATEutf8mb40900aiciCOMMENT角色表ROWFORMATDynamic;RecordsofsysroleINSERTINTOsysroleVALUES(1,admin,超级管理员,NULL,NULL,NULL,NULL);TablestructureforsysrolemenuDROPTABLEIFEXISTSsysrolemenu;CREATETABLEsysrolemenu(idint(0)NOTNULLAUTOINCREMENTCOMMENTid,roleidvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT角色id,menuidvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT菜单id,createbyvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT创建人,createtimedatetime(0)NULLDEFAULTNULLCOMMENT创建时间,updatebyvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT更新人,updatetimedatetime(0)NULLDEFAULTNULLCOMMENT更新时间,PRIMARYKEY(id)USINGBTREE)ENGINEInnoDBAUTOINCREMENT2CHARACTERSETutf8mb4COLLATEutf8mb40900aiciCOMMENT角色菜单关联表ROWFORMATDynamic;RecordsofsysrolemenuINSERTINTOsysrolemenuVALUES(1,1,1,NULL,NULL,NULL,NULL);TablestructureforsysuserDROPTABLEIFEXISTSsysuser;CREATETABLEsysuser(idint(0)NOTNULLAUTOINCREMENTCOMMENTid,namevarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT姓名,usernamevarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT用户名,passwordvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT密码,createbyvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT创建人,createtimedatetime(0)NULLDEFAULTNULLCOMMENT创建时间,updatebyvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT更新人,updatetimedatetime(0)NULLDEFAULTNULLCOMMENT更新时间,PRIMARYKEY(id)USINGBTREE)ENGINEInnoDBAUTOINCREMENT4CHARACTERSETutf8mb4COLLATEutf8mb40900aiciCOMMENT用户表ROWFORMATDynamic;RecordsofsysuserINSERTINTOsysuserVALUES(1,超级,admin,e10adc3949ba59abbe56e057f20f883e,NULL,NULL,NULL,NULL);TablestructureforsysuserroleDROPTABLEIFEXISTSsysuserrole;CREATETABLEsysuserrole(idint(0)NOTNULLAUTOINCREMENTCOMMENTid,useridvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT用户id,roleidvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT角色id,createbyvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT创建人,createtimedatetime(0)NULLDEFAULTNULLCOMMENT创建时间,updatebyvarchar(32)CHARACTERSETutf8mb4COLLATEutf8mb40900aiciNULLDEFAULTNULLCOMMENT更新人,updatetimedatetime(0)NULLDEFAULTNULLCOMMENT更新时间,PRIMARYKEY(id)USINGBTREE)ENGINEInnoDBAUTOINCREMENT3CHARACTERSETutf8mb4COLLATEutf8mb40900aiciCOMMENT用户角色关联表ROWFORMATDynamic;RecordsofsysuserroleINSERTINTOsysuserroleVALUES(1,1,1,NULL,NULL,NULL,NULL);SETFOREIGNKEYCHECKS1; 数据如下:用户,admin,密码123456角色:超级管理员admin菜单:user:list用户查询注:这里只是测试使用、如果是实际案例的话,超级管理员是拥有全部权限的2、代码生成和mybatisplus整合 这里具体的可以查看该文章生成对应的类controller、service、serviceImpl、mapper、mapper。xml等3、编写AuthenticationToken实现类 这个目的主要是在Realm的认证和授权的时候,能够获取到tokenpackagecom。walker。shiro。common。config。shiro;importlombok。AllArgsConstructor;importorg。apache。shiro。authc。AuthenticationToken;author:walkertime:2023210description:AuthenticationToken的实现类AllArgsConstructorpublicclassJwtTokenimplementsAuthenticationToken{privateStringtoken;OverridepublicObjectgetPrincipal(){returntoken;}OverridepublicObjectgetCredentials(){returntoken;}}4、realm配置 该为主要用于配置认证和权限等packagecom。walker。shiro。common。config。shiro;importcn。hutool。core。collection。CollUtil;importcom。alibaba。fastjson。JSON;importcom。walker。shiro。common。utils。Assert;importcom。walker。shiro。common。utils。JWTUtils;importcom。walker。shiro。domain。model。SysMenu;importcom。walker。shiro。domain。model。SysRole;importcom。walker。shiro。domain。model。SysUser;importlombok。extern。slf4j。Slf4j;importorg。apache。shiro。authc。AuthenticationException;importorg。apache。shiro。authc。AuthenticationInfo;importorg。apache。shiro。authc。AuthenticationToken;importorg。apache。shiro。authc。SimpleAuthenticationInfo;importorg。apache。shiro。authz。AuthorizationInfo;importorg。apache。shiro。authz。SimpleAuthorizationInfo;importorg。apache。shiro。realm。AuthorizingRealm;importorg。apache。shiro。subject。PrincipalCollection;importorg。springframework。beans。factory。annotation。Autowired;importorg。springframework。stereotype。Component;importjava。util。List;importjava。util。stream。Collectors;author:walkertime:2023210description:tokenRealm用于配置认证和权限等ComponentSlf4j继承AuthorizingRealmpublicclassTokenRealmextendsAuthorizingRealm{AutowiredprivateJWTUtilsjwtUtils;生效条件,因为realm可以有很多个,所以需要进行设置Overridepublicbooleansupports(AuthenticationTokentoken){returntokeninstanceofJwtToken;}权限配置OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipalCollection){StringtokenprincipalCollection。toString();Assert。isBlank(token,token不存在,请先登录);SysUseruserEntityjwtUtils。parseToken(token,SysUser。class);Assert。isNull(userEntity,token解析失败,请重新登录);log。info(登录用户信息:{},JSON。toJSONString(userEntity));SimpleAuthorizationInfoauthorizationInfonewSimpleAuthorizationInfo();设置角色ListSysRolerolesuserEntity。getRoles();Assert。isCollEmpty(roles,该用户未绑定角色);authorizationInfo。addRoles(roles。stream()。map(SysRole::getKey)。collect(Collectors。toSet()));设置权限for(SysRolerole:roles){ListSysMenumenuEntityListrole。getMenus();if(CollUtil。isEmpty(menuEntityList)){continue;}authorizationInfo。addStringPermissions(menuEntityList。stream()。map(SysMenu::getPerm)。collect(Collectors。toSet()));}log。info(用户{}shiro角色:{}权限:{},userEntity。getUsername(),authorizationInfo。getRoles(),authorizationInfo。getStringPermissions());returnauthorizationInfo;}认证配置OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokenauthenticationToken)throwsAuthenticationException{Stringtoken(String)authenticationToken。getCredentials();returnnewSimpleAuthenticationInfo(token,token,getName());}}5、token过滤器packagecom。walker。shiro。common。config。shiro;importcn。hutool。core。util。StrUtil;importcom。walker。shiro。common。constants。RedisConstant;importcom。walker。shiro。common。properties。JWTProperties;importcom。walker。shiro。common。utils。HttpUtils;importlombok。AllArgsConstructor;importlombok。SneakyThrows;importorg。apache。shiro。web。filter。authc。BasicHttpAuthenticationFilter;importorg。springframework。data。redis。core。StringRedisTemplate;importjavax。servlet。ServletRequest;importjavax。servlet。ServletResponse;importjavax。servlet。http。HttpServletRequest;author:walkertime:2023210description:token过滤器publicclassTokenFilterextendsBasicHttpAuthenticationFilter{这里因为TokenFilter不是容器,所以需要设置属性,然后通过初始化的时候传递privateStringRedisTemplateredisTemplate;privateJWTPropertiesjwtProperties;publicTokenFilter(StringRedisTemplateredisTemplate,JWTPropertiesjwtProperties){this。redisTemplateredisTemplate;this。jwtPropertiesjwtProperties;}isAccessAllowed:是否允许访问OverrideprotectedbooleanisAccessAllowed(ServletRequestrequest,ServletResponseresponse,ObjectmappedValue){if(isLoginAttempt(request,response)){try{executeLogin(request,response);}catch(Exceptione){e。printStackTrace();}returntrue;}returnfalse;}onAccessDenied:拒绝访问时,需要做什么处理如果isAccessAllowed方法返回True,则不会再调用onAccessDenied方法,如果isAccessAllowed方法返回Flase,则会继续调用onAccessDenied方法。而onAccessDenied方法里面则是具体执行登陆的地方。由于我们已经登陆,所以此方法就会返回True(filter放行),所以上面的onPreHandle方法里面的onAccessDenied方法就不会被执行。OverrideprotectedbooleanonAccessDenied(ServletRequestrequest,ServletResponseresponse)throwsException{这里直接返回false,如果不支持访问时,直接为false即可returnfalse;}尝试登录SneakyThrowsOverrideprotectedbooleanisLoginAttempt(ServletRequestrequest,ServletResponseresponse){HttpServletRequestreq(HttpServletRequest)request;Stringtokenreq。getHeader(jwtProperties。getHeader());if(StrUtil。isEmpty(token)){HttpUtils。resp(response,请先进行登录);returnfalse;}判断token是否存在,如果不存在则证明失效或者过期StringsredisTemplate。opsForValue()。get(RedisConstant。TOKENUSERKEYtoken);if(StrUtil。isEmpty(s)){HttpUtils。resp(response,token已失效不存在,请重新登录);returnfalse;}returntrue;}执行登录OverrideprotectedbooleanexecuteLogin(ServletRequestrequest,ServletResponseresponse)throwsException{HttpServletRequestreq(HttpServletRequest)request;Stringtokenreq。getHeader(jwtProperties。getHeader());JwtTokenjwtTokennewJwtToken(token);getSubject(request,response)。login(jwtToken);returntrue;}}6、shiro配置packagecom。walker。shiro。common。config。shiro;importcom。walker。shiro。common。properties。JWTProperties;importcom。walker。shiro。common。utils。JWTUtils;importorg。apache。shiro。mgt。DefaultSessionStorageEvaluator;importorg。apache。shiro。mgt。DefaultSubjectDAO;importorg。apache。shiro。mgt。SecurityManager;importorg。apache。shiro。spring。security。interceptor。AuthorizationAttributeSourceAdvisor;importorg。apache。shiro。spring。web。ShiroFilterFactoryBean;importorg。apache。shiro。web。mgt。DefaultWebSecurityManager;importorg。springframework。aop。framework。autoproxy。DefaultAdvisorAutoProxyCreator;importorg。springframework。beans。factory。annotation。Autowired;importorg。springframework。boot。autoconfigure。condition。ConditionalOnMissingBean;importorg。springframework。context。annotation。Bean;importorg。springframework。context。annotation。Configuration;importorg。springframework。data。redis。core。StringRedisTemplate;importjavax。servlet。Filter;importjava。util。HashMap;shiro配置ConfigurationpublicclassShiroConfig{AutowiredprivateTokenRealmtokenRealm;AutowiredprivateJWTPropertiesjwtProperties;AutowiredprivateStringRedisTemplateredisTemplate;免校验StringANONanon;filter名称tokenStringFILTERTOKENtoken;所有路径StringALLPATH;过滤工厂配置哪些请求需要过滤器,哪些不需要BeanpublicShiroFilterFactoryBeanshiroFilterFactoryBean(SecurityManagersecurityManager){ShiroFilterFactoryBeanbeannewShiroFilterFactoryBean();bean。setSecurityManager(securityManager);添加token过滤器HashMapString,FilterfilterHashMapnewHashMap(1);filterHashMap。put(FILTERTOKEN,newTokenFilter(redisTemplate,jwtProperties));bean。setFilters(filterHashMap);请求过滤需要认证:authc不需要认证:anontoken:需要经过token过滤器处理HashMapString,StringmapnewHashMap();白名单String〔〕whiteListjwtProperties。getWhiteList()。split(,);for(Strings:whiteList){map。put(s,ANON);}map。put(ALLPATH,FILTERTOKEN);bean。setFilterChainDefinitionMap(map);returnbean;}安全管理器设置realm和关闭sessionBeanpublicSecurityManagersecurityManager(){DefaultWebSecurityManagermanagernewDefaultWebSecurityManager();设置realmmanager。setRealm(tokenRealm);关闭shiro自带的session,现在使用jwt不需要sessionDefaultSubjectDAOsubjectDAOnewDefaultSubjectDAO();DefaultSessionStorageEvaluatorsessionStorageEvaluatornewDefaultSessionStorageEvaluator();sessionStorageEvaluator。setSessionStorageEnabled(false);subjectDAO。setSessionStorageEvaluator(sessionStorageEvaluator);manager。setSubjectDAO(subjectDAO);returnmanager;}根据springframeworkreference,DefaultAdvisorAutoProxyCreator创建代理更加通用强大,使用此机制包括:a。指定一个DefaultAdvisorAutoProxyCreatorBean的定义。b。指定在相同或相关的上下文中任意数量的Advisor。注意,必须是Advisor,而不仅仅是interceptor或advice。这是必要的,因为必须有一个切点被评估,以便检查每个advice到候选bean定义是否合格ConditionalOnMissingBean,它是修饰bean的一个注解,主要实现的是,当你的bean被注册之后,如果而注册相同类型的bean,就不会成功,它会保证你的bean只有一个,即你的实例只有一个,当你注册多个相同的bean时,会出现异常BeanConditionalOnMissingBeanpublicDefaultAdvisorAutoProxyCreatordefaultAdvisorAutoProxyCreator(){DefaultAdvisorAutoProxyCreatorproxyCreatornewDefaultAdvisorAutoProxyCreator();proxyCreator。setProxyTargetClass(true);returnproxyCreator;}授权属性源顾问BeanpublicAuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor(SecurityManagersecurityManager){AuthorizationAttributeSourceAdvisoradvisornewAuthorizationAttributeSourceAdvisor();advisor。setSecurityManager(securityManager);returnadvisor;}}7、controller测试类packagecom。walker。shiro。domain。controller;importcom。sun。media。sound。FFT;importcom。walker。shiro。domain。component。UserComponent;importcom。walker。shiro。domain。model。common。R;importcom。walker。shiro。domain。model。form。UserLoginForm;importorg。springframework。beans。factory。annotation。Autowired;importorg。springframework。web。bind。annotation。PostMapping;importorg。springframework。web。bind。annotation。RequestBody;importorg。springframework。web。bind。annotation。RequestMapping;importorg。springframework。web。bind。annotation。RestController;importjavax。validation。Valid;p用户表前端控制器authorwalkersince20230209RestControllerRequestMapping(sysuser)publicclassSysUserController{AutowiredprivateUserComponentuserComponent;登录接口PostMapping(login)publicRlogin(RequestBodyValidUserLoginFormform){returnR。ok(userComponent。login(form));}新增用户接口PostMapping(add)publicRadd(RequestBodyValidUserLoginFormform){userComponent。add(form);returnR。ok();}}packagecom。walker。shiro。domain。controller;importcom。walker。shiro。domain。model。common。R;importorg。apache。shiro。authz。annotation。RequiresPermissions;importorg。apache。shiro。authz。annotation。RequiresRoles;importorg。springframework。web。bind。annotation。GetMapping;importorg。springframework。web。bind。annotation。RequestMapping;importorg。springframework。web。bind。annotation。RestController;RestControllerRequestMapping(test)publicclassTestController{GetMapping(get)publicRget(){returnR。ok(hello);}GetMapping(roleadmin)RequiresRoles(admin)publicRroleadmin(){returnR。ok(OK);}GetMapping(rolevip)角色判断RequiresRoles(vip)publicRrolevip(){returnR。ok(OK);}GetMapping(userAdd)RequiresPermissions(user:add)publicRuserAdd(){returnR。ok(fail);}GetMapping(userList)RequiresPermissions(user:list)publicRuserList(){returnR。ok(ok);}}8、使用postman进行测试login接口 login接口因为是设置为白名单的,所以不需要Token,直接放行可以发现能够获取到token, 之后将登录之后的token复制一下,用来其他接口的测试get接口,不带token时 可以发现,提示需要tokenget接口,带token 当带上token时,就ok了roleadmin接口 该接口需要admin这个角色,然后目前我们的admin账号刚好有该角色 发现是可以获取到结果的rolevip接口 该接口需要vip角色,admin没有,所以结果提示为没有权限userAdd接口 由于admin只有user:list账号,所以不具备该权限userList 调用成功 总结 权限管理可以说在大部分的项目都是需要使用的了,shiro具体权限和认证的配置,是符合我们企业项目开发的,所以我们还是得学习一下,希望对你有所帮助哈 原文链接;https:mp。weixin。qq。comsvpJm31F4UjLxP9Mnaqna4w