关系经济人类预测化学自然
中准网
自然科学
知识物理
化学生物
地理解释
预测理解
本质社会
人类现象
行为研究
经济政治
心理结构
关系指导
人文遗产

Shiro安全框架最佳实践

3月19日 栀璃鸢投稿
  背景
  我们在做系统开发的时候,都少不了权限这块,认证授权对于系统安全、隐私合规尤其重要,最常见权限模型是RBAC(用户角色权限)。
  我们将整体授权类型划分为三级,依据不同等级的授权,来控制授权的最小的颗粒度,今天来讲下前两个,数据权限放到后续。一级权限:访问权限二级权限:菜单、按钮权限三级权限:数据权限解决方案
  市面上做鉴权框架不少,我们今天讲下Shiro,它比较轻量级,使用起来比较简单。
  其基本功能点如下图所示:
  Authentication:身份认证登录,验证用户是不是拥有相应的身份;Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;SessionManagement:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;WebSupport:Web支持,可以非常容易的集成到Web环境;Caching:缓存,比如用户登录后,其用户信息、拥有的角色权限不必每次去查,这样可以提高效率;Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;Testing:提供测试支持;RunAs:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;RememberMe:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
  记住一点,Shiro不会去维护用户、维护权限;这些需要我们自己去设计提供;然后通过相应的接口注入给Shiro即可。
  接下来我们分别从外部和内部来看看Shiro的架构,对于一个好的框架,从外部来看应该具有非常简单易于使用的API,且API契约明确;从内部来看的话,其应该有一个可扩展的架构,即非常容易插入用户自定义实现,因为任何框架都不能满足所有需求。
  首先,我们从外部来看Shiro吧,即从应用程序角度的来观察如何使用Shiro完成工作。如下图:
  可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是S其每个API的含义:
  Subject:主体,代表了当前用户,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityM可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
  SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有S可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
  Realm:域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
  也就是说对于我们而言,最简单的一个Shiro应用:应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityM我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
  从以上也可以看出,Shiro不提供维护用户权限,而是通过Realm让开发人员自己注入。工程实施
  springbootshirojwtredis
  引入maven依赖!shiroshiro。version1。9。1shiro。versionjavajwt。version3。11。0javajwt。versionshiroredis。version3。1。0shiroredis。versiondependencygroupIdorg。springframework。bootgroupIdspringbootstarterdataredisartifactIddependency!JWTdependencygroupIdcom。auth0groupIdjavajwtartifactIdversion{javajwt。version}versiondependency!shirodependencygroupIdorg。apache。shirogroupIdshirospringbootstarterartifactIdversion{shiro。version}versiondependency!shiroredisdependencygroupIdorg。crazycakegroupIdshiroredisartifactIdversion{shiroredis。version}versionexclusionsexclusiongroupIdorg。apache。shirogroupIdshirocoreartifactIdexclusionexclusionsdependency
  配置文件(ShiroConfig)Slf4jConfigurationpublicclassShiroConfig{ResourceprivateLettuceConnectionFactorylettuceConnectionFAutowiredprivateEResourceprivateJeecgBaseConfigjeecgBaseCFilterChain定义说明1、一个URL可以配置多个Filter,使用逗号分隔2、当设置多个过滤器时,全部验证通过,才视为通过3、部分过滤器可指定参数,如perms,rolesBean(shiroFilterFactoryBean)publicShiroFilterFactoryBeanshiroFilter(SecurityManagersecurityManager){CustomShiroFilterFactoryBeanshiroFilterFactoryBeannewCustomShiroFilterFactoryBean();shiroFilterFactoryBean。setSecurityManager(securityManager);拦截器MapString,StringfilterChainDefinitionMapnewLinkedHashMapString,String();支持yml方式,配置拦截排除if(jeecgBaseConfig!nulljeecgBaseConfig。getShiro()!null){StringshiroExcludeUrlsjeecgBaseConfig。getShiro()。getExcludeUrls();if(oConvertUtils。isNotEmpty(shiroExcludeUrls)){String〔〕permissionUrlshiroExcludeUrls。split(,);for(Stringurl:permissionUrl){filterChainDefinitionMap。put(url,anon);}}}登录接口排除filterChainDefinitionMap。put(login,anon);添加自己的过滤器并且取名为jwtMapString,FilterfilterMapnewHashMapString,Filter(1);filterMap。put(jwt,newJwtFilter());shiroFilterFactoryBean。setFilters(filterMap);!过滤链定义,从上向下顺序执行,一般将放在最为下边filterChainDefinitionMap。put(,jwt);未授权界面返回JSONshiroFilterFactoryBean。setUnauthorizedUrl(403);shiroFilterFactoryBean。setLoginUrl(403);shiroFilterFactoryBean。setFilterChainDefinitionMap(filterChainDefinitionMap);returnshiroFilterFactoryB}Bean(securityManager)publicDefaultWebSecurityManagersecurityManager(ShiroRealmmyRealm){DefaultWebSecurityManagersecurityManagernewDefaultWebSecurityManager();securityManager。setRealm(myRealm);DefaultSubjectDAOsubjectDAOnewDefaultSubjectDAO();DefaultSessionStorageEvaluatordefaultSessionStorageEvaluatornewDefaultSessionStorageEvaluator();defaultSessionStorageEvaluator。setSessionStorageEnabled(false);subjectDAO。setSessionStorageEvaluator(defaultSessionStorageEvaluator);securityManager。setSubjectDAO(subjectDAO);自定义缓存实现,使用redissecurityManager。setCacheManager(redisCacheManager());returnsecurityM}下面的代码是添加注解支持returnBeanDependsOn(lifecycleBeanPostProcessor)publicDefaultAdvisorAutoProxyCreatordefaultAdvisorAutoProxyCreator(){DefaultAdvisorAutoProxyCreatordefaultAdvisorAutoProxyCreatornewDefaultAdvisorAutoProxyCreator();defaultAdvisorAutoProxyCreator。setProxyTargetClass(true);解决重复代理问题github994添加前缀判断不匹配任何AdvisordefaultAdvisorAutoProxyCreator。setUsePrefix(true);defaultAdvisorAutoProxyCreator。setAdvisorBeanNamePrefix(noadvisor);returndefaultAdvisorAutoProxyC}BeanpublicstaticLifecycleBeanPostProcessorlifecycleBeanPostProcessor(){returnnewLifecycleBeanPostProcessor();}BeanpublicAuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor(DefaultWebSecurityManagersecurityManager){AuthorizationAttributeSourceAdvisoradvisornewAuthorizationAttributeSourceAdvisor();advisor。setSecurityManager(securityManager);}cacheManager缓存redis实现使用的是shiroredis开源插件returnpublicRedisCacheManagerredisCacheManager(){log。info((1)创建缓存管理器RedisCacheManager);RedisCacheManagerredisCacheManagernewRedisCacheManager();redisCacheManager。setRedisManager(redisManager());redis中针对不同用户缓存(此处的id需要对应user实体中的id字段,用于唯一标识)redisCacheManager。setPrincipalIdFieldName(id);用户权限信息缓存时间redisCacheManager。setExpire(200000);returnredisCacheM}配置shiroredisManager使用的是shiroredis开源插件returnBeanpublicIRedisManagerredisManager(){log。info((2)创建RedisManager,连接Redis。。);IRedisMredis单机支持,在集群为空,或者集群无机器时候使用if(lettuceConnectionFactory。getClusterConfiguration()nulllettuceConnectionFactory。getClusterConfiguration()。getClusterNodes()。isEmpty()){RedisManagerredisManagernewRedisManager();redisManager。setHost(lettuceConnectionFactory。getHostName());redisManager。setPort(lettuceConnectionFactory。getPort());redisManager。setDatabase(lettuceConnectionFactory。getDatabase());redisManager。setTimeout(0);if(!StringUtils。isEmpty(lettuceConnectionFactory。getPassword())){redisManager。setPassword(lettuceConnectionFactory。getPassword());}managerredisM}else{redis集群支持,优先使用集群配置RedisClusterManagerredisManagernewRedisClusterManager();SetHostAndPortportSetnewHashSet();lettuceConnectionFactory。getClusterConfiguration()。getClusterNodes()。forEach(nodeportSet。add(newHostAndPort(node。getHost(),node。getPort())));if(oConvertUtils。isNotEmpty(lettuceConnectionFactory。getPassword())){JedisClusterjedisClusternewJedisCluster(portSet,2000,2000,5,lettuceConnectionFactory。getPassword(),newGenericObjectPoolConfig());redisManager。setPassword(lettuceConnectionFactory。getPassword());redisManager。setJedisCluster(jedisCluster);}else{JedisClusterjedisClusternewJedisCluster(portSet);redisManager。setJedisCluster(jedisCluster);}managerredisM}}}
  认证授权(ShiroRealm)ComponentSlf4jpublicclassShiroRealmextendsAuthorizingRealm{LazyResourceprivateCommonAPIcommonALazyResourceprivateRedisUtilredisU必须重写此方法,不然Shiro会报错Overridepublicbooleansupports(AuthenticationTokentoken){returntokeninstanceofJwtT}权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)触发检测用户权限时才会调用此方法,例如checkRole,checkPermissionparamprincipals身份信息returnAuthorizationInfo权限信息OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){log。debug(Shiro权限认证开始〔roles、permissions〕);Sif(principals!null){LoginUsersysUser(LoginUser)principals。getPrimaryPrincipal();usernamesysUser。getUsername();}SimpleAuthorizationInfoinfonewSimpleAuthorizationInfo();设置用户拥有的角色集合,比如admin,testSetStringroleSetcommonApi。queryUserRoles(username);System。out。println(roleSet。toString());info。setRoles(roleSet);设置用户拥有的权限集合,比如sys:role:add,sys:user:addSetStringpermissionSetcommonApi。queryUserAuths(username);info。addStringPermissions(permissionSet);System。out。println(permissionSet);log。info(Shiro权限认证成功);}用户信息认证是在用户进行登录的时候进行验证(不存redis)也就是说验证用户输入的账号和密码是否正确,错误抛出异常paramauth用户登录的账号密码信息return返回封装了用户信息的AuthenticationInfo实例throwsAuthenticationExceptionOverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokenauth)throwsAuthenticationException{log。debug(Shiro身份认证开始doGetAuthenticationInfo);Stringtoken(String)auth。getCredentials();if(tokennull){HttpServletRequestreqSpringContextUtils。getHttpServletRequest();log。info(身份认证失败IP地址:oConvertUtils。getIpAddrByRequest(req),URL:req。getRequestURI());thrownewAuthenticationException(token为空!);}校验token有效性LoginUserloginUtry{loginUserthis。checkUserTokenIsEffect(token);}catch(AuthenticationExceptione){JwtUtil。responseError(SpringContextUtils。getHttpServletResponse(),401,e。getMessage());e。printStackTrace();}returnnewSimpleAuthenticationInfo(loginUser,token,getName());}校验token的有效性paramtokenpublicLoginUsercheckUserTokenIsEffect(Stringtoken)throwsAuthenticationException{解密获得username,用于和数据库进行对比StringusernameJwtUtil。getUsername(token);if(usernamenull){thrownewAuthenticationException(token非法无效!);}查询用户信息log。debug(校验token是否有效checkUserTokenIsEffecttoken);LoginUserloginUserTokenUtils。getLoginUser(username,commonApi,redisUtil);LoginUserloginUsercommonApi。getUserByName(username);if(loginUsernull){thrownewAuthenticationException(用户不存在!);}判断用户状态if(loginUser。getStatus()!1){thrownewAuthenticationException(账号已被锁定,请联系管理员!);}校验token是否超时失效或者账号密码是否错误if(!jwtTokenRefresh(token,username,loginUser。getPassword())){thrownewAuthenticationException(CommonConstant。TOKENISINVALIDMSG);}returnloginU}JWTToken刷新生命周期(实现:用户在线操作不掉线功能)1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样),缓存有效期设置为Jwt有效时间的2倍2、当该用户再次请求时,通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证3、当该用户这次请求jwt生成的token值已经超时,但该token对应cache中的k还是存在,则表示该用户一直在操作只是JWT的token失效了,程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值,该缓存生命周期重新计算4、当该用户这次请求jwt在生成的token值已经超时,并在cache中不存在对应的k,则表示该用户账户空闲超时,返回用户信息已失效,请重新登录。注意:前端请求Header中设置Authorization保持不变,校验有效性以缓存中的token为准。用户过期时间Jwt有效时间2。paramuserNameparampassWordreturnpublicbooleanjwtTokenRefresh(Stringtoken,StringuserName,StringpassWord){StringcacheTokenString。valueOf(redisUtil。get(CommonConstant。PREFIXUSERTOKENtoken));if(oConvertUtils。isNotEmpty(cacheToken)){校验token有效性if(!JwtUtil。verify(cacheToken,userName,passWord)){StringnewAuthorizationJwtUtil。sign(userName,passWord);设置超时时间redisUtil。set(CommonConstant。PREFIXUSERTOKENtoken,newAuthorization);redisUtil。expire(CommonConstant。PREFIXUSERTOKENtoken,JwtUtil。EXPIRETIME21000);log。debug(用户在线操作,更新token保证不掉线jwtTokenRefreshtoken);}}redis中不存在此TOEKN,说明token非法返回}清除当前用户的权限认证缓存paramprincipals权限信息OverridepublicvoidclearCache(PrincipalCollectionprincipals){super。clearCache(principals);}}
  授权token(JwtToken)publicclassJwtTokenimplementsAuthenticationToken{privatestaticfinallongserialVersionUID1L;privateSpublicJwtToken(Stringtoken){this。}OverridepublicObjectgetPrincipal(){}OverridepublicObjectgetCredentials(){}}
  鉴权登录拦截器(JwtFilter)Description:鉴权登录拦截器Slf4jpublicclassJwtFilterextendsBasicHttpAuthenticationFilter{默认开启跨域设置privatebooleanallowOpublicJwtFilter(){}执行登录认证paramrequestparamresponseparammappedValuereturnOverrideprotectedbooleanisAccessAllowed(ServletRequestrequest,ServletResponseresponse,ObjectmappedValue){try{executeLogin(request,response);}catch(Exceptione){JwtUtil。responseError(response,401,CommonConstant。TOKENISINVALIDMSG);}}OverrideprotectedbooleanexecuteLogin(ServletRequestrequest,ServletResponseresponse)throwsException{HttpServletRequesthttpServletRequest(HttpServletRequest)StringtokenhttpServletRequest。getHeader(CommonConstant。XACCESSTOKEN);JwtTokenjwtTokennewJwtToken(token);提交给realm进行登入,如果错误他会抛出异常并被捕获getSubject(request,response)。login(jwtToken);如果没有抛出异常则代表登入成功,返回}对跨域提供支持OverrideprotectedbooleanpreHandle(ServletRequestrequest,ServletResponseresponse)throwsException{HttpServletRequesthttpServletRequest(HttpServletRequest)HttpServletResponsehttpServletResponse(HttpServletResponse)if(allowOrigin){httpServletResponse。setHeader(HttpHeaders。ACCESSCONTROLALLOWORIGIN,httpServletRequest。getHeader(HttpHeaders。ORIGIN));允许客户端请求方法httpServletResponse。setHeader(HttpHeaders。ACCESSCONTROLALLOWMETHODS,GET,POST,OPTIONS,PUT,DELETE);允许客户端提交的HeaderStringrequestHeadershttpServletRequest。getHeader(HttpHeaders。ACCESSCONTROLREQUESTHEADERS);if(StringUtils。isNotEmpty(requestHeaders)){httpServletResponse。setHeader(HttpHeaders。ACCESSCONTROLALLOWHEADERS,requestHeaders);}允许客户端携带凭证信息(是否允许发送Cookie)httpServletResponse。setHeader(HttpHeaders。ACCESSCONTROLALLOWCREDENTIALS,true);}跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态if(RequestMethod。OPTIONS。name()。equalsIgnoreCase(httpServletRequest。getMethod())){httpServletResponse。setStatus(HttpStatus。OK。value());}returnsuper。preHandle(request,response);}}
  Controller入口使用RequestMapping(valueadd,methodRequestMethod。POST)RequiresPermissions(user:add)RequiresRoles(admin)publicResultadd(RequestBodyJSONObjectjsonObject)
