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

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

[复制链接]
我来看看 发表于 2023-03-06 15:25:44 | 显示全部楼层 |阅读模式
7 S, }& r0 D/ v5 v- y

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

" p! f& B- E( Y

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

+ n" a$ K) q- L- S% D

1、引入相关maven依赖 * M7 |3 i( v( s, ~3 z* k' K 1.4.0' p- U. O% y4 D" z' \# m 3.1.0 / O1 @( s/ M* ~) Z* u9 t P7 ^+ ~9 V, X- [3 a) [& I- g. B6 V+ s% K8 J , K3 I5 V8 D, P0 _5 c& P6 m$ N( W/ k" I3 I; G- y. z org.springframework.boot 0 [9 k {! I1 M3 x spring-boot-starter-aop2 q* I2 q9 P' }+ e% f8 [4 @7 b . h! U. i8 w t, p0 |6 U3 S. _7 w: a( {' V7 u % E4 h" d6 E8 }. p/ S org.springframework.boot( L% p9 i: S# @ _# T* u spring-boot-starter-data-redis-reactive: L' P: L$ e8 P/ z 8 `# `6 N$ @6 u1 P. e + a- D6 H# w! _# {( T4 b+ k6 [ org.apache.shiro - M: s% D# j* [) [& R" ` shiro-spring3 n$ Q9 r# S* }) P! z: o$ o$ n ${shiro-spring.version}$ |3 a. b& X3 \) u ~ $ M( w2 T$ f v' A! f) W+ v% V# O& R0 b$ b # p; W( ~$ [8 X org.crazycake ; n% l& W& c- {$ }+ ~5 x/ A/ ^ shiro-redis / S [) I; M* w$ E5 i5 ]- \ ${shiro-redis.version} : W# R$ p# D7 @ S2 W& f9 U: ~1 Z3 I7 g7 R" D1 \$ X: W 7 B- y; [' Q* n' O0 M$ S# q4 F1 T

7 `* H, D8 Q: x- _2 f

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

* \% p: K, O; W" o. Y/ {0 [

@Slf4j $ K' ^6 n2 h1 B5 S public class ShiroRealm extends AuthorizingRealm { * h6 |6 @, M8 X) N9 ]9 n1 z# F @Autowired - y+ b( ]* k: |6 } private UserMapper userMapper;* f% p4 R( N2 R @Autowired" j" J# [2 B; c5 u1 u private MenuMapper menuMapper; + |4 x# T, E* k0 Q2 h6 m @Autowired$ j$ T) x' a3 k# z9 `. ^; d9 L' v private RoleMapper roleMapper; _" l+ z, J {* w" i) G& G @Override! {9 \. F" x! @ z' O' l% a public String getName() { 4 s+ {$ z' ]* f9 O% a( K4 A return "shiroRealm";2 t+ B/ u( a' l1 }# R } ' w; ?6 H( X9 ?: r' \4 C7 t' @9 K /**, f/ _5 E: Y7 o: a1 T * 赋予角色和权限:用户进行权限验证时 Shiro会去缓存中找,如果查不到数据,会执行这个方法去查权限,并放入缓存中4 q8 E6 _% X1 `! i' e& H4 W */ 5 [6 k# ?& h" `9 R; w @Override ! e" `. y8 M& @' G" z protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {) l; @/ p, X7 ^' M2 } SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); / Y' `/ ~( v" N. {5 {' I8 | // 获取用户 ' l9 l- f @' |" W: z# l/ R1 H User user = (User) principalCollection.getPrimaryPrincipal();$ V0 o% s( V0 I! v- q( v Integer userId =user.getId(); ! E/ o( e- D- P" _ // 这里可以进行授权和处理 - n |9 B6 K+ B# \: g Set rolesSet = new HashSet<>();6 J( r! ]2 `* G Set permsSet = new HashSet<>();" N/ Q( ~& v. G+ [ // 获取当前用户对应的权限(这里根据业务自行查询) + z; T* O9 a$ O( ? List roleList = roleMapper.selectRoleByUserId( userId ); . `' j+ i) ]. K' d& t1 [ for (Role role:roleList) {( {* }! V9 o4 a# y" E5 x5 b5 V rolesSet.add( role.getCode() ); / j# |/ d/ @% F5 p0 @ List menuList = menuMapper.selectMenuByRoleId( role.getId() );% H7 \8 P5 p& V+ H for (Menu menu :menuList) {- |) @% `4 `. S6 H5 R& @ permsSet.add( menu.getResources() ); 1 F, G- g! r8 s3 o4 z }( e3 j; A1 B C: i- d1 g }, b, x1 ^4 o V% z5 [- q8 f' x' b //将查到的权限和角色分别传入authorizationInfo中8 n3 X/ z2 L3 G authorizationInfo.setStringPermissions(permsSet);! `! w' V9 d) r6 F% h9 g; \6 g% B; { authorizationInfo.setRoles(rolesSet);4 y: V5 u0 }7 O6 p log.info("--------------- 赋予角色和权限成功! ---------------"); 7 `$ x& i7 ^7 z' C9 {, q return authorizationInfo; $ Q; b- n' U7 t) n3 D: S( M }; s+ u- h% S( @5 d /** 0 t- w' M; t; Q4 l! H& q * 身份认证 - 之后走上面的 授权$ Z: `" o. H% b" A# t% }7 d) u */* a- E% d8 p" P @Override- X( A3 B' C) K6 `3 X0 Z! A protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { ( H: } u5 ~/ [( `% D UsernamePasswordToken tokenInfo = (UsernamePasswordToken)authenticationToken; * D5 o4 @2 p7 p2 i$ t // 获取用户输入的账号 7 J0 K& z( P1 P" l String username = tokenInfo.getUsername(); # G9 T; I H1 l% s0 k8 ] // 获取用户输入的密码7 p3 Z1 R3 @+ \! B7 J3 ~ String password = String.valueOf( tokenInfo.getPassword() );, ^$ D1 ?! N) h8 h& u3 O // 通过username从数据库中查找 User对象,如果找到进行验证% h- R- s( B0 ^" i4 v8 L4 z, t& h // 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 , Q/ R' T* u0 O" j/ E- o% I$ | User user = userMapper.selectUserByUsername(username);: F. S* H6 F5 U H // 判断账号是否存在 / a5 a1 i3 k) C0 U$ A/ [- `1 ]8 G if (user == null) { - Q# ^5 g3 a* O- d5 u& K //返回null -> shiro就会知道这是用户不存在的异常 : h; {0 U* r$ V6 Y) x return null;( B. v' [' Y- _% p }+ S5 z0 c: }5 l7 {3 } // 验证密码 【注:这里不采用shiro自身密码验证 , 采用的话会导致用户登录密码错误时,已登录的账号也会自动下线! 如果采用,移除下面的清除缓存到登录处 处理】 ( X, o& B* r. r if ( !password.equals( user.getPwd() ) ){ 4 v+ g/ [ ]1 s! f, L throw new IncorrectCredentialsException("用户名或者密码错误");, y0 Z a1 @: s$ n) P" W } + l; p9 @5 W& S3 |7 B6 c // 判断账号是否被冻结6 h. O. C4 K( j- f% A4 K4 O if (user.getFlag()==null|| "0".equals(user.getFlag())){ `5 O' o, k2 Q# {! } throw new LockedAccountException();5 e% O p/ y- Y9 t) U } 9 j8 L4 I% m+ E# B7 ?2 \4 z /*** o9 |! U) @1 Y9 l9 L) A9 L v * 进行验证 -> 注:shiro会自动验证密码; U# f" s3 `5 F8 r4 _8 o. S5 {9 }) g * 参数1:principal -> 放对象就可以在页面任意地方拿到该对象里面的值; q0 E# k* m4 I1 [" P' h * 参数2:hashedCredentials -> 密码# K4 |8 `" q! ~0 m2 b * 参数3:credentialsSalt -> 设置盐值 + T' `2 A8 ]* Q * 参数4:realmName -> 自定义的Realm ; T$ \. `8 V1 U) B7 Q */0 H# b/ c5 Y9 `' f5 W2 J: a& \ SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());" P1 S" ?3 P4 @- M9 S$ V0 I // 验证成功开始踢人(清除缓存和Session); e& }) ^7 y$ c5 I" q8 Q ShiroUtils.deleteCache(username,true);5 @* j* l- q# c: W+ g // 认证成功后更新token# G3 k' d$ ?& ~' t" H String token = ShiroUtils.getSession().getId().toString(); e3 V" O% v4 O) m user.setToken( token );! c& D2 a0 k" Q- H- M7 @ userMapper.updateById(user);! {# Y7 B3 E6 q: d' O( V p return authenticationInfo; 9 n* |5 @& S3 n: U7 Y4 L1 F% f }( k: J, d" m/ B0 U/ t# d# M' F }; O7 y( n0 t6 U& y, H8 ?6 Q' d

4 ^/ a; ~' }; \# P- f1 e

3、Shiro配置类@Configuration * x0 l* P0 l- z$ S! |# c public class ShiroConfig {* v. f7 f6 l8 ]) q% o private final String CACHE_KEY = "shiro:cache:"; 0 i& W$ ?, j0 \# T1 n; l6 y# S private final String SESSION_KEY = "shiro:session:";5 _, j! s2 I/ O/ b4 U$ w) u /**0 l- J! b! y' F3 ] * 默认过期时间30分钟,即在30分钟内不进行操作则清空缓存信息,页面即会提醒重新登录7 L% C! S# z" `( e */ # G1 S4 x" h* B# K private final int EXPIRE = 1800;, y* p+ }$ ^( o7 `6 r+ ^4 V /** 6 Y6 A0 ]- f, g( G2 ? * Redis配置! U8 H# C( A, y */ 7 Y1 I# S0 g8 y9 O; i; `$ E2 Y @Value("${spring.redis.host}") 7 z6 {8 a% z& s8 s- j( v( M private String host; $ O( l7 s4 O# T& p0 m4 p+ k2 D @Value("${spring.redis.port}") * Z- V, m" z8 ^# P [2 t private int port; u7 R4 t8 b5 x2 [- v @Value("${spring.redis.timeout}")' i: f% Z J0 @1 B0 y7 U, D4 K$ q private int timeout;7 S9 B, N5 U0 G! O6 ^ // @Value("${spring.redis.password}") ' n c6 F2 x: i: e! w0 h n" a9 A // private String password; 3 {7 n* B, C. b* u0 n, M8 e5 A /**2 h0 e( k" x8 w' m5 y* x * 开启Shiro-aop注解支持:使用代理方式所以需要开启代码支持% `; p- Z) o/ z( @* t */* u* {0 Z$ Z# E& o, | @Bean # T [- n1 |9 W6 _5 R1 N) h) } public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { + d" e. g$ }2 n$ E+ m, L( r AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); - X% \9 R' X4 `. S3 f3 Z9 B* U authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);; s- w9 p4 B- ]7 b: n2 [3 u3 E return authorizationAttributeSourceAdvisor;/ f0 p2 `: }, @ p8 M% }7 r Y7 d } ( u; ~* D; a- |% F# G7 } /** 7 D- N# R3 M S+ @ * Shiro基础配置3 n6 _6 N- p' z! o* T" g# D& U */; B# N; K/ s1 K6 d: d, m b# C( B* p @Bean! [" f2 ~/ v2 l4 P$ G. Y9 P; R" h public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager, ShiroServiceImpl shiroConfig){; b) M5 E6 j6 ^0 U ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); # X: m& w# I& U, D shiroFilterFactoryBean.setSecurityManager(securityManager); 4 }6 L* g2 ]/ N- V" x& W // 自定义过滤器. r1 h7 m$ k# R+ P4 y, y$ T Map filtersMap = new LinkedHashMap<>();- \( I" ^) @& e // 定义过滤器名称 【注:map里面key值对于的value要为authc才能使用自定义的过滤器】 * s/ F: Z7 Z$ S6 ~& o2 D filtersMap.put( "zqPerms", new MyPermissionsAuthorizationFilter() );) `) g. w$ S8 M) o0 i- R+ z8 P filtersMap.put( "zqRoles", new MyRolesAuthorizationFilter() ); 2 z, q6 E1 \+ n7 l# M6 k! K filtersMap.put( "token", new TokenCheckFilter() ); + a, n. t% L0 d% b* A. e; |7 m+ S8 r; Z shiroFilterFactoryBean.setFilters(filtersMap);. n5 b. z9 _- Y& n; h9 l+ @0 ] // 登录的路径: 如果你没有登录则会跳到这个页面中 - 如果没有设置值则会默认跳转到工程根目录下的"/login.jsp"页面 或 "/login" 映射+ E- R" }1 R7 b# f3 w shiroFilterFactoryBean.setLoginUrl("/api/auth/unLogin"); $ Y8 s1 B5 c& o0 e: w$ W // 登录成功后跳转的主页面 (这里没用,前端vue控制了跳转)6 y0 ?. y3 x8 g, d // shiroFilterFactoryBean.setSuccessUrl("/index"); 9 ?% e. {7 ]$ W0 e7 x // 设置没有权限时跳转的url. n. V0 v- s, E* p6 t# |! U shiroFilterFactoryBean.setUnauthorizedUrl("/api/auth/unauth"); # } {6 u8 i% h% O shiroFilterFactoryBean.setFilterChainDefinitionMap( shiroConfig.loadFilterChainDefinitionMap() );8 j# L% m- D G6 q- a( K return shiroFilterFactoryBean;' } c! ]$ C. G: s3 V } / N4 v& x+ R) C( F /**/ b# ~- ~' ?& ?* y; k3 B * 安全管理器. v: K7 F" s6 _5 R0 E5 v */# y- a+ y7 K; L @Bean 0 t7 c: B, p; ~/ E public SecurityManager securityManager() {: H& Q8 J5 k- Q6 H% D5 Z' `" h, h DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); ; q, ?" v/ ~8 ^, B/ U A" ^" T# |) { // 自定义session管理 M' N6 [& n5 H# i securityManager.setSessionManager(sessionManager());& R* r. v; _9 d) q // 自定义Cache实现缓存管理 : K$ ~' t& _& ?0 ]0 E% O \$ G' d securityManager.setCacheManager(cacheManager()); 7 f4 b$ B& B9 l/ w6 ]3 b1 G // 自定义Realm验证 ( `7 L1 n4 {. A6 w securityManager.setRealm(shiroRealm()); 3 V, g' ~" ?8 Q return securityManager;4 e6 n. y9 U6 @& H$ @+ G3 P6 x$ o# ~ }$ T3 W# q9 t* P7 D9 r /**7 U2 M) K1 H" k * 身份验证器 0 o+ t7 @, `& m( Z9 Q& s: T$ v! Y */ + ^& l9 E! ]. }. d7 C @Bean$ s5 C3 F+ v$ E- u$ e public ShiroRealm shiroRealm() {6 J- q& O) O' z! |8 m ShiroRealm shiroRealm = new ShiroRealm();5 t+ o, M% n1 l! G2 x# s& r# X shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());7 k9 y6 v! n6 G" l% Z* [ return shiroRealm; % B3 q( Y) T7 Z+ E } 3 A9 q; E; e& }" M1 C& H; E /** 3 I/ a5 D7 N/ Y3 o+ R * 自定义Realm的加密规则 -> 凭证匹配器:将密码校验交给Shiro的SimpleAuthenticationInfo进行处理,在这里做匹配配置 ; Z2 q- v( y7 G$ ? */7 w) n1 R- k" ~4 D; B+ |% G @Bean - B: F7 \$ k7 z( l. L public HashedCredentialsMatcher hashedCredentialsMatcher() { 0 s* U- o! C, i/ d6 _: o+ z HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();2 P, u w$ Q$ f3 C+ f // 散列算法:这里使用SHA256算法;. ]2 K; r, B2 y: Q2 X, O8 A9 ` shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);( U3 Q. O# I. A4 l0 @( E // 散列的次数,比如散列两次,相当于 md5(md5("")); 7 P7 g0 T+ @2 W1 K shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS); , _% R- P! ^7 Q/ i6 ]9 O return shaCredentialsMatcher; " A. q8 W% c" j& m0 a } 7 Z/ y3 c+ W8 G' `! P /*** S* N, V3 F5 {, B5 K * 配置Redis管理器:使用的是shiro-redis开源插件 8 g7 y9 M1 f8 | */7 m6 f% W; G& F6 T! R+ l2 q @Bean " |: R; d6 f' I# { public RedisManager redisManager() {6 A# k4 G1 P$ _3 i6 G RedisManager redisManager = new RedisManager(); 3 g* ^) t+ [. w" f redisManager.setHost(host);8 i( T U- i4 Y( A8 w5 Y+ p7 A redisManager.setPort(port); 8 x% M" u$ ?/ R! c% X5 G5 ?) C y8 N) t redisManager.setTimeout(timeout);5 h, f0 ? s5 K; a // redisManager.setPassword(password); " U5 v+ s- d3 j: T/ M: e& x return redisManager;$ s) ^3 W* A+ C2 O } & P @- Z. ~+ {1 V( ^' M: [( D' a; V /** - F6 c! x1 Y u * 配置Cache管理器:用于往Redis存储权限和角色标识 (使用的是shiro-redis开源插件)$ B$ a8 X i4 P */! L" w' P( K6 F @Bean + B" j. ]! U- }& L7 o public RedisCacheManager cacheManager() {; ]2 y. Y9 d0 C0 q: q RedisCacheManager redisCacheManager = new RedisCacheManager(); ( Y6 p( u* l) S% t* k$ o redisCacheManager.setRedisManager(redisManager());% T% t$ g' p/ q: A7 p2 J- Y8 }9 J redisCacheManager.setKeyPrefix(CACHE_KEY); 3 b# d7 f/ q! i( D. c // 配置缓存的话要求放在session里面的实体类必须有个id标识 注:这里id为用户表中的主键,否-> 报:User must has getter for field: xx 5 l5 O& ]7 Z+ C3 O5 b- g$ G redisCacheManager.setPrincipalIdFieldName("id");- i( h) S2 D2 R6 R/ e1 x* ?' P return redisCacheManager;% S5 J* `8 u1 ?$ v }* f6 O. L0 \6 T& ]" Y: b /** * \" C% |3 A. | * SessionID生成器 & b- A3 M5 p' u' V3 x; |% c0 T */0 `4 }* F4 s) j' T @Bean " d2 r6 t( M" Y6 T7 u) K+ W public ShiroSessionIdGenerator sessionIdGenerator(){8 Y' ~1 n6 M9 v return new ShiroSessionIdGenerator();* ^7 L, Y$ A* c% Z9 g5 K3 P+ \ }5 H+ d. |( a$ |) ~- C# z /**1 U! m! t& k8 ~2 c. I3 Q9 f * 配置RedisSessionDAO (使用的是shiro-redis开源插件)0 J H$ J- T( t) D( Q3 P3 |# Z */7 P( f/ T- u# f4 H Q3 J2 |* e' d) b @Bean$ R) P+ c/ |9 m8 z4 @. h public RedisSessionDAO redisSessionDAO() { * y L& Y: X# `; V* b4 I RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); 4 o5 H5 m( j. P# A* y redisSessionDAO.setRedisManager(redisManager());! Y+ Q+ ?3 f* g# n H redisSessionDAO.setSessionIdGenerator(sessionIdGenerator()); e& g" x4 B! E+ Q# c redisSessionDAO.setKeyPrefix(SESSION_KEY);4 w" D4 a/ R% J9 R) M, D redisSessionDAO.setExpire(EXPIRE); + Y4 j$ g& D) C# N return redisSessionDAO; ^9 t1 V0 h: g" Y0 c' L% L; p4 C# t( r } 9 z% ^/ v! P- Z6 @) {" e /** ) C" J4 Q4 B3 ?( ~3 O! c * 配置Session管理器 7 Y# s0 i& J3 y! W */. U; s% [, A* K. ~5 J @Bean 7 n1 k, B+ f4 ?) W3 H public SessionManager sessionManager() {! D* i0 L C9 f- H1 j ShiroSessionManager shiroSessionManager = new ShiroSessionManager(); 2 b. y7 }, a1 N+ N7 Y$ c' | shiroSessionManager.setSessionDAO(redisSessionDAO()); # R7 c8 c( Y2 r# q* ^1 \ { return shiroSessionManager;5 z" C( ?. l! w& h6 ?4 l }8 W5 |+ L6 r# s6 h$ D/ a4 N. m } % {4 V/ w$ y; }

- a5 C( y1 Q. D! F1 Z- D

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

, J* ]# F% q. e7 L$ f9 l$ L

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

8 L$ z5 k2 c) s8 P/ X) T

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

' X9 W. c' {& C2 y. G. B

public interface ShiroService {. F4 z# s& ]& H9 n7 R9 K' i /*** N5 ~$ q) |7 V% i7 D * 初始化权限 -> 拿全部权限 + ~* ^3 Q+ | R1 p *3 Q+ D( Z( j3 F6 U/ U0 G0 F' O" D * @param : 2 C3 h0 [; G5 a/ _% N5 L3 U8 | * @return: java.util.Map7 Q/ Q) U8 i5 I, R5 a: B4 E */3 w! ~; i% @0 n3 a( D; b; g Map loadFilterChainDefinitionMap(); I' F/ Y/ E1 z( H+ u /**9 L3 b* R# m- @8 C E# G' i * 在对uri权限进行增删改操作时,需要调用此方法进行动态刷新加载数据库中的uri权限 9 [# p, ?! l9 b2 A *9 _, i: W G7 \: S- v * @param shiroFilterFactoryBean- A0 v) u4 O9 G6 w$ u) H) g# c% k * @param roleId , x; v4 j: ?* N/ x9 \, E; ^ * @param isRemoveSession: 5 t8 H1 a: {5 [. D1 G8 V * @return: void) C; J `$ O6 ^! M- K7 Y, J */$ f6 u5 q$ I8 }/ g D% ]/ A6 N4 J void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean, Integer roleId, Boolean isRemoveSession); - V2 f0 w4 b; X% e" o, [0 |* k; q /** , ]7 n8 w4 P4 F L5 S * shiro动态权限加载 -> 原理:删除shiro缓存,重新执行doGetAuthorizationInfo方法授权角色和权限 7 F$ N" W! B0 J+ Z3 \9 V *! K' f' L8 H3 u" G' Y& z w2 ~7 f. u * @param roleId 5 W' p0 u- F( o7 U( D% O) t * @param isRemoveSession:5 g$ E$ E) j$ x& {: E+ H7 R$ t2 l- } * @return: void. K: N) w( s( ^% p' h# e */' k3 w+ i9 C' h% H* e& {3 b void updatePermissionByRoleId(Integer roleId, Boolean isRemoveSession); ( A, }7 A& j: d6 m% P* n }, ~. \+ G. S3 W8 J2 y) T* i @Slf4j1 s* ?5 D# K# o& |, g @Service( H6 ]/ }9 g+ j, y0 ~8 c5 i public class ShiroServiceImpl implements ShiroService { + @2 p: V. @3 \4 F. R" R2 b @Autowired5 _& Q; H' I& i- |3 M+ Q9 F private MenuMapper menuMapper;+ \8 Y3 k; ? k, v7 i; w4 } @Autowired ; H9 S$ A2 Q# D& U3 \& R- k* P private UserMapper userMapper;5 L1 n- D( m: v2 W1 e- b0 l @Autowired7 D6 f9 n1 W& W B) c: b private RoleMapper roleMapper;& i7 g" a- C( u0 R; e" o @Override - \- x& a; a- t) @ public Map loadFilterChainDefinitionMap() {1 Y; l" l, c8 B // 权限控制map o' L' {% R- d$ }7 r& B$ @ Map filterChainDefinitionMap = new LinkedHashMap<>();! |0 U5 I6 {: `* |' ?4 | // 配置过滤:不会被拦截的链接 -> 放行 start ----------------------------------------------------------+ D! Z, n8 D4 ?8 C5 R* \ // 放行Swagger2页面,需要放行这些9 {+ l: r# n" h" m filterChainDefinitionMap.put("/swagger-ui.html","anon"); . |0 r1 n& q. \$ t% Z filterChainDefinitionMap.put("/swagger/**","anon");) x( N, K- N k$ ? n0 \+ O) m filterChainDefinitionMap.put("/webjars/**", "anon"); 7 q% \& }0 ?3 z filterChainDefinitionMap.put("/swagger-resources/**","anon");/ N% b& x0 ~! ^2 P) ~1 } filterChainDefinitionMap.put("/v2/**","anon"); 5 ]9 `# `6 s! g1 l$ K6 S, c filterChainDefinitionMap.put("/static/**", "anon"); ; G3 M3 x. s f5 E // 登陆 : m) J5 ]; `5 L filterChainDefinitionMap.put("/api/auth/login/**", "anon"); 0 p: q9 }2 S$ J# L" D0 T- Y3 s1 k // 三方登录+ X. G9 v* ~: ^ filterChainDefinitionMap.put("/api/auth/loginByQQ", "anon"); 4 f2 V; H* s) v! b! W) G. ` filterChainDefinitionMap.put("/api/auth/afterlogin.do", "anon"); 9 P6 a, d3 a9 I6 T3 b! Y& N // 退出 , W! D, _! }; m# l2 s0 ?6 ?" a/ N filterChainDefinitionMap.put("/api/auth/logout", "anon");1 B. M9 S1 ]8 ~- D // 放行未授权接口,重定向使用* D/ H4 m: `6 b6 y filterChainDefinitionMap.put("/api/auth/unauth", "anon"); & y. x+ D- l1 M" b3 d // token过期接口, r5 C7 |' r4 Y2 U+ U G filterChainDefinitionMap.put("/api/auth/tokenExpired", "anon");& M+ N, e- p+ B4 c7 L // 被挤下线' v" d2 r% \, p7 c7 \ filterChainDefinitionMap.put("/api/auth/downline", "anon"); 6 v! l# ~4 w( C. |$ f- H" E // 放行 end ---------------------------------------------------------- ! _0 r7 F( r# b, j* _ // 从数据库或缓存中查取出来的url与resources对应则不会被拦截 放行 ! {& Y5 e( N) {0 R( Y) ? List permissionList = menuMapper.selectList( null );/ R1 ?7 r/ B8 p3 w7 e4 ^( @0 m if ( !CollectionUtils.isEmpty( permissionList ) ) {1 L* U- W! Q) L2 c permissionList.forEach( e -> { 4 R2 |8 t9 ~* F( B. z if ( StringUtils.isNotBlank( e.getUrl() ) ) { + O! Y6 c: H6 Z, l, o1 N7 _ // 根据url查询相关联的角色名,拼接自定义的角色权限 / j3 v; ?- k( R* R8 X List roleList = roleMapper.selectRoleByMenuId( e.getId() );8 K/ W; o$ o! H5 M2 q) u StringJoiner zqRoles = new StringJoiner(",", "zqRoles[", "]");$ p B( H$ T) w J8 x! L+ q if ( !CollectionUtils.isEmpty( roleList ) ){ U/ u% q2 K# n roleList.forEach( f -> { n" q. G- P; o; M$ Z zqRoles.add( f.getCode() ); ( M) o2 n; ]: x0 m- n/ j, f, [' G/ T });7 w) w9 A+ ^; b9 w( _9 j, Y' R8 i7 {& [ } L% ]4 }2 R: r // 注意过滤器配置顺序不能颠倒/ w* D8 E6 F$ `; `6 O w# w' F // ① 认证登录* m [- X! f2 a7 O( g* S // ② 认证自定义的token过滤器 - 判断token是否有效 6 E6 B7 v# [) y& ~, y& x$ |9 ~ // ③ 角色权限 zqRoles:自定义的只需要满足其中一个角色即可访问 ; roles[admin,guest] : 默认需要每个参数满足才算通过,相当于hasAllRoles()方法 % v' S1 h1 Y" h- x // ④ zqPerms:认证自定义的url过滤器拦截权限 【注:多个过滤器用 , 分割】 ' B& O/ r7 M6 k; v1 f2 y // filterChainDefinitionMap.put( "/api" + e.getUrl(),"authc,token,roles[admin,guest],zqPerms[" + e.getResources() + "]" ); $ ?- M. X( w: h2 f" F filterChainDefinitionMap.put( "/api" + e.getUrl(),"authc,token,"+ zqRoles.toString() +",zqPerms[" + e.getResources() + "]" );+ d9 L: z, ^$ ^ // filterChainDefinitionMap.put("/api/system/user/listPage", "authc,token,zqPerms[user1]"); // 写死的一种用法 . N! o( { H2 T9 ]1 ? }3 U" q. S* x& {# E, d; ~ j/ J T });: F; T4 F5 w$ C& S$ H( P } 5 U- ~3 ?9 N4 } // ⑤ 认证登录 【注:map不能存放相同key】 ( X1 z+ Y' r- D# d! v r+ ^ filterChainDefinitionMap.put("/**", "authc");7 H. T! V- m1 {3 L* I( x return filterChainDefinitionMap;+ O$ B& |6 [* W } 5 d& l4 Q& z0 O' p" f1 `) q @Override+ b: Q3 s, \3 {+ L6 A& [/ i* @3 U$ S( U public void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean, Integer roleId, Boolean isRemoveSession) { + z0 t F( K! x% |9 N synchronized (this) { + Y+ q7 I& |$ k/ t. S AbstractShiroFilter shiroFilter; 0 y" B: T2 c) |1 b try { % y: ?5 C. A; T3 Y( l shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject(); : m2 b% A( L6 q/ a* Z5 w6 M/ J/ _ } catch (Exception e) { & \' {- C+ f1 }0 Y) V0 o7 f throw new MyException("get ShiroFilter from shiroFilterFactoryBean error!"); $ ~4 k! e6 O$ Z3 ?+ p }: S& p2 E- U8 u( R4 Y7 ? PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver(); 2 o( ], ^' G0 w6 |3 _: K5 ~* W! t DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();& k. x0 ]: K3 h2 E, ~1 E* ? // 清空拦截管理器中的存储: U( G2 j- `4 n8 X% ` manager.getFilterChains().clear(); 0 c: o. d4 f; P) n: W6 l6 g/ I3 Y // 清空拦截工厂中的存储,如果不清空这里,还会把之前的带进去8 M& ?' j5 R/ B" K0 B) F% G' I // ps:如果仅仅是更新的话,可以根据这里的 map 遍历数据修改,重新整理好权限再一起添加 3 d u, c' T9 m, }+ \ y shiroFilterFactoryBean.getFilterChainDefinitionMap().clear(); 4 h( c: f8 r$ G; E) @7 y/ q2 t // 动态查询数据库中所有权限 9 E7 l" c9 M; \ shiroFilterFactoryBean.setFilterChainDefinitionMap(loadFilterChainDefinitionMap()); * @. ?! x/ m. \8 j/ _# }# r3 v' X. C // 重新构建生成拦截; H1 A9 R6 E& w; T! m# z Map chains = shiroFilterFactoryBean.getFilterChainDefinitionMap();! U1 p& U& i, Q+ U8 g' t for (Map.Entry entry : chains.entrySet()) { j& `4 S* h4 Z! m manager.createChain(entry.getKey(), entry.getValue());- ^! q; p* _0 L5 m( S; j8 V } + A9 B; n& x b5 [2 j% l log.info("--------------- 动态生成url权限成功! ---------------"); 3 Z5 E; u& X3 L( {/ P5 j // 动态更新该角色相关联的用户shiro权限% V9 B3 o, G( W8 L# ^$ ?% b+ i7 [ if(roleId != null){+ y8 W$ ?" B) N updatePermissionByRoleId(roleId,isRemoveSession);: Y" x6 z5 t$ b9 H" C) t7 |5 E }* Y; z8 {. `; j: ?& X } ' B i* o$ E- R6 r+ K6 N }( y- d/ Q* F; }: j, W! N @Override+ B# h& g7 ]8 G5 m. \ public void updatePermissionByRoleId(Integer roleId, Boolean isRemoveSession) { ) B2 D* E; [0 y# ? // 查询当前角色的用户shiro缓存信息 -> 实现动态权限1 ]1 p& S) K; R3 W2 C6 b List userList = userMapper.selectUserByRoleId(roleId);- S2 z! N0 [! c# c // 删除当前角色关联的用户缓存信息,用户再次访问接口时会重新授权 ; isRemoveSession为true时删除Session -> 即强制用户退出9 L2 o. Z8 k& }0 z9 }; [ if ( !CollectionUtils.isEmpty( userList ) ) {+ N+ R; V6 @- O for (User user : userList) {( \4 ?* H1 V1 r* m9 `9 ] ShiroUtils.deleteCache(user.getUsername(), isRemoveSession); * s+ R- S$ [- D! d. D% Q) x }1 c; B6 S+ M0 I, ^- m; t }! t1 S: ?' L8 i9 u! K) P log.info("--------------- 动态修改用户权限成功! ---------------"); 6 B/ L! {8 n+ m& d2 d0 `. u8 [ } 6 w g% b U9 j. I. g } # I0 ^# ?& G( Z T

4 w2 [7 T) V" R0 X/ I7 S# }! s Z

四、shiro中自定义角色、权限过滤器1、自定义uri权限过滤器 zqPerms@Slf4j + o6 {- v7 r4 P- Y6 D1 B public class MyPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {% L: v p7 j \/ \1 ?: O5 f @Override % E5 e I; Z" ~- C; M0 f protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {9 M" P" T# W9 y( x0 t9 i6 k8 a3 u% n HttpServletRequest httpRequest = (HttpServletRequest) request;6 P( k! t; z' p3 C HttpServletResponse httpResponse = (HttpServletResponse) response;/ E9 i8 @5 {* ^; `3 ^. A String requestUrl = httpRequest.getServletPath(); ' d; @5 o& f) ]. G log.info("请求的url: " + requestUrl);. o, O" A: r& H! H" ^ // 检查是否拥有访问权限 3 K5 ]& i, f) ]5 O% i1 O0 J t Subject subject = this.getSubject(request, response); 8 d5 E, _; b" [4 x if (subject.getPrincipal() == null) { : ^ K+ \. b6 S* h$ K) K this.saveRequestAndRedirectToLogin(request, response); + C5 \- P" d) c$ l [0 d } else { - p" D% o2 ]' L) y // 转换成http的请求和响应 " z. y. j9 e( e% U0 z1 R- R HttpServletRequest req = (HttpServletRequest) request; 8 b& N b- {1 O8 @6 ~2 s3 y HttpServletResponse resp = (HttpServletResponse) response;6 g0 L6 O9 R) T" q0 z // 获取请求头的值 # d+ F7 n: L0 K2 |0 Z String header = req.getHeader("X-Requested-With"); + @4 i+ `0 N( V* F3 y! F& R- z3 { // ajax 的请求头里有X-Requested-With: XMLHttpRequest 正常请求没有 * g3 o; s: F9 n! Y$ L# N* n" I" [ if (header!=null && "XMLHttpRequest".equals(header)){ 3 ]5 S. M1 d8 q1 J l4 z: P resp.setContentType("text/json,charset=UTF-8");; S3 r/ q* f* I1 ]( y# j! c resp.getWriter().print("{\"success\":false,\"msg\":\"没有权限操作!\"}");7 s2 p, k7 g% B# B7 H" B \ }else { //正常请求/ `8 K8 e5 l% N3 ]/ W String unauthorizedUrl = this.getUnauthorizedUrl();/ L: A- b, v5 w2 {( B* M if (StringUtils.hasText(unauthorizedUrl)) { # m2 O8 L4 j9 A- A0 J8 I. H WebUtils.issueRedirect(request, response, unauthorizedUrl);0 j& G6 g8 J6 H0 u+ H3 \ } else { 1 N1 @ z U$ f3 p7 s& S WebUtils.toHttp(response).sendError(401); * A7 y8 w6 r! P1 H$ e3 X } 1 d/ c7 W$ w; t+ W* K& ?6 i }) @! }& Y8 C& f4 J; s } X, O0 I8 s+ {7 x3 K return false; # T3 o9 w- W4 J7 L# V3 c# \/ q9 w }( T2 m' \* a. ?5 D$ s 5 g: Q) M% D8 F4 A. r, E3 f } t3 T$ |/ j' d5 t; u7 C

2 m' C+ _/ U8 E. S. [

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

$ @3 J1 j- K& L4 |

ex: zqRoles[admin,guest]public class MyRolesAuthorizationFilter extends AuthorizationFilter { . m! {! C( q+ E/ a( \* p @Override! h8 e# i) X+ ]# ~1 s protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) throws Exception {- Q- I5 T" N6 I; f- r9 w Subject subject = getSubject(req, resp); ; n. D2 b7 ?% H8 U+ D' o1 R String[] rolesArray = (String[]) mappedValue;* N9 y9 c( P; b7 }! h; @2 i // 没有角色限制,有权限访问 5 Y# s. D& X0 A if (rolesArray == null || rolesArray.length == 0) {% {& \ Q" K; V$ C7 D" x' n. W( t return true;9 D9 j) q3 |4 @9 s2 ^) Z% Q }% W) N9 Q9 M. H. ? d5 s, ] for (int i = 0; i < rolesArray.length; i++) { & S7 z U7 m1 L5 R //若当前用户是rolesArray中的任何一个,则有权限访问$ D \- o# R" U" K1 l if (subject.hasRole(rolesArray[i])) { 5 ^3 H6 V' h- S return true; [& ^( p# R% P1 ?: s/ p } 3 H3 ~0 R1 ^% J2 c8 M } , ^! s0 f" P+ W return false; ; o2 Z7 M) m9 R r) O* f" h } 6 O5 i0 ] n1 }- C( U* x2 @ }% V3 x( f$ a" _

: Z ?# |2 t. o; n

3、自定义token过滤器 token -> 判断token是否过期失效等@Slf4j4 O( F/ ~& f; E" ?& U- E9 _- [, ?8 x public class TokenCheckFilter extends UserFilter {1 J* h+ U9 j1 A3 b /** + [9 ?$ R% k5 T0 E t * token过期、失效; K9 V+ O- v0 ^! Y+ Q1 y% l$ x+ e9 P */% E3 r4 \+ `$ C5 q4 p* y6 L private static final String TOKEN_EXPIRED_URL = "/api/auth/tokenExpired"; " Y6 Y* T; w# U: V$ P( F% }% P0 L /** ) P' n/ o# w" W. m0 { * 判断是否拥有权限 true:认证成功 false:认证失败 : D3 g, u( }. E- k( c * mappedValue 访问该url时需要的权限% w7 e1 d& M* D* O: x( E * subject.isPermitted 判断访问的用户是否拥有mappedValue权限) A3 }1 S% D6 }0 H1 y! A7 r/ p */ : @6 }7 _: a0 } @Override3 E3 j4 b. s5 A! V. W public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { 9 }$ i+ N9 e8 b HttpServletRequest httpRequest = (HttpServletRequest) request;' U2 B1 a% ` O- ~' K5 \6 B HttpServletResponse httpResponse = (HttpServletResponse) response;5 Z1 d% ^6 h9 K // 根据请求头拿到token( `# d' U4 ^! p2 t. D9 p String token = WebUtils.toHttp(request).getHeader(Constants.REQUEST_HEADER); / _7 R; l- j' ?' v8 [% [9 l0 |$ u log.info("浏览器token:" + token ); ; `( C6 T" }9 v7 g; v User userInfo = ShiroUtils.getUserInfo();( O$ E8 C* `5 G/ V! l String userToken = userInfo.getToken(); 4 O5 X! r) p; u) v% v // 检查token是否过期 0 X1 U0 j7 P: N& E! M' @. E2 n$ r if ( !token.equals(userToken) ){" P, O% d( C `) A# v# P2 k# S) p return false;5 R2 |8 V, k7 E5 ~+ i. l' o% A }; E/ n" ?& @, x return true;) ~2 d5 I" B4 d" a } 3 |, O2 y5 d0 k /**8 v6 G3 I9 U2 k9 a * 认证失败回调的方法: 如果登录实体为null就保存请求和跳转登录页面,否则就跳转无权限配置页面 M& u+ G& ?; L& s5 j1 \- c# R */ * M0 e4 D) \4 _3 ~! f @Override # y7 d4 n0 G O6 m" W protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { Q& l: a1 o+ @5 Z$ n, r* H User userInfo = ShiroUtils.getUserInfo(); / j5 D3 g# e f8 y // 重定向错误提示处理 - 前后端分离情况下 ! C2 ^/ c9 ]5 f! _2 v. r! b9 K9 Z WebUtils.issueRedirect(request, response, TOKEN_EXPIRED_URL);) e4 j, \/ X' ]/ m return false; $ l l" t& b; h/ ` }5 s0 H* Q( h/ |$ I; K) x, j; M } * y/ e- `' y. l. |

* h- o: ?/ q* C+ Z/ Y2 F

五、项目中会用到的一些工具类、常量等温馨小提示:这里只是部分,详情可参考文章末尾给出的案例demo源码1、Shiro工具类public class ShiroUtils {0 \# V% o, x/ g# P# r6 Q6 M /** 私有构造器 **/ 4 ]7 b$ ~: S( A private ShiroUtils(){ }1 \" N2 a5 Z2 }/ ? private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);: G1 ]/ i. `- z j /** & @; a2 m; I4 P9 N7 W7 C& t+ X * 获取当前用户Session" G) |; M# U* q: o" A( l$ M* H * @Return SysUserEntity 用户信息 ; S3 b, ^. ]2 r* ^# K! D/ m */8 D3 F+ O3 q( I5 B; f' e' H- _ public static Session getSession() { % i, _. m4 B# y4 p J return SecurityUtils.getSubject().getSession(); ) A+ ^& P' H& F ^. T3 V }5 V: g: @8 N# j$ @ /**5 C' X* u6 b& n3 y o. i- k% A * 用户登出 p {9 ^/ T! z4 [# K; K */1 z8 w( o! R, f' }7 S& n% j public static void logout() {5 T# a; F/ Y( Q8 y0 I# c5 d* A SecurityUtils.getSubject().logout(); 9 X. W0 m N/ i0 J } 4 f7 u f( ?6 W /**, d. \2 l: h: d( s% t * 获取当前用户信息3 V: K5 q6 {- P: F4 \+ q, s * @Return SysUserEntity 用户信息; c- e% S" r: x5 [* M */! }* }* Y1 F! {, h6 s public static User getUserInfo() {2 o/ G' {/ g! y% m return (User) SecurityUtils.getSubject().getPrincipal(); 2 |% y6 }' [: `! U } # b4 b1 |, }/ @2 } /**2 y ^7 {, N* m; ~0 n, \ * 删除用户缓存信息( Y7 g% O, H# I+ ]% g3 K# _ * @Param username 用户名称 + r# g2 S0 c) l! ~ * @Param isRemoveSession 是否删除Session,删除后用户需重新登录 C6 ~) k, m! P/ C# s: ~, L- l */; R. Z+ ~6 M5 v9 N# d) Y0 q public static void deleteCache(String username, boolean isRemoveSession){ ' N- b, y4 i' P; R5 J* C& } //从缓存中获取Session3 J( N# f* [! J1 J5 n& ~ Session session = null; 9 A3 g+ ^2 K; N7 i; S3 A8 i2 W // 获取当前已登录的用户session列表+ D8 W8 j3 X3 i2 a1 q Collection sessions = redisSessionDAO.getActiveSessions();! P" g! V$ R+ h7 ? User sysUserEntity; : `( r! v; s, [# N8 _0 i. Y( q! k Object attribute = null;8 n8 f. \3 f* t- { e; E // 遍历Session,找到该用户名称对应的Session7 @4 H! h/ v6 E5 ?+ Z0 r0 k8 z for(Session sessionInfo : sessions){ ; c1 J! M6 ?, j$ a attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); ; d: C/ k, H5 }1 i6 Y4 { if (attribute == null) {% O# J8 ^5 ~+ A2 V% K continue; ) S% {# H+ A% j: g$ d# N; @ }9 S& u9 n! K, y$ u( ? sysUserEntity = (User) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal(); + C) a$ p3 B9 G; t+ |. ^ if (sysUserEntity == null) {' R. ]- t, r1 G+ C( F continue; ; |3 E3 m( a& J4 i% V }+ k9 ~1 P- `3 y2 T5 f) v if (Objects.equals(sysUserEntity.getUsername(), username)) {* m# K& l0 R4 r% Q% Z8 H2 X session=sessionInfo;6 n7 f! @" [: @7 X. c% B/ B! {2 @, l // 清除该用户以前登录时保存的session,强制退出 -> 单用户登录处理" X3 P- X! m9 b: ~ if (isRemoveSession) {/ ^3 f" T* T- m+ [0 d" ]1 z redisSessionDAO.delete(session);/ E5 @1 Z( T& I: {' V } ( L# W/ Q! Q2 d. n5 Q; u2 Y# _ }0 s1 F, L5 c( b' _) ] }& ~9 G" }: t: W8 C! } if (session == null||attribute == null) {( x& n: d3 o8 o/ p; W return;0 f: K, |% H: L9 a: n4 Y }8 l$ K: B: A. Q" n* h& T) x+ { //删除session0 G/ q: V$ ` G% C* |& e if (isRemoveSession) { ) v" z) s, R) x: o redisSessionDAO.delete(session);0 _7 G1 \5 L7 l5 f/ ] } 1 s3 b! \ O. D7 s- h //删除Cache,再访问受限接口时会重新授权& J# Y$ } w- G1 Q1 O' R. V DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager(); - X0 j& H* R* k7 P( y+ P2 { Authenticator authc = securityManager.getAuthenticator();8 C9 b7 `; C( `. J/ r) X6 } ((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute); * c' @* C; H8 f% [$ s9 q } 4 O" A3 p- z9 p /**0 H/ b1 K$ d! o& X% e * 从缓存中获取指定用户名的Session4 @9 z5 y5 k. |/ N; E * @param username+ P2 {8 ?+ o8 D8 g */7 s& j! ~" P9 s2 {, s& `( c5 f private static Session getSessionByUsername(String username){ 5 h0 f4 u( H. ] // 获取当前已登录的用户session列表 / q; g3 A4 j$ C5 \9 ]8 Q Collection sessions = redisSessionDAO.getActiveSessions();9 }' B* t, X- n+ @0 g: a* J0 C) B User user;6 x% j+ t) T2 y8 L" D1 o Object attribute;8 b6 E3 X. [! o$ c% [/ M // 遍历Session,找到该用户名称对应的Session+ o: o7 I# m2 |+ V9 |1 x2 P4 e5 A for(Session session : sessions){; @$ p- P# g" | attribute = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); 8 R) M# ?9 N' e: T& W! e) ]5 ^, P if (attribute == null) {* Y: ~ y: z2 i' f- |8 p continue;: G" Z& q" w, v; D! j E } - y' Z# U2 N- q" q user = (User) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();4 E- B. y% W8 O9 t$ q# G5 R if (user == null) { 2 M9 F$ u/ C9 W. H continue; % f& m# X1 c# K7 D& m: g } 2 ]8 X* p4 i, I* \ if (Objects.equals(user.getUsername(), username)) { * T. B8 K( {, @4 g& y" t7 W4 P$ C return session;* t- ?7 x8 g9 q) Z$ ?7 m7 Z }+ m' v$ |0 w1 D( Y8 v/ P# N& a }/ K& \: Z0 S' E0 Y& g2 ~ return null; ! l7 } S4 z( d8 W0 w2 f: R+ B2 u } 2 G5 M1 j# m4 S4 K, {$ J& x }9 @. l. A+ t' Z: P

! o" g4 i% Q% m

2、Redis常量类public interface RedisConstant { % c! [( g. u: h- C) L! E& t /** * M0 F; |2 _& O' w * TOKEN前缀 ) h! y6 w1 c8 I */ 1 c; M H( F3 e& x* | String REDIS_PREFIX_LOGIN = "code-generator_token_%s"; 2 E( v; ?" \4 s0 f3 ? }% |/ K- G6 \( a

5 b8 ~* X$ @- C

3、Spring上下文工具类@Component; Q; a4 [/ E4 U8 K public class SpringUtil implements ApplicationContextAware { . P# m: U( s h q. q private static ApplicationContext context; 3 A! p4 U( g9 g/ q \3 N* k8 t2 X /** 6 i- u0 a6 x8 _! ?( ^0 O3 @ * Spring在bean初始化后会判断是不是ApplicationContextAware的子类/ O6 a% ^+ J* q * 如果该类是,setApplicationContext()方法,会将容器中ApplicationContext作为参数传入进去* g1 |' o: J7 g3 x- X- F# c# m7 ` */* y9 W: h% j6 W) I1 n# c @Override/ @) d! D3 [2 h7 ^/ F public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { / J: T ]- |% ~& O" h, X6 W2 ] context = applicationContext; & _1 ~* d( U, X% U( h' ^ }$ c* X, v& ?' s2 j- k% H /** 7 z" k( s( t: S1 R: _ * 通过Name返回指定的Bean / T6 T0 k2 E, s8 O */7 Z: {* M$ B5 m5 n7 p5 r. y- H/ P public static T getBean(Class beanClass) { & m" w3 D* M. m8 [ return context.getBean(beanClass);) ^% {7 ?+ g! L8 a2 e, q* b }: C( ~; B6 @' \ } 8 I3 A8 r& \. P5 q: H( O6 |) i D

- O+ a9 `6 g M& {

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

2 y* j2 n. q- g, @; [2 z

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

/ f) F1 x' U; _ x8 B $ l4 h, r/ M3 ^ . R6 P. N& `' k" ^& a ! |% F6 k4 N% O' U+ a8 J3 N4 u4 n- C6 y. I! w/ s. _
回复

使用道具 举报

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

    本版积分规则

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

    GMT+8, 2026-7-9 02:11 , Processed in 0.039877 second(s), 25 queries , Gzip On.

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

    Powered by Discuz! X3.5

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