|
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. _
|