找回密码
 加入怎通
查看: 123|回复: 0

SpringBoot集成Shiro 实现动态加载权限(springboot集成neo4j)

[复制链接]
我来看看 发表于 2023-03-06 15:25:44 | 显示全部楼层 |阅读模式
7 S6 ~' J, D. e! [* j: F# C' }

一、前言本文小编将基于 SpringBoot 集成 Shiro 实现动态uri权限,由前端vue在页面配置uri,Java后端动态刷新权限,不用重启项目,以及在页面分配给用户 角色 、 按钮 、uri 权限后,后端动态分配权限,用户无需在页面重新登录才能获取最新权限,一切权限动态加载,灵活配置

: u$ o2 C0 H- U' u5 X5 L; D1 K

基本环境spring-boot 2.1.7mybatis-plus 2.1.0mysql 5.7.24redis 5.0.5温馨小提示:案例demo源码附文章末尾,有需要的小伙伴们可参考哦 ~二、SpringBoot集成Shiro

# ?$ {* L$ c) h, q% F+ Y. f

1、引入相关maven依赖 " ~6 h" b, z2 U/ {0 j 1.4.0 1 Q1 a, j' P; p. I7 o( x 3.1.0 . R* t& L) {# M. A) b. A! A" p7 I- x7 j8 `0 p ; Q3 @* U4 s; D 7 G, ~- l! D6 E& D( A4 ~8 R" d8 L w org.springframework.boot & U& }0 i, q/ m+ a4 Z" c spring-boot-starter-aop C# c/ a" z3 H& q" z# g! `+ E0 G" S. M 4 k5 D- f7 B2 f/ }! ^7 j- _" W- t* ^7 O; a. s, k7 K+ Z# F ! t: P5 a, t. g9 U& r org.springframework.boot 9 G7 I4 g' }7 q; ^2 d; {9 J spring-boot-starter-data-redis-reactive" y( @1 H* [; g; y & ~# o$ I! H8 D6 v" O ) a: k7 g) b2 C4 T) B6 B6 i . ~9 } ^% n: i+ z1 q$ k: `8 U: e org.apache.shiro5 ~5 s1 l9 \0 {8 l2 _ shiro-spring/ w) q0 J% {0 l& ~1 g+ y ${shiro-spring.version}. ]3 I# m7 f9 C4 e. j 2 g5 s5 i6 v. f) q: n$ [ T% |+ Y* S5 R7 I* d7 g; C) k ; _: q" g, y2 g2 j1 t' U& J org.crazycake! u. U1 e4 T5 C8 b+ M1 p! {9 F shiro-redis$ M5 Y, w' A3 J3 u; W3 f( W ${shiro-redis.version} ) A0 H J# h: W: d8 x4 ` 8 E k w& D5 _3 T' D" e 0 a& m$ \* q& F0 k$ {+ _: s0 ]