投诉 评论 转载

某宝的手机大功率原装快充头能不能买?我们帮大家试了试水前言说一下笔者吧,在来到充电头网前认为18W快充已经非常不错了,来了之后才知道18W仅仅是入门级,此前也因为手机原装充电器太贵(刚去苹果官网查了一下,5W充电器还卖145……加密市场再度掉头向下以太坊合并稳步推进比特币8月市场表现急转直下,不同于7月的强势演出,8月比特币价格跌幅达到14,自23300跌至20000整数关口附近。虽然在8月中上旬比特币市场表现良好,但是宏观经济环境的持续……赌王家再添喜事,三房长女接受男友求婚,摆脱渣男后再度选择下嫁赌王又一女儿选择下嫁!曾和渣男纠缠多年,未婚夫家境实力悬殊引言。豪门近期赌王家的消息可谓是接连不断,除了澳娱董事成员大换血之外,一直低调的三房家族也传来了好消息,那……岁以下婴幼儿如何做好防护?岁以下婴幼儿如何做好防护?目前3岁以下儿童尚无新冠病毒疫苗,针对这一免疫空白,如何做好防护?中国疾控中心提出这些建议,请查收。一、家长、监护人或看护人在照看低龄儿童……如何区别高价低配和高配低价手机?内行人提醒,看这3点长期以来,我们都对高价低配的手机感到抵触,因为这些手机货不对价,总感觉像花了冤枉钱,那么外行人应该如何区分高价低配和高配低价的手机呢?内行人提醒其实并不难,关注这4点就行了。……声带和听力受损却坚持原声表演,李雪健打了多少演员的脸在前段时间热播的电视剧《北辙南辕》中,黄渤饰演的数字演员给大家留下了非常深刻的印象,剧中的黄渤在拍戏的时候不念台词,只说数字,然后再通过后期配音进行修正。黄渤饰演的角色在……手机屏幕上所谓的E4E5和E6到底是什么意思?究竟有何区别?相信现在还有很多小伙伴可能会好奇,这手机屏幕的E4、E5、E6到底是什么意思?今天我们就来简单聊一聊!目前在手机屏幕领域公认的显示效果最好的OLED屏幕来自三星,而我们作……仅差62分!湖人末节发威胜步行者迎连胜,持续记录老詹生涯数据北京时间2023年2月3日在今日结束的NBA常规赛中,湖人客场以112111战胜步行者队迎连胜。老詹本场比赛数据:上场33分钟26分7板7助0断0冒三分5投2中3失误19……早春要多吃这种杀菌菜,隔三差五吃,全家受益,体质好不感冒早春要多吃这种杀菌菜,隔三差五吃,全家受益,体质好不感冒早春,季节交替,气温变化多端,细菌和病毒都比较活跃。而大蒜富含大蒜素,含量就高达2左右,它可以抑制细菌生长,效果比……请您欣赏杜鹃花开春灿漫杜鹃花开春灿漫时下正是春暖花开的季节,近日,和平乡清江村青马屯的杜鹃花相继开放。一枝枝、一簇簇,如火焰热烈,像红绸舞动,似彩霞燃烧染红了春色,吸引了不少村民前往观赏、拍照……Shiro安全框架最佳实践背景我们在做系统开发的时候,都少不了权限这块,认证授权对于系统安全、隐私合规尤其重要,最常见权限模型是RBAC(用户角色权限)。我们将整体授权类型划分为三级,依据不……八贤王归来!马卡T汤普森想和詹姆斯重聚,同时方便接近科勒北京时间3月22日,多家媒体的消息,已经在某电视台当解说的前NBA球星特里斯坦汤普森目前正在为季后赛努力的洛杉矶湖人队试训。这样,他有可能和好兄弟勒布朗詹姆斯重聚。另外,……
又是闫军!广东男篮申诉成功,CBA官方取消杜锋技术犯规丰田的外壳比亚迪的三电,不到17万!有戏吗?5年期定期存款利率4。125,要求5万起存,值不值得存?你怎2022年6月份汽车销量将大幅回升张益唐谈朗道西格尔零点猜想针没捞到,但摸清了海底的地貌懦弱不前,会让你死得更惨,豁出去说不定你就脱颖而出拼夕夕45元拼的无线充电器(PINSHENG)使用一段时间,早樱谢幕,中樱来袭!本周染井吉野樱进入最佳观赏期,不妨到申城二维平面,三维立体,我国发射四维0102号卫星,能看到什么?消失55天后,薇娅又割了1000万中国斯诺克球员梁文博再遭禁赛英媒马云转居日本深居简出,蜕变为收藏家爱画水彩
给娃冲米粉7个误区,新手宝妈容易犯!附不同时期宝宝辅食添加表我与黄山日报征文52颜良昌一石激起心中浪真正的乳胶枕会掉渣吗八年级班主任工作计划土地征收程序中最重要的是什么?如何提高中老年女性的心理免疫力反贪局办案程序抓人可以么?占星基础知识怎样查看自己的星盘当年春晚千手观音聋哑人领舞,被富豪苦追8年,如今成了这样读银顶针的夏天有感作文库图佐夫坚壁清野退法军支援边疆辛苦您了

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找菏泽德阳山西湖州宝鸡上海茂名内江三亚信阳长春北海西安安徽黄石烟台沧州湛江肇庆鹤壁六安韶关成都钦州