高手的存在,就是让服务10亿人的时候,你感觉只是为你一个人服务......

iBase4J 源码之Shiro用户安全校验

目录
  1. 1. 第一步:创建实体类:用户,角色,权限。
  2. 2. 第二步:创建自定义安全数据源Realm
  3. 3. 第三步:创建Spring整合Shiro配置类
  4. 4. shiro redis cache集成
    1. 4.1. 第一步:实现Shiro的Cache接口
    2. 4.2. 第二步:自定义RedisCacheManager实现CacheManager

Apache Shiro? is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.

by https://shiro.apache.org/


springboot与shiro整合教程具体参考:https://segmentfault.com/a/1190000013449167 ,送作者两个字:到位!

SpringBoot 整合Shiro 三个步骤:

第一步:创建实体类:用户,角色,权限。确定三者关系,以方便Realm的授权工作。

第二步:创建自定义安全数据源Realm:负责用户登录认证,用户操作授权。

第三步:创建Spring整合Shiro配置类:负责Shiro生命周期管理。

根据以上步骤,咱们来阅码iBase4J关于shiro的具体实现。


shiro的依赖:

1
2
3
4
5
6
7
8
9
10
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>

<properties>
<shiro.version>1.2.4</shiro.version>
</properties>

第一步:创建实体类:用户,角色,权限。

iBase4J 关于用户,角色,权限的表如下,表中各字段解释详见iBase4J-SYS-Facade项目org.ibase4j.model包下的实体类。

sys_user
Alt text

sys_user_menu
Alt text

sys_user_role
Alt text

sys_role_menu
Alt text

sys_menu
Alt text

sys_dic
Alt text


第二步:创建自定义安全数据源Realm

iBase4J 创建了Realm类,用于登录认证、用户授权、session保存操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* 权限检查类
*
*/

public class Realm extends AuthorizingRealm {
private final Logger logger = LogManager.getLogger();
@Autowired
@Qualifier("sysProvider")
protected BaseProvider provider;
@Autowired
private RedisOperationsSessionRepository sessionRepository;

// 权限-授权,身份认证通过后,再判断用户是否有权操作
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Long userId = (Long) WebUtil.getCurrentUser();
Parameter parameter = new Parameter("sysAuthorizeService", "queryPermissionByUserId", userId);
logger.info("{} execute queryPermissionByUserId start...", parameter.getNo());
List<?> list = provider.execute(parameter).getResultList();
logger.info("{} execute queryPermissionByUserId end.", parameter.getNo());
for (Object permission : list) {
if (StringUtils.isNotBlank((String) permission)) {
// 添加基于Permission的权限信息
info.addStringPermission((String) permission);
}
}
// 添加用户权限
info.addStringPermission("user");
return info;
}

// 登录验证-身份认证、登录-权限管理的第一个门槛
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
throws AuthenticationException {
//使用用户的登录信息创建令牌,token可以理解为用户令牌,登录的过程被抽象为Shiro验证令牌是否具有合法身份以及相关权限。
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
Map<String, Object> params = new HashMap<String, Object>();
params.put("enable", 1);
params.put("account", token.getUsername());
Parameter parameter = new Parameter("sysUserService", "queryList", params);
logger.info("{} execute sysUserService.queryList start...", parameter.getNo());
List<?> list = provider.execute(parameter).getResultList();
logger.info("{} execute sysUserService.queryList end.", parameter.getNo());
if (list.size() == 1) {
SysUser user = (SysUser) list.get(0);
StringBuilder sb = new StringBuilder(100);
for (int i = 0; i < token.getPassword().length; i++) {
sb.append(token.getPassword()[i]);
}
if (user.getPassword().equals(sb.toString())) {
WebUtil.saveCurrentUser(user.getId());
saveSession(user.getAccount(), token.getHost());
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getAccount(), user.getPassword(),
user.getUserName());

return authcInfo;
}
logger.warn("USER [{}] PASSWORD IS WRONG: {}", token.getUsername(), sb.toString());
return null;
} else {
logger.warn("No user: {}", token.getUsername());
return null;
}
}

/** 保存session */
private void saveSession(String account, String host) {
// 踢出用户
SysSession record = new SysSession();
record.setAccount(account);
Parameter parameter = new Parameter("sysSessionService", "querySessionIdByAccount", record);
logger.info("{} execute querySessionIdByAccount start...", parameter.getNo());
List<?> sessionIds = provider.execute(parameter).getResultList();
logger.info("{} execute querySessionIdByAccount end.", parameter.getNo());
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
String currentSessionId = session.getId().toString();
if (sessionIds != null) {
for (Object sessionId : sessionIds) {
record.setSessionId((String) sessionId);
parameter = new Parameter("sysSessionService", "deleteBySessionId", record);
logger.info("{} execute deleteBySessionId start...", parameter.getNo());
provider.execute(parameter);
logger.info("{} execute deleteBySessionId end.", parameter.getNo());
if (!currentSessionId.equals(sessionId)) {
sessionRepository.delete((String) sessionId);
sessionRepository.cleanupExpiredSessions();
}
}
}
// 保存用户
record.setSessionId(currentSessionId);
record.setIp(StringUtils.isBlank(host) ? session.getHost() : host);
record.setStartTime(session.getStartTimestamp());
parameter = new Parameter("sysSessionService", "update", record);
logger.info("{} execute sysSessionService.update start...", parameter.getNo());
provider.execute(parameter);
logger.info("{} execute sysSessionService.update end.", parameter.getNo());
}
}

doGetAuthorizationInfo()–负责授权,身份认证通过后,再判断用户是否有权操作。

doGetAuthenticationInfo()–负责登录验证-身份认证、登录-权限管理的第一个门槛。

saveSession()–负责保存session。


