[JavaWeb] Spring 集成 Apache Shiro 安全框架

Apache Shiro 简介

Apache Shiro 是 Java 的一个开源安全框架,提供身份验证、授权、密码学和会话管理。

Shiro 框架直观、易用,同时也能提供健壮的安全性。而 Spring Security 功能更强大,相对来说会比较复杂。本次选择的是 Apache Shiro,接下来开始 Shiro 安全框架之旅吧。

名词解释

  • 认证:用户身份识别,通常被称为用户“登录”
  • 授权:访问控制
  • 密码加密:保护或隐藏数据防止被偷窥
  • 会话管理:用户访问应用时保持的连接关系。这里主要指 SESSION 和 COOKIE

Apache Shiro 架构介绍

Shiro的三个核心组件
上图为 Shiro 的三个核心组件:Subject,SecurityManager 和 Realms。

  • Subject:主体,代表当前操作用户。这个“用户”,不仅仅指代人,也指代与之交互的网络爬虫等等。
  • SecurityManager:安全管理器,是 Shiro 的核心。它管理所有的 Subject,并负责与后面的组件交互。有点像 SpringMVC 中的 DispatcherServlet。
  • Realm:域,可以看成是安全数据源。Shiro 从 Realm 获取安全数据(如用户、角色、权限)。例如用户登录的时候,Shiro 会从 Realm 配置角色和权限。

Shiro 不提供维护用户或权限,而是通过 Realm 让开发人员自己注入。

补上一张 Shiro 完整架构图
Shiro完整架构图

Maven 引入 jar 包

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
<!-- shiro 安全框架-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>${shiro.version}</version>
</dependency>
<!--shiro 单点登录-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>${shiro.version}</version>
</dependency>

web.xml 配置

首先配置过滤器,让请求资源经过 Shiro 的过滤处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<!-- shiro 安全过滤器 配置在 DispatcherServlet 之前-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

spring-web.xml 配置

在 SpringMVC 中启用注解方式授权。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 配置使 Spring 采用 CGLIB 代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>

<!-- 启用shrio授权注解拦截方式 -->
<aop:config proxy-target-class="true"></aop:config>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>

<!-- shiro cacheManager 配置 ,还是没有成功 -->
<cache:annotation-driven cache-manager="cacheManager"/>
<bean id="ehCacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
p:configLocation="classpath:shiro/ehcache.xml" />

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"
p:cacheManager-ref="ehCacheManagerFactory"/>

applicationContext-shiro.xml 配置

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

<description>apache shiro配置</description>

<!--注入自己写好的 shiro Realm 组件-->
<bean id="securityRealm" class="cn.doity.security.SecurityRealm"></bean>


<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/page/login"/>
<property name="successUrl" value="/index"/>
<property name="unauthorizedUrl" value="/page/401"/>
<property name="filterChainDefinitions">
<value>
<!-- 静态资源允许访问 -->
/resources/css/** = anon
/resources/fonts/** = anon
/resources/img/** = anon
/resources/js/** = anon
/resources/plugins/** = anon
/resources/favicon.ico = anon
<!-- 登录页允许访问 -->
/user/login = anon
<!-- 其他资源需要认证 -->
<!--/** = anon-->
/** = authc
</value>
</property>
</bean>

<!-- 缓存管理器 使用Ehcache实现 -->
<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:shiro/ehcache-shiro.xml"/>
</bean>

<!-- 会话DAO -->
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.MemorySessionDAO"/>

<!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionDAO" ref="sessionDAO"/>
</bean>

<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realms">
<list>
<ref bean="securityRealm"/>
</list>
</property>
<!-- cacheManager,集合spring缓存工厂 -->
<property name="cacheManager" ref="shiroEhcacheManager" />
<!--<property name="sessionManager" ref="sessionManager" />-->
</bean>

<!-- Shiro生命周期处理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

</beans>

ehcache-shiro.xml 配置

1
2
3
4
5
6
7
8
9
10
11
12
<ehcache updateCheck="false" name="shiroCache">

<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>

</ehcache>

登录示例

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
/**
* 用户登录
* @param user
* @param result
* @param model
* @param request
* @return
*/

@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(@Valid User user, BindingResult result, Model model, HttpServletRequest request) {
try {
Subject subject = SecurityUtils.getSubject();
// 已登陆则 跳到首页
if (subject.isAuthenticated()) {
return "redirect:/";
}
if (result.hasErrors()) {
model.addAttribute("error", "参数错误!");
return "login";
}
// 身份验证
subject.login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
// 验证成功在Session中保存用户信息
final User authUserInfo = userService.selectByUsername(user.getUsername());
request.getSession().setAttribute(authUserInfo, authUserInfo);
} catch (AuthenticationException e) {
// 身份验证失败
model.addAttribute("error", "用户名或密码错误 !");
return "login";
}
return "redirect:/";
}

/**
* 用户退出登录
*/

@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpSession session) {
session.removeAttribute(authUserInfo);
// 登出操作
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}

小记

未完待续。

参考资料