$ Q6 x: K8 U3 C4 L1 x( O

2、自定义RealmdoGetAuthenticationInfo:身份认证 (主要是在登录时的逻辑处理)doGetAuthorizationInfo:登陆认证成功后的处理 ex: 赋予角色和权限【 注:用户进行权限验证时 Shiro会去缓存中找,如果查不到数据,会执行doGetAuthorizationInfo这个方法去查权限,并放入缓存中 】 -> 因此我们在前端页面分配用户权限时 执行清除shiro缓存的方法即可实现动态分配用户权限

8 E+ J5 ?0 G4 f! J6 u8 h$ D: X* a8 g

@Slf4j X% K0 a9 n5 C( h8 @" R& ? public class ShiroRealm extends AuthorizingRealm {9 a9 t5 S7 z Q4 H @Autowired6 Y3 U" ], X! H; t0 q private UserMapper userMapper;$ ]1 h4 u6 `7 z2 @9 P) Q @Autowired 3 l' l, K7 V" k! g; V4 g5 _ private MenuMapper menuMapper; . c. ?3 F% E# h3 {. A7 ]# J @Autowired 0 u) J1 D/ `2 q" j; \8 x+ [ private RoleMapper roleMapper;0 L: M/ I9 `5 d& X: R; [ @Override9 T- v% r2 o, F& q4 g public String getName() { 4 A4 O& E5 R7 c, m return "shiroRealm"; - u; m3 w) T. {# l- Q) k) i" s" w }' y7 W( h3 Z/ j /** 9 z" y" V" a7 |1 p * 赋予角色和权限:用户进行权限验证时 Shiro会去缓存中找,如果查不到数据,会执行这个方法去查权限,并放入缓存中 ) S4 r/ g" E4 ] */ . o( f4 @! J! o/ A2 e/ ` @Override; R- B5 y8 j5 Q% Y: e7 M/ _% B protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { ( Q& l& G8 ~) O$ L SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); - A2 i0 [, }4 B7 |8 m* `8 e3 p- \ // 获取用户 5 W/ O$ l4 I3 j7 K D User user = (User) principalCollection.getPrimaryPrincipal(); 6 w- q5 B1 e+ T0 g3 L/ u Integer userId =user.getId();$ T6 K3 V. ^) A // 这里可以进行授权和处理7 o) V& g: l# x4 ]& [% m Set rolesSet = new HashSet<>();& F: }; S' j/ _$ A' |0 q# } Set permsSet = new HashSet<>(); & _) Q9 _8 a( h; c2 A // 获取当前用户对应的权限(这里根据业务自行查询) / N9 O% V% S6 t+ ], f) N0 Y. w, w List roleList = roleMapper.selectRoleByUserId( userId );" Y* F0 B5 Q J2 j0 V0 G: V for (Role role:roleList) { ! {$ s! b5 d3 I+ h M rolesSet.add( role.getCode() ); 4 c7 O3 I" k$ X, X8 r1 F1 ]! d List menuList = menuMapper.selectMenuByRoleId( role.getId() );9 b+ ]5 D8 D4 T# y0 }# C5 m for (Menu menu :menuList) { 0 K/ K& e, _+ p0 @ q7 b b$ f0 p( ~ permsSet.add( menu.getResources() );' o! ]3 l# Z9 q( F6 ^9 c }" `" M5 w' a0 }/ |/ `/ B }+ k/ i6 l3 E0 i1 O ~ //将查到的权限和角色分别传入authorizationInfo中 1 @0 j& M+ m" j; { authorizationInfo.setStringPermissions(permsSet);: o1 }8 ^+ w5 ^+ P authorizationInfo.setRoles(rolesSet);, t p" [' Q* q/ p2 @. ]' B log.info("--------------- 赋予角色和权限成功! ---------------");8 b( |, i( W l0 B# |5 x9 N return authorizationInfo; . x* X. Y% e5 A5 U. _/ M: r } 3 p+ A) r0 b& `& P /**' C% v! r0 j6 _7 |1 M * 身份认证 - 之后走上面的 授权4 |, n0 _1 F j& _2 Y7 Z& [0 k/ J$ A */6 m- C( }, E2 F. I3 }# c @Override. O" K8 o: B6 @" G0 R! ~- ^ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {' y- ~. h' T: h& E UsernamePasswordToken tokenInfo = (UsernamePasswordToken)authenticationToken; 8 G7 c4 q/ l$ e& {8 C4 {; | // 获取用户输入的账号- K* i2 B* {: A( @ String username = tokenInfo.getUsername();; G, a" P: p7 ]/ |8 L // 获取用户输入的密码 : ]& E3 q0 B3 }9 ?. } String password = String.valueOf( tokenInfo.getPassword() ); 9 \" g* g5 G# ]# Y6 d( _% w // 通过username从数据库中查找 User对象,如果找到进行验证9 x3 t& B6 ^2 O+ r // 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 , H) S% O& ~4 y- A% }/ i User user = userMapper.selectUserByUsername(username); 9 I4 e& p m- q) e* {) k: R // 判断账号是否存在 3 H. W# h0 Z) q6 y$ {2 h: h if (user == null) { ' I; m1 ?! v, p! F //返回null -> shiro就会知道这是用户不存在的异常 P: Q, p" v6 J% a; h& _ return null;+ f' v a% A8 M7 s } , R6 ?0 ^8 \( I4 P+ `8 }; g, M // 验证密码 【注:这里不采用shiro自身密码验证 , 采用的话会导致用户登录密码错误时,已登录的账号也会自动下线! 如果采用,移除下面的清除缓存到登录处 处理】) z& [6 _# R( p! o: l7 c if ( !password.equals( user.getPwd() ) ){ ' Y, F9 i# ^' u throw new IncorrectCredentialsException("用户名或者密码错误"); 3 C- x5 a3 b! G( x }' @- x) d. r* H+ Q/ t // 判断账号是否被冻结( J4 {+ ?( P$ ~8 y) A/ D8 w8 r if (user.getFlag()==null|| "0".equals(user.getFlag())){; |# n. B, x2 v4 R1 x) m throw new LockedAccountException(); % Y: P) w5 z' g. t5 L } j5 H9 b3 N9 M- U /**" y0 {1 \5 L! t' P+ H! e5 e1 M% o * 进行验证 -> 注:shiro会自动验证密码 / L& ?- }$ \3 U7 H0 p* i0 ]# p0 {, A * 参数1:principal -> 放对象就可以在页面任意地方拿到该对象里面的值 - Y3 _* |+ O% T * 参数2:hashedCredentials -> 密码 % l: `$ F: _8 i7 a3 p, F; s6 P* a * 参数3:credentialsSalt -> 设置盐值 + b+ t0 ]# Q' r) w * 参数4:realmName -> 自定义的Realm- x! m; v e( ]$ F# Z# f( y4 s% c */ $ n. l8 m- O( h2 n SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName()); 4 i3 R$ X9 P& T. I5 b5 P // 验证成功开始踢人(清除缓存和Session) $ p6 |, U$ ]0 ~+ a" I4 x ShiroUtils.deleteCache(username,true); # k$ J& p. T- C) y9 v9 g8 Y; T // 认证成功后更新token . e& p: u' }$ a/ Z; w String token = ShiroUtils.getSession().getId().toString(); ) h1 u. S$ V2 B+ C" c' ~; l& { user.setToken( token );9 e4 b9 |2 {+ A1 c7 }2 G userMapper.updateById(user);! t: Y$ P1 `0 V- W return authenticationInfo;0 e6 D" S5 g, X7 j/ y6 R } # Q) O6 p* |* f) n }; c$ ?8 v9 f/ V

$ S$ ]0 {8 L2 `3 w. K! W# @

3、Shiro配置类@Configuration7 x5 T! l/ ^# X- u5 x+ R, E! A" ^2 H/ Y public class ShiroConfig {9 }4 g+ j' j# P private final String CACHE_KEY = "shiro:cache:"; 8 Z) V" m* A$ X/ J% B$ b J$ A5 ?2 C% f private final String SESSION_KEY = "shiro:session:"; " F! S+ R2 t* j& P /**& V U' m& @" X9 D1 q) Y * 默认过期时间30分钟,即在30分钟内不进行操作则清空缓存信息,页面即会提醒重新登录 # W9 r/ u9 O8 p, R7 L3 g */ 6 W; A: i" p! {% W private final int EXPIRE = 1800;* U* o! E" S4 s% H9 r( Z /** $ _: P: f d! c* p' N * Redis配置 ! N: E! V$ ?" z8 {* ~. r */ 6 k6 I, ?! C+ w" y, `5 J @Value("${spring.redis.host}")% x4 q$ B7 J. G# f5 J# M private String host; ( d3 M8 Y) ]2 [- a' K& G/ I# U6 H @Value("${spring.redis.port}") * c# `0 R3 w [, Z private int port;7 P: _. E$ q& \$ e% a5 Z @Value("${spring.redis.timeout}") ; q9 Q+ z: {' S; R& L! C private int timeout;3 O% e4 i) k1 v6 c' [$ [/ r$ z, a8 w // @Value("${spring.redis.password}") * }8 C: I) w( B // private String password; f6 K' t, c$ I, f( d/ w /**: f9 u5 T a0 r {5 O * 开启Shiro-aop注解支持:使用代理方式所以需要开启代码支持 ! f3 A( N0 t( I, j5 L; p */# r$ V V$ t2 O9 T @Bean/ e( i9 M# r9 J" a& S# f* B5 { public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {* @# e/ M; r" c AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();" R: t$ _* A5 T& {2 I( }+ y1 N authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); 8 A# \8 Q* G \7 X return authorizationAttributeSourceAdvisor; - }- C, h2 R9 B7 W } ' y; @: E- v7 u: C* t1 {( O /** 5 ?5 [/ T; P; s6 M s" h/ M& ]; I * Shiro基础配置 O6 h Z! \6 T, B! ? */ % F$ Q* y# `" L* T( r$ I @Bean: r$ M* ^) t3 f" q- K+ ~( `4 `8 c+ l public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager, ShiroServiceImpl shiroConfig){) f( M, X- n% b- {' b# y3 J/ o ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 4 R4 H: i* S8 d9 J, j shiroFilterFactoryBean.setSecurityManager(securityManager); 3 ?' U+ X( O+ t" C // 自定义过滤器' P4 Y& J/ K/ \! H' i) {# H- V5 O Map filtersMap = new LinkedHashMap<>();- F. K7 Z6 ]# J/ h0 v# H( C // 定义过滤器名称 【注:map里面key值对于的value要为authc才能使用自定义的过滤器】 & y( L3 j* V) u$ v5 ` filtersMap.put( "zqPerms", new MyPermissionsAuthorizationFilter() );- J' B9 A4 p* o8 t% U* r' R. p( _ filtersMap.put( "zqRoles", new MyRolesAuthorizationFilter() ); ( f% u8 m' V. i2 ?* [ filtersMap.put( "token", new TokenCheckFilter() ); 7 o7 }7 v2 r; n* \3 F( d$ L shiroFilterFactoryBean.setFilters(filtersMap);( i/ h: R) Y$ X& | F- S7 ? // 登录的路径: 如果你没有登录则会跳到这个页面中 - 如果没有设置值则会默认跳转到工程根目录下的"/login.jsp"页面 或 "/login" 映射 0 d$ R& e! ^2 @- M& ? shiroFilterFactoryBean.setLoginUrl("/api/auth/unLogin");: u8 s l; ]9 F6 x. `) X" m- ~ // 登录成功后跳转的主页面 (这里没用,前端vue控制了跳转) 1 R* z' G. S$ R2 [7 z( T1 t // shiroFilterFactoryBean.setSuccessUrl("/index");1 ^% L, [2 ~& u7 M# E# E6 b. ]8 }/ y // 设置没有权限时跳转的url & {+ h# V$ `! z/ B shiroFilterFactoryBean.setUnauthorizedUrl("/api/auth/unauth"); ( N: _2 G$ A, y$ p) E shiroFilterFactoryBean.setFilterChainDefinitionMap( shiroConfig.loadFilterChainDefinitionMap() ); ' U. ^$ q7 B' a9 W- A return shiroFilterFactoryBean;0 j s5 {+ w( H! t: Z } - X" U8 Y" b7 {9 X2 ~$ C& P /**2 L: U6 W; q5 o" Z7 Z * 安全管理器 . o8 Y. S# L; a/ z4 ? */3 e1 Z0 c/ k7 t" ]" d @Bean , l# i* L/ u) \* o/ |5 \5 e public SecurityManager securityManager() { 8 P4 A$ D/ {1 K& w0 O9 o DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); & E0 c& w' a5 R8 z( ^. x // 自定义session管理 4 e$ f& w! [" o* q, ~# s6 B/ K securityManager.setSessionManager(sessionManager());* }& n/ J) g ], K' N& o // 自定义Cache实现缓存管理3 }# Q& r% R6 L5 @. |5 S securityManager.setCacheManager(cacheManager()); # S3 e5 C2 G k8 S // 自定义Realm验证9 ^4 J$ ?/ b1 k" x- t securityManager.setRealm(shiroRealm()); 2 Z( Y! \1 }6 I1 M8 j return securityManager;5 U' o' p" j6 a' }4 W# h }9 Z" [5 a' q4 T9 } /** 6 U* y* b. {7 A5 k! _$ w1 [ * 身份验证器 0 `, I: z* K7 r- H */6 P6 ]" g$ A, j. B" m- { @Bean 2 ~7 U( v I, O public ShiroRealm shiroRealm() { , u. J7 m5 @' ^! i' ]$ `5 N ShiroRealm shiroRealm = new ShiroRealm(); 9 }7 r/ V7 g# y. P- Z a shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());* x" `! b. Y8 G" b C. ~2 p% V+ I& N return shiroRealm; ! v2 Z/ V& N# H: `3 k }; r3 ]2 x; H. [- ^+ f' ~% A /**: G/ L U3 |) q) h * 自定义Realm的加密规则 -> 凭证匹配器:将密码校验交给Shiro的SimpleAuthenticationInfo进行处理,在这里做匹配配置0 y, R! ~ n* V* N/ K" f9 F$ O: Y */0 `$ k0 b) v7 O/ U @Bean$ q+ d7 S3 u) R5 q9 J1 _5 ^/ z9 d public HashedCredentialsMatcher hashedCredentialsMatcher() { # M9 M9 g8 A k( P$ C HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();: \8 r- X9 f8 ?* }' z- y& g // 散列算法:这里使用SHA256算法; + R' a t `9 o! g. [+ b, W shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME); 4 h0 q7 ]) O# T& C // 散列的次数,比如散列两次,相当于 md5(md5(""));+ T5 l$ ]9 a$ w m shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);# Q$ ~1 A7 v) E! U return shaCredentialsMatcher;' F+ G Q4 e3 n6 a) `/ Q } 6 V! B+ Y5 |# g# \) @$ m3 F0 T /** 1 n7 W! x G$ D# y9 d" N0 G * 配置Redis管理器:使用的是shiro-redis开源插件 % X3 N' F: r- a! q */8 x# S" T4 W- N& ~/ S( l @Bean1 D9 y. {+ O. ]; g public RedisManager redisManager() { ( F; g5 L1 o7 A4 ^" L( } RedisManager redisManager = new RedisManager();; H. t2 S" c" W redisManager.setHost(host);( x& H* f9 N4 J$ X/ K! M redisManager.setPort(port);7 e8 A% ]6 O y( R: b3 n redisManager.setTimeout(timeout); , J6 C% B; i c- r$ X9 o+ ?6 G // redisManager.setPassword(password); 2 `7 ?; o. h* y% k. G# S return redisManager;( [& `& y) F; i7 C3 h } # ^. t' I" M9 `! }( a /** ' r6 u. t6 Z/ l& `* s4 q) i * 配置Cache管理器:用于往Redis存储权限和角色标识 (使用的是shiro-redis开源插件)/ t/ Y1 R/ u" ^* j a/ W */ 5 E. ^' f9 _ P$ T; V @Bean , Q2 O1 x' a* [! S* D# x public RedisCacheManager cacheManager() {+ q! }5 s2 Q, h2 p RedisCacheManager redisCacheManager = new RedisCacheManager();- h; \6 h v, J2 G redisCacheManager.setRedisManager(redisManager());; [. P# E3 ?% s redisCacheManager.setKeyPrefix(CACHE_KEY);+ h5 ]2 T3 a1 `5 q // 配置缓存的话要求放在session里面的实体类必须有个id标识 注:这里id为用户表中的主键,否-> 报:User must has getter for field: xx8 |9 [+ e- X: j3 i2 U3 |6 x$ B redisCacheManager.setPrincipalIdFieldName("id"); - J1 _' D3 L. k" a; m return redisCacheManager; & G% H* k. z( a% Y3 k% Y, L } 2 I+ p1 h2 @; j$ z, x1 ]% [; W /**4 }: m: c# x$ f6 y * SessionID生成器# `: X- u) [: a$ ?9 X' V */' H+ |& X- M5 O' M, E @Bean , _ Y( L4 p, A: Q5 K* D public ShiroSessionIdGenerator sessionIdGenerator(){/ m' C6 l. n& ^! M/ i return new ShiroSessionIdGenerator(); ! a, g s5 p1 ]6 z- q }8 M( F* C" M) w e /** # I5 `( K5 s) I p8 ]" ` * 配置RedisSessionDAO (使用的是shiro-redis开源插件)) a( g2 I( y2 W3 ]& ~; I- R- D */ 6 I* G S% y7 C/ B @Bean+ O: |! T a! J2 M. R public RedisSessionDAO redisSessionDAO() { 6 ]* L* r2 G6 D! `: ]7 Q. v" M/ A RedisSessionDAO redisSessionDAO = new RedisSessionDAO();6 O4 N) I3 o+ l9 k$ X+ \6 s6 M( t$ z redisSessionDAO.setRedisManager(redisManager()); ' ]! A+ R9 u) r redisSessionDAO.setSessionIdGenerator(sessionIdGenerator()); ' t% m4 u6 q, n redisSessionDAO.setKeyPrefix(SESSION_KEY); E4 U6 }" J0 u0 P6 H redisSessionDAO.setExpire(EXPIRE);% G) G2 Y# ^& y8 W return redisSessionDAO;/ I# b* l6 i$ t1 h: D }7 ]$ H0 B8 l, V( c* @) O /**# W* J7 \4 Y/ m4 n7 J * 配置Session管理器 8 I' a- }( \% D */ - S* W: F. q% [' w6 s @Bean % k( y7 m0 ~: O9 V2 k+ @ public SessionManager sessionManager() { 7 m6 P3 _, V: a) j1 i ShiroSessionManager shiroSessionManager = new ShiroSessionManager(); ! O3 ?! k& z1 a- b0 z% Q9 t5 _8 u shiroSessionManager.setSessionDAO(redisSessionDAO());0 X$ J8 [: _/ a- N$ {- {0 ^' `; h return shiroSessionManager; 9 ]% G2 H- [% _8 B Q& O }0 z2 r0 [( O0 T0 l( I/ A1 H+ b } `5 d1 ^: T B% d6 ^4 c2 h

_" Q3 e: M* A) S

三、shiro动态加载权限处理方**oadFilterChainDefinitionMap:初始化权限ex: 在上面Shiro配置类ShiroConfig中的Shiro基础配置shiroFilterFactory方法中我们就需要调用此方法将数据库中配置的所有uri权限全部加载进去,以及放行接口和配置权限过滤器等

3 I# \% |% W6 W/ s) G: Q

【注:过滤器配置顺序不能颠倒,多个过滤器用 , 分割】ex: filterChainDefinitionMap.put("/api/system/user/list", "authc,token,zqPerms[user1]")updatePermission:动态刷新加载数据库中的uri权限 -> 页面在新增uri路径到数据库中,也就是配置新的权限时就可以调用此方法实现动态加载uri权限

# H# y Y: f; e( {% g

updatePermissionByRoleId:shiro动态权限加载 -> 即分配指定用户权限时可调用此方法删除shiro缓存,重新执行doGetAuthorizationInfo方法授权角色和权限

& w7 J& a( r. F+ Q' r- C0 h. U

public interface ShiroService {; N0 m( U3 X. t; G /** , A0 H, n6 Z( U) ` * 初始化权限 -> 拿全部权限) E, J* `, R1 x* h * 0 L# D8 {4 `8 U" U * @param :- F: Y$ s8 s- i" Z. L * @return: java.util.Map / f$ y, k6 z) F6 U9 U */ 1 B7 I2 L7 r0 o* p' Q+ z* w7 [ Map loadFilterChainDefinitionMap();0 h, `, b9 k: N9 `+ M3 C /**+ S. \. F" G, O0 ?. V8 W' Z * 在对uri权限进行增删改操作时,需要调用此方法进行动态刷新加载数据库中的uri权限, X. M+ _5 ^! w8 H, J * * [/ r9 }; x f- M9 i. k/ o * @param shiroFilterFactoryBean 9 u: h1 v8 {3 a% a8 ~2 U! H * @param roleId 6 W) \4 H' o; z% w: {: d8 ^ * @param isRemoveSession: + q( R8 a8 y! i' T$ [( _& d# ] * @return: void 6 V, ], ]3 _* I) N }. T6 w1 \9 | *// c" |; y! F) c4 z1 s7 i void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean, Integer roleId, Boolean isRemoveSession);, e8 J C& `; A /** ! v( v9 O. Z2 T- p4 `( f$ k * shiro动态权限加载 -> 原理:删除shiro缓存,重新执行doGetAuthorizationInfo方法授权角色和权限 & C) k" J* x) A% ?" ? * 6 u0 k. J4 k" A% w1 f3 |- F * @param roleId % C/ C L+ |& b* L! I, L7 A$ L+ p * @param isRemoveSession:0 F8 M$ l: @1 l$ g% S * @return: void. w* j3 a6 L9 Y0 p1 X( A8 ~ */1 V' b5 W O9 f% k6 ]8 h void updatePermissionByRoleId(Integer roleId, Boolean isRemoveSession);/ I. D5 _2 a& o5 u6 R }. u* j6 x3 g. g2 D7 H6 ^) l @Slf4j; D% ]+ p9 J9 m4 ^, _2 Z& F4 y @Service7 v# I# O2 `. L _) c' F public class ShiroServiceImpl implements ShiroService { & V' t1 {) j( \7 [& s$ Z @Autowired: m7 W: A% u5 V7 @$ M+ ?) ~% k* k private MenuMapper menuMapper; 0 T9 Z3 E2 p T6 M @Autowired9 F% O* b( X9 M# x3 H private UserMapper userMapper; 9 R9 h8 ^& f9 N- A1 e @Autowired 3 m: v5 o g- a private RoleMapper roleMapper;9 i: h' k5 z! K* k# ]$ c: [ @Override 8 B2 i) f* i5 m6 H6 H; K8 r public Map loadFilterChainDefinitionMap() { 5 A; I/ M. y& R, i1 O // 权限控制map2 U. e! Q2 |0 ?! W u Map filterChainDefinitionMap = new LinkedHashMap<>(); 2 U" k% S' o) t5 L" K& p+ E // 配置过滤:不会被拦截的链接 -> 放行 start ---------------------------------------------------------- 8 d* J: @5 \' N2 M' A // 放行Swagger2页面,需要放行这些 " S6 x5 V- S* r& {6 K% p$ d" D filterChainDefinitionMap.put("/swagger-ui.html","anon"); : c* O4 B& g! _3 ?0 ` F" ?7 Z' L filterChainDefinitionMap.put("/swagger/**","anon");* t- @0 J7 H4 N" _; T filterChainDefinitionMap.put("/webjars/**", "anon");$ c& d" l: t2 E0 k filterChainDefinitionMap.put("/swagger-resources/**","anon");# {$ ~9 e2 H: R+ r filterChainDefinitionMap.put("/v2/**","anon"); ( S9 a5 I$ _4 t- F8 C& ]/ M4 o filterChainDefinitionMap.put("/static/**", "anon"); 0 k4 K* ?1 W y9 G F- B& ^' B% o // 登陆 1 O( S8 F* r' X! i filterChainDefinitionMap.put("/api/auth/login/**", "anon"); 6 E( N- J0 F* ~+ U% v; K // 三方登录' \4 A9 ^) i2 i9 ]2 N filterChainDefinitionMap.put("/api/auth/loginByQQ", "anon");3 H8 }( u' u, c1 J* D6 g2 R+ F filterChainDefinitionMap.put("/api/auth/afterlogin.do", "anon"); % ~! E0 R U( j/ @$ t, E // 退出 2 K; T0 j2 t1 m* N" L filterChainDefinitionMap.put("/api/auth/logout", "anon"); 6 ~+ k' I. V4 ~( p0 e // 放行未授权接口,重定向使用, M7 p" I) y) L+ W9 ]. q) M filterChainDefinitionMap.put("/api/auth/unauth", "anon");' [4 K5 R) B k& U // token过期接口 - @7 J1 P2 k4 b9 ^ filterChainDefinitionMap.put("/api/auth/tokenExpired", "anon"); % H6 G6 w0 \% j9 R; c# q; k7 u // 被挤下线 % y' R; _5 U! |' L c5 D% N filterChainDefinitionMap.put("/api/auth/downline", "anon");6 j' R. Q6 ^/ {! Q3 n // 放行 end ---------------------------------------------------------- i% E" }) W8 s. @+ L2 M // 从数据库或缓存中查取出来的url与resources对应则不会被拦截 放行 S4 r3 E+ u2 t List permissionList = menuMapper.selectList( null ); 7 d$ Q- v" y5 z4 {3 v: j Y# j if ( !CollectionUtils.isEmpty( permissionList ) ) { & q0 K- s8 b$ }& m& \4 a permissionList.forEach( e -> { 5 K9 [" W) B# |& H2 O if ( StringUtils.isNotBlank( e.getUrl() ) ) { / l1 L( x) }. `3 R/ ^ // 根据url查询相关联的角色名,拼接自定义的角色权限 6 s0 Q: c( E5 V5 E0 |* l" J8 I# L List roleList = roleMapper.selectRoleByMenuId( e.getId() );' V N+ |, {5 V StringJoiner zqRoles = new StringJoiner(",", "zqRoles[", "]");5 ?- `5 M1 [) ]0 Q( F/ C; C* J3 S if ( !CollectionUtils.isEmpty( roleList ) ){# P' @5 g" x" h% h roleList.forEach( f -> { 6 E7 D" T" q( F8 O% D9 v" p4 Z zqRoles.add( f.getCode() );! G* o' d# b- w) m/ i5 ? });8 i Z% m1 C- @" z8 D# d } " F* v9 D8 Q$ l3 z" t. E8 @ // 注意过滤器配置顺序不能颠倒 M+ L# H0 W6 f- E( m // ① 认证登录 9 B' N5 J5 Z0 K: Y7 H // ② 认证自定义的token过滤器 - 判断token是否有效" Y- O6 H, v: R# o5 P, t+ l- `- ~ // ③ 角色权限 zqRoles:自定义的只需要满足其中一个角色即可访问 ; roles[admin,guest] : 默认需要每个参数满足才算通过,相当于hasAllRoles()方法7 q( A% T3 F* w+ I5 r( Y8 g( Q // ④ zqPerms:认证自定义的url过滤器拦截权限 【注:多个过滤器用 , 分割】0 J# K9 X! M# T$ Z: X // filterChainDefinitionMap.put( "/api" + e.getUrl(),"authc,token,roles[admin,guest],zqPerms[" + e.getResources() + "]" );# m! a" V5 F) ?# M filterChainDefinitionMap.put( "/api" + e.getUrl(),"authc,token,"+ zqRoles.toString() +",zqPerms[" + e.getResources() + "]" );1 O9 N* R/ f# y4 [: k( w // filterChainDefinitionMap.put("/api/system/user/listPage", "authc,token,zqPerms[user1]"); // 写死的一种用法$ k; e0 Y$ c& k# Z. Y5 D4 y. G5 `' O' ` } 3 C" v6 e8 F7 g5 ` });: w3 g7 |& g/ }& H }" @, }& n0 R; i // ⑤ 认证登录 【注:map不能存放相同key】2 G$ _$ \( B. L' I. | filterChainDefinitionMap.put("/**", "authc"); % A6 S3 E9 Y, V& W' k( A return filterChainDefinitionMap;4 }$ a3 Q7 M+ s6 W, V# X# p }! {* z9 W6 s( N$ X: S- Q, U- ^& V @Override % t, r7 L, r% B9 k8 n public void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean, Integer roleId, Boolean isRemoveSession) {6 r2 S# a# [+ j* d: q) [0 i synchronized (this) { 3 Z, E( R8 H" @ AbstractShiroFilter shiroFilter;3 c6 Z' U4 _' ^0 l, n/ \. p try {5 `( \" G& O& U0 z* q0 B shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject(); : N, _1 i1 d: M5 Q2 M0 r } catch (Exception e) {# [2 [1 Z2 `. }" k; V2 L ]/ r throw new MyException("get ShiroFilter from shiroFilterFactoryBean error!");: [+ }6 j+ _" }$ d; E# u% N } / C3 A) @$ @. G+ x0 z3 Q PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();* S8 E& P) F1 e% K' e! a9 |5 K- D5 j) Y DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();0 a% _1 r' ?2 B) j& T) n // 清空拦截管理器中的存储 , R+ X& e2 {" T2 R$ z R L manager.getFilterChains().clear();5 _8 U8 b# b* C4 t8 B# X& P$ G // 清空拦截工厂中的存储,如果不清空这里,还会把之前的带进去 & Z2 {; n+ Y( W3 p* _8 J // ps:如果仅仅是更新的话,可以根据这里的 map 遍历数据修改,重新整理好权限再一起添加' Q* q n! F6 f! H4 j" W9 G shiroFilterFactoryBean.getFilterChainDefinitionMap().clear(); 0 o# l% `; b7 b1 f' O // 动态查询数据库中所有权限 6 |; H9 P* S9 s' c9 l shiroFilterFactoryBean.setFilterChainDefinitionMap(loadFilterChainDefinitionMap());( k: V( |4 R0 g! X: T // 重新构建生成拦截 5 B+ P/ l4 E5 J% h+ i; y8 p) E Map chains = shiroFilterFactoryBean.getFilterChainDefinitionMap(); " W( }" Q+ H6 Z/ m- E5 z7 i for (Map.Entry entry : chains.entrySet()) { % [ D) d. k$ [# a# J! y$ i. p; J( ] manager.createChain(entry.getKey(), entry.getValue());( `' q5 l5 f4 p6 n. r5 V. b } - j. N) ^6 b) S( q/ A( Q3 t log.info("--------------- 动态生成url权限成功! ---------------"); ( ~! o$ L& J) L4 Q$ ? // 动态更新该角色相关联的用户shiro权限6 F$ O: G T; C% A7 l# Y if(roleId != null){ * F- b3 e9 _4 O( @; L3 m& z& e0 u updatePermissionByRoleId(roleId,isRemoveSession);8 N2 Z% I4 S1 {- k } 6 {2 C. n8 ?. E+ e } * o% g$ s( U2 B& g+ @/ P! _( \ }- Y, K' m& X5 r/ Y. D @Override ( u. x m$ [0 Q2 E, r public void updatePermissionByRoleId(Integer roleId, Boolean isRemoveSession) { # V3 z- v# X0 I' N, n# z+ u6 j* _$ w // 查询当前角色的用户shiro缓存信息 -> 实现动态权限 & j8 t3 _& r l' o: S0 o- D List userList = userMapper.selectUserByRoleId(roleId); / d6 u% Y3 Z0 B, F( A; x // 删除当前角色关联的用户缓存信息,用户再次访问接口时会重新授权 ; isRemoveSession为true时删除Session -> 即强制用户退出 % V) \" _; o+ ?% t. F; }9 h if ( !CollectionUtils.isEmpty( userList ) ) {: `; N! ~- T1 P$ P for (User user : userList) { ' w# ]6 u5 M9 w6 q! N& b& h5 j9 s ShiroUtils.deleteCache(user.getUsername(), isRemoveSession);, k% ~- ^: y) O; y2 q } % s) Y3 N5 Y6 q3 _2 P }. H- _9 ]% _- o log.info("--------------- 动态修改用户权限成功! ---------------"); " v0 ~& t3 H# J% r9 a, n, g2 z7 @ } ! b% t: I$ b1 y! v4 f' s- b6 `) u } e. M2 C5 D4 O) a1 o) p! v6 \

! O# T4 V0 X( }

四、shiro中自定义角色、权限过滤器1、自定义uri权限过滤器 zqPerms@Slf4j / f3 f8 \% o3 @# v public class MyPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {( y# ^! J! M1 X# ^ @Override 6 Q( U$ Q: s [0 {& n8 m/ B protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {) D1 A, A, m) m# h8 j HttpServletRequest httpRequest = (HttpServletRequest) request; 7 L! l! j2 N2 n# O g. L HttpServletResponse httpResponse = (HttpServletResponse) response; n; K- z% Y! G. \ String requestUrl = httpRequest.getServletPath();! N: w) R, W7 X4 f# m4 ^4 @4 { log.info("请求的url: " + requestUrl); 4 h* C/ o7 e/ { K // 检查是否拥有访问权限% W) {& p4 |. p, X$ V E2 O r Subject subject = this.getSubject(request, response);1 g5 Q) f, X5 m4 W n. H if (subject.getPrincipal() == null) { / {$ E6 d' {% m- T C( ^ this.saveRequestAndRedirectToLogin(request, response);1 R4 Y$ v! X. B, a9 t& r$ z- w } else {8 U( Q$ p6 x E7 j8 K8 ]( y) x$ m ]' l // 转换成http的请求和响应 , N6 o4 W; ]9 Z HttpServletRequest req = (HttpServletRequest) request; * x4 |( F V# K( l9 E HttpServletResponse resp = (HttpServletResponse) response; - `% r+ ~1 E- c# E) ^1 B; m // 获取请求头的值0 @" X& ?: c: i s" h+ `* V! H String header = req.getHeader("X-Requested-With");+ B3 ^, _6 W: Q, Z$ V i5 u // ajax 的请求头里有X-Requested-With: XMLHttpRequest 正常请求没有% ~7 h% f8 b0 X if (header!=null && "XMLHttpRequest".equals(header)){ . T K* X$ _8 V5 Q9 y- `7 I" T8 k resp.setContentType("text/json,charset=UTF-8"); 5 P( ] {$ t( J. E7 e/ ~8 c; S resp.getWriter().print("{\"success\":false,\"msg\":\"没有权限操作!\"}");0 h2 \3 U3 R: C5 `- U- I! K }else { //正常请求* X# k2 z' C+ I/ N6 U& V$ p! ~ String unauthorizedUrl = this.getUnauthorizedUrl(); # R9 g+ F S, c/ Q if (StringUtils.hasText(unauthorizedUrl)) {' W* q" }3 V$ ~. v7 @4 a( ^. l4 n5 |+ M% @ WebUtils.issueRedirect(request, response, unauthorizedUrl);! {! O2 {9 {$ D3 g# _' T } else {$ j2 ~- ~: I3 w3 a8 d" m( s WebUtils.toHttp(response).sendError(401); r9 x& X+ S0 L( }' k }5 K" S8 u& r7 {$ w } ; z8 U- a4 k3 @' j0 [4 s. Q0 Q }- v, [/ J; D6 F1 b" G4 x return false; / A1 w; K' Y6 _3 \. B7 l }1 k* k# ]; y6 q( y [ m% K" ? R: P }3 M6 I3 |; N9 ?) J0 j9 a1 B" m

+ b0 Z/ n: q$ P; a8 f3 N. G

2、自定义角色权限过滤器 zqRolesshiro原生的角色过滤器RolesAuthorizationFilter 默认是必须同时满足roles[admin,guest]才有权限,而自定义的zqRoles 只满足其中一个即可访问

2 i% y+ ] {7 s0 N( ~; e

ex: zqRoles[admin,guest]public class MyRolesAuthorizationFilter extends AuthorizationFilter { B, Y- l( ~) C- c9 j) C a: I @Override & T" C7 f: ~6 n protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) throws Exception { $ b8 m5 ?+ W% Z" f Subject subject = getSubject(req, resp); 6 `3 J: e9 H; k0 Q String[] rolesArray = (String[]) mappedValue;" b# x, P" M" l+ J( i& g // 没有角色限制,有权限访问 8 ? d5 t; \0 r if (rolesArray == null || rolesArray.length == 0) {/ R! Q2 ?5 L( E! J l. W- o return true;: T3 O+ g. s4 l5 m } - Y: f7 a1 _$ d w* e) C for (int i = 0; i < rolesArray.length; i++) { $ y8 ^0 _7 @* h# a/ Q //若当前用户是rolesArray中的任何一个,则有权限访问( p$ l. z) ?" Z" D if (subject.hasRole(rolesArray[i])) { 2 v$ o1 E# j% m; L return true; % Q. E! x! U9 m4 E% P }; I- @9 w1 ~# C" g( q }3 |& l. q' s* a |9 z- \" g return false;" X& x: [- ]- p } H* C4 E' z. n8 l/ }8 R- h }$ ^9 q. @5 u1 S7 a

; U- o6 p% F2 @2 P* q1 w2 r' I8 i

3、自定义token过滤器 token -> 判断token是否过期失效等@Slf4j+ h2 d" r; O! d; x public class TokenCheckFilter extends UserFilter {- w: ~ x! c5 W- g ?& s: C /**- p$ |9 x8 z, M( K7 [6 i* q, H * token过期、失效 3 _" I+ ?4 x4 L5 X+ s */3 @. Z/ p9 X* ?5 D, H% }" o private static final String TOKEN_EXPIRED_URL = "/api/auth/tokenExpired"; P C7 ]# N9 D% ]; W$ T. l /** 8 Z% @, x: \( v" }, _ * 判断是否拥有权限 true:认证成功 false:认证失败 $ O1 R" C6 W' L$ U& p2 Q * mappedValue 访问该url时需要的权限) `6 |* a" F% A. z' B8 M * subject.isPermitted 判断访问的用户是否拥有mappedValue权限 / ~3 Q+ n f, D5 s& d! b */ 7 e# R: e' O. F; {, |1 e @Override) f$ O* ~9 t( W/ D public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {: L9 [# t3 L. d/ ]$ ]6 ^: P. x* c HttpServletRequest httpRequest = (HttpServletRequest) request; 0 ]/ t6 H& h. F0 { HttpServletResponse httpResponse = (HttpServletResponse) response; z4 z% P [+ b: x! R% w% g, D7 g: x // 根据请求头拿到token K6 Y7 B# X) S7 l( r String token = WebUtils.toHttp(request).getHeader(Constants.REQUEST_HEADER); 3 D8 o$ _6 q% `' Q8 S+ W log.info("浏览器token:" + token ); ( ?: D% \2 H- \ User userInfo = ShiroUtils.getUserInfo(); + o3 q5 V! o1 n String userToken = userInfo.getToken();, w, b2 q0 [; D7 u* G // 检查token是否过期1 a' n& |2 N* G/ Z e* Y; z if ( !token.equals(userToken) ){ 9 k& O( a' N6 U O" A8 \& A/ ` return false; . ?; Y4 O, p4 ]0 ? }$ h# o2 c, M7 d0 T" p3 ~/ { return true;' N+ C' j# u; A+ X } ' D, t& E; l4 Q3 E. t: L# G8 U0 f' C /** 9 B6 e4 n$ y, X3 x * 认证失败回调的方法: 如果登录实体为null就保存请求和跳转登录页面,否则就跳转无权限配置页面 ) I4 [9 H! s) ^& } p */ 2 r6 A+ b7 j3 ^$ k* Z8 F' B( M @Override * ]- ~! r# e3 u' P protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { & m' U! J6 o c- d User userInfo = ShiroUtils.getUserInfo();6 P2 I- d+ r% f; p7 B // 重定向错误提示处理 - 前后端分离情况下5 u. T: t: F3 U+ {0 i; r9 B WebUtils.issueRedirect(request, response, TOKEN_EXPIRED_URL); ! m* l! B4 C6 m# X; A3 l return false;! g* P# C1 x4 t7 h3 }4 S& g }2 f# [7 a! g$ H9 E } / `0 ~3 v1 P- U0 J- v4 j$ `4 v

; G7 p! S! R# I( b5 @- S% g

五、项目中会用到的一些工具类、常量等温馨小提示:这里只是部分,详情可参考文章末尾给出的案例demo源码1、Shiro工具类public class ShiroUtils { " A! g8 G5 P8 ] /** 私有构造器 **/, i9 A7 c4 \) z, u& q, g private ShiroUtils(){ }! D1 C3 I3 I: C+ `7 \ private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class); / a$ k' J! S6 U S5 d* r A /** # k/ e7 H' ]; R1 I * 获取当前用户Session j' [/ b4 ~, @- j4 ] * @Return SysUserEntity 用户信息4 _/ b* E- i! {% V$ ^/ D */; Z$ F" T4 N, X public static Session getSession() {* w$ E% C! X* v; E. K" D4 I) s& D/ H return SecurityUtils.getSubject().getSession();0 P7 P* [/ Q3 h$ P } & }0 H) h2 I+ L/ W1 B; |3 E /**+ n* `. l b7 _* L * 用户登出 ) o2 i$ j$ I* [# M& r */. u+ p J3 ]. R- D' |* a! Y public static void logout() { 5 X; S. ? d4 \! Y SecurityUtils.getSubject().logout();/ k# z6 _: h( U! w1 N. g' G } . Q8 i, |& S: ~2 V& ~ /** . S5 I; J% A" _. p8 ] * 获取当前用户信息 7 u& T3 ^! C" c, Q0 r * @Return SysUserEntity 用户信息2 }% v( B$ ~9 L/ y */ 1 y( r; U. _0 z# j0 J! h" O public static User getUserInfo() {! T2 i6 N7 F3 T2 h- J7 o return (User) SecurityUtils.getSubject().getPrincipal(); % O4 x$ T& Z- P( J) V& |. u } ) F/ A* x* n) E% p2 M6 Z /**; J! j+ y3 R! E * 删除用户缓存信息 2 H( Y, m3 j$ B * @Param username 用户名称2 p% Z) p8 G, x9 \9 I8 Y * @Param isRemoveSession 是否删除Session,删除后用户需重新登录7 R+ a$ L4 k0 @6 A2 k3 ?' G */ ' V* z+ \" q/ k* P) P public static void deleteCache(String username, boolean isRemoveSession){9 s; j: o4 A6 c( V //从缓存中获取Session 4 N1 h: v9 r5 a c# C! ^ Session session = null; / z# t9 X' m1 I1 F // 获取当前已登录的用户session列表6 H- E) a b& h: o, n Collection sessions = redisSessionDAO.getActiveSessions(); ' k' O6 o9 W. t5 o User sysUserEntity;! L. L# |% W6 P) r* k6 y `/ q0 x7 N q Object attribute = null;$ }, @ w$ y; g/ l- L$ W // 遍历Session,找到该用户名称对应的Session4 u9 m2 q0 T2 `9 B( k9 o- E for(Session sessionInfo : sessions){ : u) G6 x. _* o" e5 X attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);& G! ]1 d" n$ J0 m4 {$ R0 C if (attribute == null) {/ g i% Z6 u! ? continue; 8 x9 t; I9 \: j& G } # ~$ r' i" ^2 \0 B sysUserEntity = (User) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();0 [; C/ Y2 D. q8 y: _0 O if (sysUserEntity == null) {1 C4 ^& u% V" [1 y$ L continue;6 |1 [8 o4 y) @- \ }4 C4 T4 m# p$ i1 Q" |6 O if (Objects.equals(sysUserEntity.getUsername(), username)) { ; Z. Q. x+ b# v& i- N: ?) `) W4 y session=sessionInfo; ; W" J% m' Q$ ` // 清除该用户以前登录时保存的session,强制退出 -> 单用户登录处理 $ G% l8 b9 n5 ?: C, g& k- u if (isRemoveSession) { 7 g [+ p4 t4 {* B6 F7 w$ s redisSessionDAO.delete(session);( R8 E& i8 s3 X0 S) U0 P$ S! o( B }) w+ }+ Z6 L1 Y; W, L9 r6 H9 m } ~( w) x) v/ @$ T6 O$ S3 g" w N: Z4 U }5 A0 X1 n# g6 C# n if (session == null||attribute == null) {9 L' U8 R/ Y; j: ?8 m: l& l return; - b0 N5 x4 g4 E( G6 o9 W) } }+ | X7 B& U' [: s //删除session 5 y9 w8 L T/ Y; a" o% x if (isRemoveSession) { L [: T9 A; n2 @ redisSessionDAO.delete(session); : a7 I/ N6 k. B }: h! K# G, W+ E1 o' J7 l+ @9 J! L //删除Cache,再访问受限接口时会重新授权 y X J. u5 G$ o$ J4 j' N3 I DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager(); L2 t6 \: M% W0 c( N! J- r8 w Authenticator authc = securityManager.getAuthenticator(); / `' B* o6 N1 d- Q; l5 } ((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);2 H% q( ~9 U/ D. B5 {' Y }3 S0 b; n, H* e$ D+ O: p {$ N/ b /**) f) G H# i& b * 从缓存中获取指定用户名的Session9 G: d1 g* x" i, m# C9 | * @param username , T$ @. D1 t) j, O* ]7 `3 f */ * g3 d0 F* e5 u( _& F( t private static Session getSessionByUsername(String username){2 E& r9 G0 d" K5 z // 获取当前已登录的用户session列表 " y2 t' p5 O) j8 Y- q' F9 B Collection sessions = redisSessionDAO.getActiveSessions();# n+ }( H/ d) B' m% S5 y User user;* ~" G3 D3 ~# ]6 R. ?2 F Object attribute;9 s+ w( F0 {: ]5 _* J // 遍历Session,找到该用户名称对应的Session2 _2 ~' p( P) f for(Session session : sessions){ & ]$ F. p: X# B, k+ x& N, t attribute = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); ! d: X0 k2 T5 w6 X if (attribute == null) { ; a" ?# g$ Z; W; l8 _$ x continue; w* y% i" Q4 u; i# ~ }% Q9 ?9 e2 x" X6 Z, Z5 j user = (User) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal(); & R+ L# s9 D4 O q) a8 u if (user == null) {" w# ~: r2 R( @1 l5 N b# T continue; 9 |9 `1 }- `2 l. v, X4 Y3 H* x. D3 w" | }: Z- {$ s# p! { @ if (Objects.equals(user.getUsername(), username)) { y: G6 `+ |: V o* R1 e return session;0 f6 K# m- |# {2 L; N } 6 } o( |% n1 ?5 H# m }' r r) v1 X1 G' g4 E$ h return null; ' f) @8 v4 [9 R& w* T+ \$ H# r }- C' [- }) ^" p% e }) O5 e. O* |( O

; }* P" O+ Z0 `& X; p4 x; P& m

2、Redis常量类public interface RedisConstant { # R/ C, p: Y% g6 ^) n) W5 h /**0 p0 |% [, u$ G# n9 Z# x: M% e1 D * TOKEN前缀 - H5 Q. [: L7 U, ? */$ m1 @( i5 [+ f! W String REDIS_PREFIX_LOGIN = "code-generator_token_%s"; 2 A# l" g0 X7 e, ?6 N8 a v }, _, X$ B* _3 K' {; n

# ?& j4 g/ b8 d' V0 L. w

3、Spring上下文工具类@Component+ R+ l9 ?/ k( A" v! z5 h0 l+ L/ p, B public class SpringUtil implements ApplicationContextAware {5 [! ~* ^8 A7 _7 z9 t: D6 ? private static ApplicationContext context;/ R* Q6 v1 d5 R4 U( u, U /** + o3 q* i% }* n$ v * Spring在bean初始化后会判断是不是ApplicationContextAware的子类7 ?* Q& W( ~5 O$ M" } |3 i1 m * 如果该类是,setApplicationContext()方法,会将容器中ApplicationContext作为参数传入进去 ' g8 f7 S" O, S( [) v: E */ ( B) @- T$ i9 D ~4 B0 ~ @Override7 i8 M/ B- r2 f5 \& u public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {6 [2 @$ f2 }$ d _ context = applicationContext; - ?% d9 ~% [2 h& c6 i% W+ ]3 ^ }7 F+ a1 O7 S/ Z& t6 r /**- w( s5 X5 Q- C$ @ * 通过Name返回指定的Bean 4 b6 A$ v' B |' @1 { */ 3 r/ y) r8 v! L8 R5 x- F public static T getBean(Class beanClass) {7 d9 m2 L) R. m+ i; b4 N4 r5 F: i return context.getBean(beanClass);5 d+ Q( W$ J2 h) w' Y) d }% b. U% f9 j% _5 x } . v, D" H. e g( \9 j

9 t3 V: o2 p5 d+ _

六、案例demo源码GitHub地址:https://github.com/zhengqingya/code-generator/tree/master/code-generator-api/src/main/java/com/zhengqing/modules/shiro

7 l% A s4 `( c* P% h" F; C

码云地址:https://gitee.com/zhengqingya/code-generator/blob/master/code-generator-api/src/main/java/com/zhengqing/modules/shiro

9 u8 p! V g! s# v( }$ w4 X5 n$ j/ i+ j% e/ [; C6 N4 c) } }& N ! J2 O3 j+ A' W4 H& M7 S# W1 {& o 7 r: y) B3 B/ |2 V4 L! E8 r0 G* r$ G
回复

使用道具 举报

    您需要登录后才可以回帖 登录 | 加入怎通

    本版积分规则

    QQ|手机版|小黑屋|网站地图|真牛社区 ( 苏ICP备2023040716号-2 )

    GMT+8, 2026-4-4 19:20 , Processed in 0.123644 second(s), 23 queries , Gzip On.

    免责声明:本站信息来自互联网,本站不对其内容真实性负责,如有侵权等情况请联系420897364#qq.com(把#换成@)删除。

    Powered by Discuz! X3.5

    快速回复 返回顶部 返回列表