第三步:创建Spring整合Shiro配置类

iBase4J-SYS-Web项目的web.xml中配置spring跟shiro集成的过滤代理

Alt text

shiro.xml中配置securityManager和拦截规则。securityManager是shiro的核心,iBase4J是通过org.apache.shiro.spring.web.ShiroFilterFactoryBean,来创建一个org.apache.shiro.web.mgt.DefaultWebSecurityManager。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 -->
<!-- 创建安全管理器 -->
<bean id="realm" class="org.ibase4j.core.shiro.Realm" />
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 依赖自定义realms -->
<property name="realm" ref="realm" />
<!-- 依赖缓存 -->
<property name="cacheManager">
<bean class="org.ibase4j.core.support.cache.shiro.RedisCacheManager" />
</property>
</bean>
<!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager" />
<!-- 要求登录时的链接 -->
<property name="loginUrl" value="/unauthorized" />
<!-- 用户访问未对其授权的资源时,所显示的连接 -->
<property name="unauthorizedUrl" value="/forbidden" />
<!-- Shiro连接约束配置,即过滤链的定义 -->
<!-- anon:它对应的过滤器里面是空的,什么都没做 -->
<!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter -->
<property name="filterChainDefinitions">
<value>
/=anon
/app/**=anon
/index.jsp=anon
/regin=anon
/login=anon
/*.ico=anon
/upload/*=anon
/unauthorized=anon
/forbidden=anon
/sns*=anon
/*/api-docs=anon
/callback*=anon
/swagger*=anon
/configuration/*=anon
/*/configuration/*=anon
/webjars/**=anon
/**=authc,user
</value>
</property>
</bean>
<!-- Shiro生命周期处理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
</beans>


以上,iBase4J完成了shiro的集成。


shiro redis cache集成

iBase4J 中将Shiro的缓存交给redis来管理,三个步骤:

第一步:实现Shiro的Cache接口

将Shiro的缓存交给redis需要实现Shiro的Cache接口,iBase4J中RedisCache类对Cache接口进行了实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public class RedisCache<K, V> implements Cache<K, V> {
private final Logger logger = LogManager.getLogger();

/**
* The Redis key prefix for the sessions
*/

private String keyPrefix = "shiro_redis_session:";

/**
* Returns the Redis session keys
* prefix.
* @return The prefix
*/

public String getKeyPrefix() {
return keyPrefix;
}

/**
* Sets the Redis sessions key
* prefix.
* @param keyPrefix The prefix
*/

public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}

/**
* Constructs a cache instance with the specified
* Redis manager and using a custom key prefix.
* @param prefix The Redis key prefix
*/

public RedisCache(String prefix) {
// set the prefix
this.keyPrefix = prefix;
}

@Override
public V get(K key) throws CacheException {
logger.debug("根据key从Redis中获取对象 key [" + key + "]");
@SuppressWarnings("unchecked")
V value = (V)CacheUtil.getCache().get(getKey(key));
return value;
}

@Override
public V put(K key, V value) throws CacheException {
logger.debug("根据key从存储 key [" + key + "]");
CacheUtil.getCache().set(getKey(key), (Serializable)value);
return value;
}

@Override
public V remove(K key) throws CacheException {
logger.debug("从redis中删除 key [" + key + "]");
V previous = get(key);
CacheUtil.getCache().del(getKey(key));
return previous;
}

@Override
public void clear() throws CacheException {
logger.debug("从redis中删除所有元素");
CacheUtil.getCache().delAll(this.keyPrefix + "*");
}

@Override
public int size() {
return CacheUtil.getCache().getAll(this.keyPrefix + "*").size();
}

@SuppressWarnings("unchecked")
@Override
public Set<K> keys() {
Set<Object> keys = CacheUtil.getCache().getAll(this.keyPrefix + "*");
if (CollectionUtils.isEmpty(keys)) {
return Collections.emptySet();
} else {
Set<K> newKeys = new HashSet<K>();
for (Object key : keys) {
newKeys.add((K)key);
}
return newKeys;
}
}

@Override
public Collection<V> values() {
Set<Object> keys = CacheUtil.getCache().getAll(this.keyPrefix + "*");
if (!CollectionUtils.isEmpty(keys)) {
List<V> values = new ArrayList<V>(keys.size());
for (Object key : keys) {
@SuppressWarnings("unchecked")
V value = get((K)key);
if (value != null) {
values.add(value);
}
}
return Collections.unmodifiableList(values);
} else {
return Collections.emptyList();
}
}

private String getKey(K key) {
return this.keyPrefix + key;
}
}

第二步:自定义RedisCacheManager实现CacheManager

iBase4J的RedisCacheManager类对CacheManager进行实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@SuppressWarnings({ "rawtypes", "unchecked" })
public class RedisCacheManager implements CacheManager {
private final Logger logger = LogManager.getLogger();

// fast lookup by name map
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();
/**
* The Redis key prefix for caches
*/

private String keyPrefix = Constants.CACHE_NAMESPACE + "shiro_redis_cache:";

/**
* Sets the Redis sessions key prefix.
*
* @param keyPrefix
* The prefix
*/

public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}

/*
* (non-Javadoc)
*
* @see org.apache.shiro.cache.CacheManager#getCache(java.lang.String)
*/

public <K, V> Cache<K, V> getCache(String name) throws CacheException {
logger.debug("获取名称为: " + name + " 的RedisCache实例");

Cache c = caches.get(name);

if (c == null) {
// create a new cache instance
c = new RedisCache<K, V>(keyPrefix);
// add it to the cache collection
caches.put(name, c);
}
return c;
}

}

第三步:在Shiro的配置中配置cacheManager

在shiro.xml配置文件中有这样一段:

Alt text


以上,完成 shiro redis cache的集成。