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

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

[复制链接]
我来看看 发表于 2023-03-06 15:25:44 | 显示全部楼层 |阅读模式
# f3 E/ O! V8 i: K

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

! L2 }4 `! a) [" E$ j* o

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

& O$ {1 i, C7 j! m

1、引入相关maven依赖" A& v! Z4 j, J 1.4.00 O+ }/ X" I: J1 K& {6 } 3.1.0 0 B) c6 y+ C+ y- h1 y+ {; S) H. R3 X5 x5 B4 T9 w4 ?7 v" o 5 m% C4 x1 x/ M4 V. r- I $ J5 X( r: f* e T8 n. o, l7 h% U% x) f3 c9 O! z3 B org.springframework.boot ' W @- j2 o3 M8 Y ~- ] spring-boot-starter-aop+ @ J u s. Q M+ [. n; H) k3 ^ e( g* O8 e6 S) Y) F' k5 A1 y0 n8 ]/ S $ T& w9 `3 c4 ^ org.springframework.boot 5 v: m- r$ b, X* H( I spring-boot-starter-data-redis-reactive % F9 G3 L( P) \) i+ ~( A( ^: `$ C' S, _ \3 p( s! u4 | 2 e3 K0 U$ p5 Q) L. x- m3 \+ { 9 Z, T9 a; u8 F# R! | org.apache.shiro - ? U& S/ _. w. o) \ shiro-spring) ^* f* p# o) j2 C& x/ _5 t ${shiro-spring.version}+ g1 W* Q0 C( C* f9 L/ Y ! h& _% L" \: \" y: D* Z * }6 f$ z( p& l2 ^- q " u% ]0 ~! [* u! i) ` org.crazycake 6 x7 a& C6 ]+ V2 S' O1 Z) m shiro-redis 8 R$ L& J& y* Y, _2 |0 x! M ${shiro-redis.version} ; H. I3 G; y8 K1 u- {# h# h4 o/ r& [; [/ x, F 6 {5 ~# r" ?! u- ]1 [- P: K5 O

$ ~. q. f" x b! ?- g0 O

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

n5 M" x" d4 s0 \

@Slf4j ! {$ R( n0 |$ X9 ^4 u public class ShiroRealm extends AuthorizingRealm { w% q' g8 |3 o+ k8 L% H# ~5 ] @Autowired5 Z$ z5 N' U* u J2 S& X private UserMapper userMapper; 9 j$ e0 q# s. m' q @Autowired + ?; Y) m+ r+ l private MenuMapper menuMapper; 7 k: W. D1 u) m% _) H8 F( Z* ~ @Autowired2 I7 v$ s: n( _( B) D' B% D private RoleMapper roleMapper; 4 F) m) u( r9 y% @+ { @Override& y/ r' ?) }* q" r public String getName() { ' p4 Q* C& j; `; W, V, b return "shiroRealm";4 w+ G8 F4 p6 v8 m } 7 Q' V& ^- R2 g /** ( F- U% p4 z* A( \. _' U1 g4 M4 Y * 赋予角色和权限:用户进行权限验证时 Shiro会去缓存中找,如果查不到数据,会执行这个方法去查权限,并放入缓存中* Q+ k2 A+ B L7 a: i */% d2 y3 r9 l+ U* w6 R @Override' o; o3 x' I7 [* _# M, @' D protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {7 n( k6 I l6 H SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); 7 \ x2 X! A8 s' v, ] // 获取用户6 H: ?# I+ ]. r9 f User user = (User) principalCollection.getPrimaryPrincipal(); 2 ]+ M/ k7 b' F/ Y Integer userId =user.getId(); 5 s( l7 Y. U' e0 ^% x$ H" o1 A* I // 这里可以进行授权和处理 ( T) ~2 Z6 O' a5 y K6 k- R; R( ` Set rolesSet = new HashSet<>();' g8 T) R/ z# O6 D Set permsSet = new HashSet<>();- U& \+ E# f' U0 ` // 获取当前用户对应的权限(这里根据业务自行查询) & G! Z) c* n! ?7 _; j( } ^ List roleList = roleMapper.selectRoleByUserId( userId ); 1 E# J4 V! E. N8 o for (Role role:roleList) { ( k/ Y( T8 x4 q9 k+ f5 Z, T& N! n+ L rolesSet.add( role.getCode() ); " |) G/ C9 {/ i, B% L# ^5 S List menuList = menuMapper.selectMenuByRoleId( role.getId() ); b7 o* n4 F; _& t7 ^$ L for (Menu menu :menuList) { * [6 q- {* N- l5 r( c! x! G/ D permsSet.add( menu.getResources() ); ! a4 x; e5 [- V! ` }- k( B1 b2 ]0 @ y2 V } 6 k. F1 L/ _7 b9 r" e //将查到的权限和角色分别传入authorizationInfo中$ v& }* s0 V2 H- T- L9 u' N; h authorizationInfo.setStringPermissions(permsSet);" W: D, m& c# `4 E* D% v authorizationInfo.setRoles(rolesSet); / ]$ ]& p, X% Z+ ]7 ` log.info("--------------- 赋予角色和权限成功! ---------------"); % C$ ], z5 O* ]0 g- K return authorizationInfo;3 U) n: B/ d/ N2 z }% b8 Y: j* v+ A: t) G; o. `# D /** 9 B6 j' R+ p: U' t * 身份认证 - 之后走上面的 授权 4 p5 j) V/ w' w+ Z */ 0 |6 S6 l% }+ [& Q& c0 u7 [' j @Override ) l5 ?4 m6 B% I9 u protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { 6 l% e2 t; D$ L UsernamePasswordToken tokenInfo = (UsernamePasswordToken)authenticationToken; / E5 W' ?5 p- m' V( u( T // 获取用户输入的账号 0 A% }' T3 v5 a String username = tokenInfo.getUsername();0 q# | c" z* Z) Z$ m- Z // 获取用户输入的密码6 y: Y' D8 L( q+ ~4 V9 n( E4 L String password = String.valueOf( tokenInfo.getPassword() ); # P2 E2 k! [( L) O' H8 j- l7 e4 p // 通过username从数据库中查找 User对象,如果找到进行验证 1 k/ d: _" O5 j // 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 0 C% p* m( Q, G3 Z User user = userMapper.selectUserByUsername(username);& v8 L1 |) G# X: D1 [. O6 I // 判断账号是否存在8 c% y2 n2 O/ z: x# g: J4 R3 B- ` if (user == null) {& K7 c: K. ^ U: d //返回null -> shiro就会知道这是用户不存在的异常 9 P" n& ?* @6 N2 ~4 O a return null; ' x5 d. |1 l, q- v }& d1 {( _. ?5 D2 L B! c+ l9 I // 验证密码 【注:这里不采用shiro自身密码验证 , 采用的话会导致用户登录密码错误时,已登录的账号也会自动下线! 如果采用,移除下面的清除缓存到登录处 处理】 ) F# T/ D8 V; v6 R y5 M4 H if ( !password.equals( user.getPwd() ) ){ ; r, z; A6 a. p) i% ?" { throw new IncorrectCredentialsException("用户名或者密码错误");% l _9 l/ }6 H3 {4 ^2 p. U" g }' ^9 N- Z+ r. r4 v9 o6 U: h // 判断账号是否被冻结% B0 N" I h; ], J/ y if (user.getFlag()==null|| "0".equals(user.getFlag())){ {. P* _1 ^' r6 ?2 [ throw new LockedAccountException();( C; E( P/ `- \" _% A3 ? }( W3 M$ P# j8 C4 A7 y+ R- Z6 Z /** # B! B9 u, R4 j, a1 ~ D) w l6 v * 进行验证 -> 注:shiro会自动验证密码 ( W+ l. [! J) k& S2 i * 参数1:principal -> 放对象就可以在页面任意地方拿到该对象里面的值: J- s) _: h4 |1 C, W1 g; P * 参数2:hashedCredentials -> 密码 9 e8 D* s4 V( J2 f8 \ * 参数3:credentialsSalt -> 设置盐值 8 _0 i% C$ \. N9 t * 参数4:realmName -> 自定义的Realm7 b( ]5 R, }9 n, V) N5 |) |4 ?! s */2 O4 u5 g1 h) i, w0 Z! q2 Y SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());" ^4 _& X2 i' c, a; F6 X$ J/ n" f" C // 验证成功开始踢人(清除缓存和Session) _1 a0 |) D J% x- h: q# t8 w ShiroUtils.deleteCache(username,true);9 X% Q! V2 ]/ i+ B // 认证成功后更新token 7 K% f# r8 s3 ~% `7 g String token = ShiroUtils.getSession().getId().toString(); ' H4 o. g1 W" K' i- l# G* Z# M user.setToken( token );5 }( U& m0 q0 u& z# n, V userMapper.updateById(user);# H* f) q- J4 Q$ k7 j6 U: J9 Q return authenticationInfo; # a9 f/ [( }. R+ i" u }. z4 N( I# E( A: @$ I } # ^5 M" |- g8 U- ]

j8 n+ k P) Z8 ]

3、Shiro配置类@Configuration 4 z; ?1 X/ g& Z ^: T public class ShiroConfig { 9 s2 q* m8 u( J. ?3 Y private final String CACHE_KEY = "shiro:cache:"; : L2 |3 @6 u7 [ private final String SESSION_KEY = "shiro:session:"; " O- z# i. S4 l* Q /**% i2 E( Q2 `% Q& H0 a$ L0 y * 默认过期时间30分钟,即在30分钟内不进行操作则清空缓存信息,页面即会提醒重新登录 6 _/ U) D5 t: M2 u; Q- M1 [" A# j3 [ */ 6 ^! [; H( s, p- C* @ private final int EXPIRE = 1800; ' |+ P X* B; |0 x4 r# c6 G /** {9 V! W. g% A * Redis配置 : p$ X% e8 i w, s" _ */$ h3 J. ? ?9 k S @Value("${spring.redis.host}")+ k' X- w2 P+ \! ^ private String host; 6 J c0 q( c [2 Z$ k) u @Value("${spring.redis.port}")& O2 x4 W8 ?0 Z: M) }: t8 @* [ private int port; ( I6 h x6 Z3 j @Value("${spring.redis.timeout}")( F2 h8 |4 L4 v3 C+ N: p private int timeout;6 W* Z* @, R) E* F; L ] // @Value("${spring.redis.password}")- g: K2 U3 R9 K5 j5 z8 i // private String password;5 {( {5 M' k1 |5 K7 {. v' U5 T& k /** / X. L" z) P& K; A; r% _. { * 开启Shiro-aop注解支持:使用代理方式所以需要开启代码支持# Q1 k, ]2 j- d3 t" m5 T( P */* P, o: u f9 O1 g5 ^* t @Bean6 a# t; m. c. C public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {1 m* T/ f2 G8 c0 h* l AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); 5 a$ Y% ?: t6 O authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); 2 W7 W& ^# _9 o) _0 ^: ~ return authorizationAttributeSourceAdvisor; 0 ?+ K @* A5 h# \) E } 2 O" b2 o; G3 f1 w: y3 N; r /** . |6 G* P' N) h( I4 |- A- ^ * Shiro基础配置 % j: d7 ^" l4 ` */ : z. G% `3 Z7 k @Bean7 Z& o4 H5 Q \1 O, A, G' x! v6 v public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager, ShiroServiceImpl shiroConfig){ ; ]5 J4 _7 }! G2 A! Y ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); - }9 b- {$ Z2 I- G shiroFilterFactoryBean.setSecurityManager(securityManager);5 w' h: J; C+ h7 Z // 自定义过滤器" }/ M) ^/ x/ c. b; D( {9 B Map filtersMap = new LinkedHashMap<>();: R+ y! `6 J9 F7 w( \/ Y // 定义过滤器名称 【注:map里面key值对于的value要为authc才能使用自定义的过滤器】! i* d6 J$ B1 c, ? A$ l: g# y filtersMap.put( "zqPerms", new MyPermissionsAuthorizationFilter() );% A9 Q! v8 c; `/ m) x; m filtersMap.put( "zqRoles", new MyRolesAuthorizationFilter() );2 V0 Z- H5 t) p% a; J8 m( K" h8 ^ filtersMap.put( "token", new TokenCheckFilter() );6 h- W# Z" X) \9 n6 |- m; U8 ] shiroFilterFactoryBean.setFilters(filtersMap);+ b% |% D2 o2 B/ j5 Y2 r$ B6 O // 登录的路径: 如果你没有登录则会跳到这个页面中 - 如果没有设置值则会默认跳转到工程根目录下的"/login.jsp"页面 或 "/login" 映射; q% a" W. C- ` o3 u+ w8 A* K shiroFilterFactoryBean.setLoginUrl("/api/auth/unLogin");2 v U; d1 \" f // 登录成功后跳转的主页面 (这里没用,前端vue控制了跳转) 7 C; C& m& b% C0 G" v! A: m // shiroFilterFactoryBean.setSuccessUrl("/index");4 g) J4 ^6 Y7 {7 G" T3 {$ r // 设置没有权限时跳转的url $ j7 I* K) \" @* `) f shiroFilterFactoryBean.setUnauthorizedUrl("/api/auth/unauth");$ u! W/ c0 w! V# t0 x" a shiroFilterFactoryBean.setFilterChainDefinitionMap( shiroConfig.loadFilterChainDefinitionMap() ); ) T y. q* m6 N$ @3 ` return shiroFilterFactoryBean; - W; R# ^' o7 |! h }: D; \" m7 y# ^8 g% X* K /** 9 X$ e. q7 Z1 P" k" { * 安全管理器 + \7 `, R2 N" I */ ' q# o8 `/ e( _- D @Bean% o# K: t- u4 F m' @( e5 g9 s public SecurityManager securityManager() { . E: l: Q/ z# \/ O f7 }4 w F' Z DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();! H/ \7 ~( `* W // 自定义session管理 9 [* X8 m9 k) L- l2 P% `9 Q& W securityManager.setSessionManager(sessionManager());; s8 {2 k1 @* X) t* s+ f& m // 自定义Cache实现缓存管理' t* Y x8 v8 W6 r1 H securityManager.setCacheManager(cacheManager());# X1 y; R" H2 d& A2 Q5 ?- Y // 自定义Realm验证 3 _. O: K% I, ^" n& G' b securityManager.setRealm(shiroRealm()); % X, Q- O6 s! m# \5 j return securityManager; * ], J/ p9 b" G3 l% ^2 @2 ] }! f- H9 {5 T" s( X9 S2 {& c3 S /**9 x8 q9 H9 a, |& z# G * 身份验证器 3 ?2 Q$ k& N; k, l. D: y */ |) y9 w; D9 Y# I/ B/ I8 B @Bean3 c' N6 u: l9 ]$ b public ShiroRealm shiroRealm() { ( o1 Q' m& u/ l1 G; F$ j* X ShiroRealm shiroRealm = new ShiroRealm(); 4 c; Z- r. B: l# T shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); $ }$ n2 p* X6 k( C4 B+ q+ K5 Z4 b return shiroRealm; 6 k U/ c$ O" [8 ^ }" e- H& |% B& P0 ] /**, p' S6 h/ R/ b2 ? * 自定义Realm的加密规则 -> 凭证匹配器:将密码校验交给Shiro的SimpleAuthenticationInfo进行处理,在这里做匹配配置 " w( d0 d9 F3 ?$ r$ d0 B Y- t0 D */) {* P" T# f( |0 B# ~7 @; p3 l @Bean & t7 _$ ?1 ?6 b% \ public HashedCredentialsMatcher hashedCredentialsMatcher() {8 a* F+ R7 ?5 q6 g HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher(); ) [4 s% U- L X5 i // 散列算法:这里使用SHA256算法;/ M! |! U5 q: ]- O4 x# L shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME); ' Q! E3 }! |/ Y // 散列的次数,比如散列两次,相当于 md5(md5("")); * H5 c4 U* g; o" x5 D* N shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS); * J' X. [8 R" x" r5 T. e' i$ R) ?5 e return shaCredentialsMatcher; ' E& f, W7 ^2 M- P2 k4 B } 7 Y# P" k6 c$ M+ G D" q' V( h /** ' K! c5 C9 r0 ~9 X * 配置Redis管理器:使用的是shiro-redis开源插件 / f, k$ L3 _6 ^2 a */+ X/ R' B3 t- k3 W$ U* E @Bean6 `5 V* R8 e/ r5 h public RedisManager redisManager() { , h. X+ T5 ^: k: G4 v" O/ d4 D RedisManager redisManager = new RedisManager();. r! F1 {$ Z6 b; @3 } redisManager.setHost(host); ! T( \$ Z8 o* x7 \9 F& d6 m redisManager.setPort(port); 0 G" u" c- ~* r* U redisManager.setTimeout(timeout); ' K1 Q: @9 k/ S/ j0 u- ~ // redisManager.setPassword(password);( \/ r, B# q! J: e9 X& `2 w return redisManager; ! L/ }. Z- ]; [1 @' W* |* f }' s+ Z; @9 F" U* S" X! ` /**6 t" U) J2 }( }- j- F! P- x * 配置Cache管理器:用于往Redis存储权限和角色标识 (使用的是shiro-redis开源插件) : j% J. D1 O5 `( k8 c( q* s. q6 ~9 J */+ f% n6 Z3 l1 L L @Bean, r9 c+ s* ~6 a+ e8 ~ public RedisCacheManager cacheManager() { ]1 s) `5 z$ w: t. x9 L( [: r/ S RedisCacheManager redisCacheManager = new RedisCacheManager(); / S! L9 x" I* s( ~6 P redisCacheManager.setRedisManager(redisManager());2 D: _5 [8 C" |; f! o3 p redisCacheManager.setKeyPrefix(CACHE_KEY);/ a% L' w6 |5 A, d; Z // 配置缓存的话要求放在session里面的实体类必须有个id标识 注:这里id为用户表中的主键,否-> 报:User must has getter for field: xx # O5 ` A$ m% M0 q* T3 K$ S- ] x k redisCacheManager.setPrincipalIdFieldName("id");. I$ n8 i1 L ] q/ V2 l% u return redisCacheManager;6 |+ Z; a1 |8 Q$ j4 { }" t' R3 M7 `# w, T6 D0 O; m% _ /**8 I0 c! h9 g9 V5 J6 j6 O * SessionID生成器 8 i6 H- t1 c, k* p! R; p; S */ ! V# o4 ]" v" f8 M: \ @Bean( W6 X! N8 S, M. Y: \% A public ShiroSessionIdGenerator sessionIdGenerator(){ % `6 K( R& _) K0 d7 o return new ShiroSessionIdGenerator(); $ C: X& Y4 Z' k2 M; K }( n' ?/ N4 X, P* h /**: d% m; t( ~, i' V * 配置RedisSessionDAO (使用的是shiro-redis开源插件) - a+ Z, m+ f! \2 |. R */+ v7 X0 E9 t# O& d" _ @Bean . \1 ?6 w2 ]6 m" p' n' Q public RedisSessionDAO redisSessionDAO() { - q$ w- L& \0 a2 } RedisSessionDAO redisSessionDAO = new RedisSessionDAO();* `( O3 ^3 N, r5 g, P7 P) L redisSessionDAO.setRedisManager(redisManager());. z9 z1 ]: `: R, n redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());# k% D6 \: M' _; _( Y2 S redisSessionDAO.setKeyPrefix(SESSION_KEY); & {# |" y) y/ \( B7 H5 W1 s redisSessionDAO.setExpire(EXPIRE);% ?0 V# v$ I+ \ return redisSessionDAO;# t c+ Q: v' ?. k, O5 N C }; _) B. i) g3 p8 X$ J /**; D0 a4 {+ y& k1 S * 配置Session管理器 + l# T, T" ^2 b8 l2 m */ 0 U( l" M; M7 W: g# m7 b" r8 _% Q @Bean $ n6 c0 _% G1 F7 E" S W public SessionManager sessionManager() { 2 {! t3 y) x4 s; G ShiroSessionManager shiroSessionManager = new ShiroSessionManager(); 4 M* R( ?* Y7 i% H- [& D shiroSessionManager.setSessionDAO(redisSessionDAO());2 w* T# A5 O0 b9 a) u, a return shiroSessionManager;: x# _; Q' N. u; p5 a }+ `* B. t( ]8 J' p4 d }% |+ K; H- M+ N

2 P, t( e2 y1 \

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

4 V7 G! m! ?3 E' J& x

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

; K, h- n1 n0 n( B8 E5 L/ [& p* P

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

) b; ]+ W7 Q" n5 X

public interface ShiroService {( I9 U# ~4 E2 S, ^. H /** + y! R" Y( T! M q# O * 初始化权限 -> 拿全部权限' Y2 \/ g) ?. M * 9 Z5 m, v. K( Y- U * @param :) I* P u! ?1 i3 m, X * @return: java.util.Map5 I1 P; s0 n7 Q) o6 [6 D */( V" [6 G( w4 _( I/ }4 V* k Map loadFilterChainDefinitionMap();5 Q6 Z3 l) e- T: x9 g' \' ^6 f/ g /**3 t D \ d8 |4 }0 ^ * 在对uri权限进行增删改操作时,需要调用此方法进行动态刷新加载数据库中的uri权限! m' a; @8 s* U/ n' F& n * % Z/ |+ \+ H1 N- e* T: @; V4 x# e * @param shiroFilterFactoryBean' I/ M' V' G4 o Z0 U6 g * @param roleId5 s( h9 m1 ^$ \/ ?. X * @param isRemoveSession:% U1 L! ^/ V7 {: N * @return: void3 m1 {* k. x6 E, O% W */) t& @! D/ N$ J' J# R void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean, Integer roleId, Boolean isRemoveSession);- A8 z" C( p# E6 c* @0 p( u3 z /** 2 ]$ c5 q; I1 ^ R * shiro动态权限加载 -> 原理:删除shiro缓存,重新执行doGetAuthorizationInfo方法授权角色和权限 , [- P5 a7 r) }' ~% [ _2 G *1 {. [, |8 V. ], R4 O * @param roleId9 {8 K! a9 H' f, a" E * @param isRemoveSession: 0 k; o* h1 G2 f2 y" w' a" x * @return: void ( @2 y0 J- m! j) R0 k *// _8 W, H4 |+ o) ~! x( s void updatePermissionByRoleId(Integer roleId, Boolean isRemoveSession); 7 v% g" z" B3 u8 A. |% Z { } : d/ j4 G8 r: x* w- |6 |5 s @Slf4j+ @( I {, a' B( l( E: u- I( I; a! ^( v @Service; j* f/ { ]! x$ B public class ShiroServiceImpl implements ShiroService { 5 J: C2 L- m+ m) J n- b( F @Autowired - |$ j4 e+ m/ h: A8 m+ A7 M: p private MenuMapper menuMapper; 2 L; d; s0 Q g. Z0 T0 ^8 | @Autowired 7 b% X7 v2 S( }' i3 I G private UserMapper userMapper;- e0 ^4 G, r0 k6 K4 @) M% V% r @Autowired7 n) r% c, g6 J; X# D( w private RoleMapper roleMapper;. V6 w4 V1 Z* ?* Z @Override. o% _3 _1 a. T @ public Map loadFilterChainDefinitionMap() { : {" e: {% M" ] // 权限控制map% _3 W! \' A0 r# ^, \8 ~ Map filterChainDefinitionMap = new LinkedHashMap<>(); ) D- v. p! _( {- [7 }8 C5 u! a% z7 s // 配置过滤:不会被拦截的链接 -> 放行 start ----------------------------------------------------------5 S8 ?1 ?0 _) ^' W# X" F. J( f // 放行Swagger2页面,需要放行这些 & S: Y! P% d: N8 M filterChainDefinitionMap.put("/swagger-ui.html","anon"); 2 f) { u+ D' t1 f filterChainDefinitionMap.put("/swagger/**","anon");! @% B, D* Y+ l4 s filterChainDefinitionMap.put("/webjars/**", "anon"); ) o" F8 R/ e# C2 {1 U filterChainDefinitionMap.put("/swagger-resources/**","anon");8 T' ]- K0 r: W filterChainDefinitionMap.put("/v2/**","anon");* h: i$ }0 W, }: L s7 x' g. ~ filterChainDefinitionMap.put("/static/**", "anon"); ( }% y% l* A5 {; D2 [ // 登陆5 g; v2 n {; p" j5 |. _7 k& p; v filterChainDefinitionMap.put("/api/auth/login/**", "anon");* m9 p3 M) S* q5 f+ t" V // 三方登录 3 S: y2 l/ U" L1 Q, G, g4 f' \ V filterChainDefinitionMap.put("/api/auth/loginByQQ", "anon");+ h! D7 p$ C5 Q) c+ V filterChainDefinitionMap.put("/api/auth/afterlogin.do", "anon"); 5 y0 s) c" s9 H8 ^+ N# W5 R' D // 退出 ) z$ }7 M" L- A% b filterChainDefinitionMap.put("/api/auth/logout", "anon");9 R [* y. z- ^ // 放行未授权接口,重定向使用 4 c3 L3 i. Z1 P, w filterChainDefinitionMap.put("/api/auth/unauth", "anon"); ! D9 y+ |+ l/ C: |) z( j // token过期接口 * Y; ^1 ^, ^4 V: Q N! Y1 j6 \% | filterChainDefinitionMap.put("/api/auth/tokenExpired", "anon"); $ J j1 H1 A# }& D5 j: P3 g // 被挤下线: a9 A z! N7 v2 b filterChainDefinitionMap.put("/api/auth/downline", "anon"); ( T( I. Y8 a" S; z- J( P // 放行 end ---------------------------------------------------------- 9 Q% h, g0 _! {- r4 m' X // 从数据库或缓存中查取出来的url与resources对应则不会被拦截 放行 3 R+ \5 x7 p- ^ M List permissionList = menuMapper.selectList( null ); 1 E' E4 J2 o) a* C" ~" _9 V" O if ( !CollectionUtils.isEmpty( permissionList ) ) {1 T$ b5 J( w5 M6 c8 N6 ^. T permissionList.forEach( e -> { / f: ~9 |/ b R0 W if ( StringUtils.isNotBlank( e.getUrl() ) ) {) [8 ~7 N: Q! ^- O( [ // 根据url查询相关联的角色名,拼接自定义的角色权限 4 e" j% {+ _* w: ^ List roleList = roleMapper.selectRoleByMenuId( e.getId() );# c" C3 K* a- m+ O0 J) s StringJoiner zqRoles = new StringJoiner(",", "zqRoles[", "]");$ L0 C8 V6 R# l# ~ if ( !CollectionUtils.isEmpty( roleList ) ){ 2 l8 E6 k2 @6 S4 F! r0 D' w# y roleList.forEach( f -> { * `* \. V& S" Q6 C, g/ i zqRoles.add( f.getCode() ); Q% z- W, ]- x- o3 I3 K }); 3 A& h( i: @2 b/ P( t1 o } 1 ^, f Q7 B8 }1 j$ W! d // 注意过滤器配置顺序不能颠倒 0 \, o- {* S6 c. I: U( X! g# A* I // ① 认证登录 7 x6 ~9 Q! y8 N. Q. C // ② 认证自定义的token过滤器 - 判断token是否有效1 U( i: H# l6 o" D# e // ③ 角色权限 zqRoles:自定义的只需要满足其中一个角色即可访问 ; roles[admin,guest] : 默认需要每个参数满足才算通过,相当于hasAllRoles()方法 1 z1 r0 Z' Z4 w" g // ④ zqPerms:认证自定义的url过滤器拦截权限 【注:多个过滤器用 , 分割】% V W, X, I5 k0 H // filterChainDefinitionMap.put( "/api" + e.getUrl(),"authc,token,roles[admin,guest],zqPerms[" + e.getResources() + "]" ); * {; x% G5 R8 e5 Z4 q. B+ j. |0 l2 e. r filterChainDefinitionMap.put( "/api" + e.getUrl(),"authc,token,"+ zqRoles.toString() +",zqPerms[" + e.getResources() + "]" ); 7 k* ^: @. n, o- `. x4 i0 F // filterChainDefinitionMap.put("/api/system/user/listPage", "authc,token,zqPerms[user1]"); // 写死的一种用法! v' W& }5 n/ Y3 A* ?- t }6 F( E# |0 K( `5 p6 }& u ? });9 |( X7 r$ H6 R0 O; Z } 5 ?$ I2 @" Y' a+ r3 g // ⑤ 认证登录 【注:map不能存放相同key】 a" O) i n k0 l7 A8 m8 ?# R filterChainDefinitionMap.put("/**", "authc");2 c/ @- M$ H1 \) D return filterChainDefinitionMap; # R0 l% K; L. H* G }. _9 n; U2 a9 o9 j! B9 k M5 [ @Override 9 l: a, G" S0 C! J6 M2 |, K1 U( \ public void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean, Integer roleId, Boolean isRemoveSession) {; H! n6 N) q4 k0 c4 @ synchronized (this) {4 S' j$ F" K% G$ j AbstractShiroFilter shiroFilter;. q9 Y) b% A3 C8 e1 ^6 P try { 3 I, H; Y }3 a+ s) M shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject(); + P" Z1 k& X M3 k } catch (Exception e) { : E, h' a" Z8 j+ q throw new MyException("get ShiroFilter from shiroFilterFactoryBean error!");- V5 d, }$ O u. \ }2 D4 ~# d \# w' H0 @6 ?% q7 k PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();7 _/ g9 _+ N( y' |6 k DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();; a+ g1 E! s, ` // 清空拦截管理器中的存储 7 D# ^0 e1 M; u7 r" c; q# G4 l6 x* f manager.getFilterChains().clear();* d* x/ S" @8 n: H1 G; _2 f // 清空拦截工厂中的存储,如果不清空这里,还会把之前的带进去3 g* ~5 h, I1 h5 y! k* U& p; A // ps:如果仅仅是更新的话,可以根据这里的 map 遍历数据修改,重新整理好权限再一起添加& v ^' m. j% E ^2 V3 e1 f shiroFilterFactoryBean.getFilterChainDefinitionMap().clear(); ) t9 U9 g7 B# n) P5 q9 C! P3 F' B. O // 动态查询数据库中所有权限! C, _' b, ?! Z9 W5 O shiroFilterFactoryBean.setFilterChainDefinitionMap(loadFilterChainDefinitionMap()); ( _7 d+ j7 h# l! `, e6 G3 ^1 A // 重新构建生成拦截 $ v5 |5 A8 Q7 O Map chains = shiroFilterFactoryBean.getFilterChainDefinitionMap();& }( A" G& J0 a1 @/ c5 Y. M( C for (Map.Entry entry : chains.entrySet()) {+ L$ W- B. E$ ?* d5 J manager.createChain(entry.getKey(), entry.getValue());- ^- t2 h' K# {/ d" I1 n! z }+ F/ f5 ~" K4 D T# B. C. p log.info("--------------- 动态生成url权限成功! ---------------");. ]2 {* ?9 R& Z2 ^0 R Y- J // 动态更新该角色相关联的用户shiro权限' u, [0 o2 \0 K; d8 O {) V if(roleId != null){7 O+ e7 S- M$ k7 e8 s+ h/ E0 ]" ^# J updatePermissionByRoleId(roleId,isRemoveSession); 7 I: M5 R' k2 f$ v. N& q3 {- A }: v y: S7 _3 g* K! [1 |+ g }4 `7 I5 t0 N' S$ f x }! V }) X, @' I2 l% C$ S6 J @Override& p1 L! [+ A9 M, j6 x public void updatePermissionByRoleId(Integer roleId, Boolean isRemoveSession) {0 { x2 H9 {: V- }. r4 L // 查询当前角色的用户shiro缓存信息 -> 实现动态权限 . x1 k& i6 A) W X( k/ b List userList = userMapper.selectUserByRoleId(roleId); ) [6 p8 u/ M4 `6 z! w4 k. ` // 删除当前角色关联的用户缓存信息,用户再次访问接口时会重新授权 ; isRemoveSession为true时删除Session -> 即强制用户退出2 U: u* a9 T5 ~% ~) \ |% n& Q- A if ( !CollectionUtils.isEmpty( userList ) ) { * m4 |) T" S3 z# o$ o+ ?. A4 B for (User user : userList) {8 ^/ `5 Y1 t+ x% b0 B) }# g ShiroUtils.deleteCache(user.getUsername(), isRemoveSession);4 Y* w- e) V5 s: B b } 3 I# K) f$ i a+ g' X8 w }- A4 U$ Y. z* K" }, J# z log.info("--------------- 动态修改用户权限成功! ---------------"); # o. P3 b9 ]7 L! ^! {6 D } $ ? Z1 |; _' C9 } H } ~2 i" {, m/ N6 ?" f5 C

! w3 e8 f: n" y5 b

四、shiro中自定义角色、权限过滤器1、自定义uri权限过滤器 zqPerms@Slf4j ! a0 I e+ J' v5 J+ @' O* T7 Z public class MyPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {; R9 O; n! [! S8 W: q @Override: ~% i7 g5 w% ^+ O! F protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { 8 N) ~7 J4 A2 S3 r, C, ~ HttpServletRequest httpRequest = (HttpServletRequest) request;0 A ]/ C% B2 o T' l HttpServletResponse httpResponse = (HttpServletResponse) response; ) M B1 _& R- w* [/ z String requestUrl = httpRequest.getServletPath();" f2 D0 G+ I: W1 Y! W7 G log.info("请求的url: " + requestUrl); $ k1 Q5 V4 y! b$ d // 检查是否拥有访问权限% d3 Q4 Y3 u9 Z3 C) E$ I Subject subject = this.getSubject(request, response); M+ D! N$ H( {9 s9 ]. X4 F, T3 A if (subject.getPrincipal() == null) { , e Y9 Z5 u! B% j5 i this.saveRequestAndRedirectToLogin(request, response); 9 I% s3 W! { `0 ~: R: V } else { 6 O$ ~ @ C) o# b4 n7 a/ G1 N // 转换成http的请求和响应/ ?* |, F1 A& ^; }! M HttpServletRequest req = (HttpServletRequest) request; 9 P# a5 C7 w' H HttpServletResponse resp = (HttpServletResponse) response;7 |& x2 ^( @' Z1 I" T // 获取请求头的值 / Q" ?- \, s+ ~2 r3 P: ]9 D+ k String header = req.getHeader("X-Requested-With");) `- f% M; |. P: C( f( } // ajax 的请求头里有X-Requested-With: XMLHttpRequest 正常请求没有) J/ P; z" @( [: F3 P* K4 O/ T7 W if (header!=null && "XMLHttpRequest".equals(header)){ , x8 y. j H, A resp.setContentType("text/json,charset=UTF-8"); 8 D# P3 s( y" c5 |- W resp.getWriter().print("{\"success\":false,\"msg\":\"没有权限操作!\"}");+ F: a& n- u* [% b7 x7 o }else { //正常请求. v; a5 S( ]3 G) q String unauthorizedUrl = this.getUnauthorizedUrl();) P+ ]2 j' p6 v+ M6 L& E if (StringUtils.hasText(unauthorizedUrl)) { / C3 r3 q: J0 T3 K, H# g WebUtils.issueRedirect(request, response, unauthorizedUrl); ( \# g: A Q3 i! W# q } else { ; u8 O! h# t1 k5 ?* H4 E, L1 i% g0 t WebUtils.toHttp(response).sendError(401);3 V# B. }! [) R/ s9 N* ~$ e }: B$ r/ M* i) S- P/ \" M$ {" ~5 ? }* y! E" W% K& z }: }; c0 V$ W3 [. D) k7 ?" M. h return false;( p, f7 n, z$ d* w4 D }. g+ [ V+ O0 ]" ~9 l& R x7 o' T" ]* z8 j3 o* q }7 W9 v8 m- n6 p3 f$ v

) o" D( n; X3 `; ?# ?0 n

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

. S1 P" ` Z n) Q

ex: zqRoles[admin,guest]public class MyRolesAuthorizationFilter extends AuthorizationFilter {. T- H, K; s3 N8 Y/ ~' F @Override; y0 x. d/ q3 Z& }; k; a7 e protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) throws Exception { + o: p* ?; x+ K, J- f* w$ a Subject subject = getSubject(req, resp); ) j! a5 U1 w$ H) Z- s8 ~- p L! ^ String[] rolesArray = (String[]) mappedValue; + v5 _0 x7 B4 u6 W& m4 N" Z& T5 O // 没有角色限制,有权限访问- ^: x/ Y& ?4 x d. z1 D( k) z. N- C if (rolesArray == null || rolesArray.length == 0) { 3 s6 G6 A0 c- c7 _6 Q' ` return true; % X) J6 c) A3 e }; E' t& `0 ?& S; v# { Z for (int i = 0; i < rolesArray.length; i++) { 2 K/ [$ H% W# b% r# e& y0 ` //若当前用户是rolesArray中的任何一个,则有权限访问 & x( M) M5 @0 W+ |2 F4 G if (subject.hasRole(rolesArray[i])) { $ W5 A( ~3 M$ Q' M% m x' _ return true;; M( y# g7 m; s" x J }6 q$ i- I3 K# i! Y$ O# } } 8 a4 L1 B: ^# M, r- Y return false; 1 G. i- M7 |# M& g7 K' D. N } 8 u3 N3 H9 E, v5 O! Q } 8 q9 Y7 n, B* e0 _* L. ]4 y/ A3 o* G

# {. ^) P6 v; l$ m* V2 f& t

3、自定义token过滤器 token -> 判断token是否过期失效等@Slf4j ) G* M# d; s% p0 E- a. s v; d public class TokenCheckFilter extends UserFilter {' t2 Z( {5 r- h* W0 |: u /** 6 }4 c& j' U! @ * token过期、失效$ \: _+ X8 B/ A- ^& \2 C3 H! o */, f) G! E" S2 Z% t: i3 u+ b. Z8 w" q private static final String TOKEN_EXPIRED_URL = "/api/auth/tokenExpired"; 4 [7 F: F! p9 i# P7 x \% T /** : @+ D* T+ z) Z; K! P * 判断是否拥有权限 true:认证成功 false:认证失败) b$ x2 _+ ?+ y" i/ i * mappedValue 访问该url时需要的权限6 E+ a! X# A3 V9 F& z * subject.isPermitted 判断访问的用户是否拥有mappedValue权限 6 T9 ?2 l5 n7 C1 J$ s */- z4 n2 x6 v. q" @8 V" t @Override2 y' {& n2 K+ y7 w public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {4 y" T; H; y% N( z+ g& Q; s HttpServletRequest httpRequest = (HttpServletRequest) request;4 l- k |6 ~$ i HttpServletResponse httpResponse = (HttpServletResponse) response; * t# s7 K! L* S: P' }, i6 C // 根据请求头拿到token $ j6 M6 O% {! e2 E- U& e v9 l5 m String token = WebUtils.toHttp(request).getHeader(Constants.REQUEST_HEADER); 1 E/ ^' O! ]! u: x8 j log.info("浏览器token:" + token );' M. j+ q* y7 ?; V User userInfo = ShiroUtils.getUserInfo();$ W c3 r/ O# g' n/ t0 e/ [" ` String userToken = userInfo.getToken();- E) n! d/ |5 q7 ^3 p' R" h, V // 检查token是否过期 1 c! f9 b5 p' C T3 m, K if ( !token.equals(userToken) ){6 d$ g/ b# ]3 O8 M" | return false; 4 ]" N. |% b5 K } ' `$ T9 H1 j$ Y0 @& E return true;( A) l1 N( W) j( o6 B }& v3 M( e8 V4 h+ F9 { /** , `( [1 I+ }0 X! V2 i+ ` * 认证失败回调的方法: 如果登录实体为null就保存请求和跳转登录页面,否则就跳转无权限配置页面 2 {% k2 q6 o6 X */1 P) N$ E% K+ P4 c( q- @# P @Override& h Z2 D' s0 }- u, f protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { ( i- ?% j+ t, N6 A# O. c User userInfo = ShiroUtils.getUserInfo(); J9 m8 i/ Q+ ^8 y // 重定向错误提示处理 - 前后端分离情况下 ' Z( W2 N6 e3 I4 ~4 f+ N4 l, X& r WebUtils.issueRedirect(request, response, TOKEN_EXPIRED_URL); 2 G* R2 { n- x1 Z1 P- k return false;9 _" y9 x6 D1 y3 m+ _/ R } + J+ h1 J$ H. j: M+ e5 g( n }7 X2 b# o9 i6 l

2 E) S6 R0 t' |# F

五、项目中会用到的一些工具类、常量等温馨小提示:这里只是部分,详情可参考文章末尾给出的案例demo源码1、Shiro工具类public class ShiroUtils { 8 q/ ]' [% ^' i; K /** 私有构造器 **/$ F$ t z, H: q: \3 J! O7 Z private ShiroUtils(){ } 3 A6 B/ {; @# Z; x' Q7 h: u private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);3 u: j+ Z5 L. _ /**! G1 @0 c8 k4 Z1 d( n * 获取当前用户Session" i2 {2 l& b4 P0 G * @Return SysUserEntity 用户信息1 H4 g9 d; a+ [9 f8 ` */ 0 O W: k+ n* R' `3 @. u3 D2 v n* w public static Session getSession() { 2 d& T1 o& F" a9 }0 |" P5 A. B1 F( K9 F return SecurityUtils.getSubject().getSession();6 M5 V+ u2 {4 w+ \5 z }3 W% e8 d- p: _. Q5 I" t5 w4 h /**, p4 R6 n1 ^ @( G# F- N+ j * 用户登出. G3 Y5 D' W/ o% K */8 K @# S# ^* ]6 e: ^8 l$ T$ [ public static void logout() {% ^9 x! j0 x' u; s, S% l SecurityUtils.getSubject().logout(); ; G$ \% r1 b: U" ]. K: j } - k8 T' e) X% p /** L( F9 Z* b' O' [$ N; S; L0 B * 获取当前用户信息' M% b/ Q) |4 f/ T6 ~2 d" s+ w * @Return SysUserEntity 用户信息 , {( P' o: Z- v- X+ n3 l */& d5 k5 F8 k% R8 k8 H- _ public static User getUserInfo() {6 ^6 D( v a$ D5 O7 r" k return (User) SecurityUtils.getSubject().getPrincipal(); $ h& B& X! J8 J6 q } " o5 c# A/ z# e$ [2 _; f/ d/ B /**% ~3 z0 t6 f) } * 删除用户缓存信息 0 ]8 m8 P5 i! i * @Param username 用户名称 * ~+ |% g& N3 K! z. k* R * @Param isRemoveSession 是否删除Session,删除后用户需重新登录 ( r5 L& H9 \7 j* K: n */ 7 R3 k) j, J( Q, p' Q; C% Q0 E public static void deleteCache(String username, boolean isRemoveSession){8 B- F) n8 s( ~6 I, [3 S! o //从缓存中获取Session3 X. ^4 @& ^* z7 K/ x- \ Session session = null;/ f6 Q6 n+ O; o' q% m5 i9 S/ I2 ` // 获取当前已登录的用户session列表3 b, O( A* R' ~- \, N2 f+ C& ~ Collection sessions = redisSessionDAO.getActiveSessions(); " ~" O' D @( F5 l6 G3 w User sysUserEntity; ' _5 O3 C: d7 h Object attribute = null; # n; ~ o' ~1 y // 遍历Session,找到该用户名称对应的Session! z9 Q) i8 a0 q) ^5 Z& O for(Session sessionInfo : sessions){6 U3 J1 I) q$ X+ ?( { attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);4 a" g _, ~' w if (attribute == null) {3 }! X$ s" @8 Q4 i continue;3 Q/ q" A d7 s1 f9 I) c6 x } , R3 B' D( I+ X* [' v sysUserEntity = (User) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal(); 7 x$ Y* N, n7 I* N# e if (sysUserEntity == null) {, ~: l8 U8 `2 m+ z3 O continue;% ?" ^% G" N! U% ^; u" W+ I }" b+ B% v% K* a if (Objects.equals(sysUserEntity.getUsername(), username)) {% B1 `7 o2 c" U+ m$ U session=sessionInfo; 7 R* J5 s9 p/ t# S. p& B // 清除该用户以前登录时保存的session,强制退出 -> 单用户登录处理3 _9 V9 {- e! ?+ c if (isRemoveSession) {* p* H" R. u! J& R, \$ e5 }2 h redisSessionDAO.delete(session); * v+ m9 D+ T' P! y1 N* e }" f- k1 P' o* x" h } c$ \% O5 |- y1 p2 Z+ N5 o } " U) D, a% z/ w/ A# m7 x, J7 T if (session == null||attribute == null) { 1 [+ G; V) a0 ~: \. M5 H: W: F return; 9 s: B+ K. D9 N- J }! {+ O3 ~! Z$ L6 q //删除session 8 C( P# `9 } K6 x3 C g; F if (isRemoveSession) {7 j8 X8 N* M5 a. B) j redisSessionDAO.delete(session);0 i0 C" Z1 f# n$ s9 e$ [ }, q+ p8 Q8 ]; f& i! O3 x) N //删除Cache,再访问受限接口时会重新授权$ v( z+ z$ Q$ _4 S DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();8 k: l: D6 f7 Q1 U Authenticator authc = securityManager.getAuthenticator(); ! m& M) `* s8 g+ q ((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);7 _& x/ {4 o3 n) q, b h# d } $ W! L7 o$ m; C8 R3 u /**7 d3 N2 ^$ h1 f$ \5 E0 ^ * 从缓存中获取指定用户名的Session* M& J" U* d. S * @param username + |) C; }, R5 p. q+ X# ?) p */ % Y1 ` {. |. _0 \ private static Session getSessionByUsername(String username){6 g& a* T3 @* q6 b // 获取当前已登录的用户session列表 ; [. l: e* V9 f9 R Collection sessions = redisSessionDAO.getActiveSessions(); H% z3 Q8 P4 Y/ i, ]) x1 M( ]6 Q User user; 6 ?3 A; F+ b* V, x" _& g; N" z0 t. E Object attribute; $ E5 D( D9 l1 U& x! A // 遍历Session,找到该用户名称对应的Session " d/ \' e1 a8 D for(Session session : sessions){0 R, O$ E" ?# ]1 p- c# o. s attribute = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);5 R- V5 j' R: o6 L6 G, d if (attribute == null) { 7 t9 ^6 C: W, B$ ?+ f continue; 8 t7 c& W3 Z. z" h0 T: D } 3 H- w- F) L4 p- Q' s user = (User) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();: h9 m8 f" n$ |+ z( s0 c if (user == null) {& i: ~5 H! E' U+ K8 Q0 { continue;) e, r# x/ t# O6 w7 { } 2 w C, T6 F) v- m: P7 B' ~, _ if (Objects.equals(user.getUsername(), username)) { ) e* \# e/ L8 o" j, O return session;" {2 t0 `( u( l$ `0 O4 h }" I) K8 v' x. r8 [9 v5 | }" J' ?( S* Q, N return null;* K; T* y4 m! [ }* |3 E& K" X* l+ i& R- P$ {7 @ }& P$ v, C3 m2 Y8 G

6 n. W" e8 c' W R( a( |/ Q+ u

2、Redis常量类public interface RedisConstant { ; ` G" G' ^, n4 G0 m" a /** ! t# z4 ]8 _/ v7 Q6 |. c, {( H" j * TOKEN前缀 ! c: @: h# |& U */0 I( t+ T6 z* g j8 Q. O, K String REDIS_PREFIX_LOGIN = "code-generator_token_%s"; % O9 t: t( T3 j5 t) C' } } 5 P2 _ d! r7 {- M. \) z% {

, x! i3 G+ l& S1 A$ N) {3 o2 p

3、Spring上下文工具类@Component( @4 J5 ^+ T; w, k3 z+ \ public class SpringUtil implements ApplicationContextAware {) a. U: r9 ^3 \ h7 o. C private static ApplicationContext context; 5 U' S, X' a) o' n! C# { /** 7 [7 r- F% B, G9 m1 S( q * Spring在bean初始化后会判断是不是ApplicationContextAware的子类; p9 \9 a& {+ c! ~8 c+ K5 f% k * 如果该类是,setApplicationContext()方法,会将容器中ApplicationContext作为参数传入进去 ' T) M/ A9 [! J( F3 V */ ; S) y; C1 c. R4 y7 F @Override; @! u( g- M; |2 l public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {; g7 h# H% g+ y/ }9 r context = applicationContext; : \8 S1 `0 z" F }+ q( i8 S# f0 l- F1 g7 E /** 1 E9 I! K$ n4 {" W& ]7 c * 通过Name返回指定的Bean # O% B5 l/ H7 Z0 I */ 4 b( O, e3 }6 X public static T getBean(Class beanClass) {1 p/ S [* {. [4 n; ^$ E return context.getBean(beanClass); 8 Z8 @$ H. l- ?1 c S. r2 b: ~( V }% ~. B7 _9 L& b }$ D- F9 [2 q4 N& V: c3 A/ |

6 g3 s' H- W6 Q) ^5 S+ i

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

4 b) i$ W# S+ q/ o9 s

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

% v z. n0 o9 W( s8 E) c5 G o/ b/ _' Y7 [ 7 N6 G+ c! ^+ c$ h: E- p }/ [$ Y* V- b; }. ~# m/ y ) N# U% T' ]3 ^ E/ M1 E4 h

暂时无法加载帖子列表

回复

使用道具 举报

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

    本版积分规则

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

    GMT+8, 2026-10-1 20:14 , Processed in 0.053468 second(s), 25 queries , Gzip On.

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

    Powered by Discuz! X3.5

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