Shiro整合SpringMVC之web搭建

在上一篇博客Shiro基本原理概念中讲解了shiro的基本原理,本章继续通过实例来整合spring搭建web项目。

项目框架:Spring + SpringMvc + Shiro + Mybatis + Redis

本章的源码地址:https://gitee.com/Luke-Lu/shiro

Shiro认证与授权的在Web中实现

第一步:添加依赖

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

第二步:配置web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- shiro 过滤器 start -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true由servlet容器控制filter的生命周期 -->
<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>
<!-- shiro 过滤器 end -->

第三步:自定义Realm继承AuthorizingRealm,重写AuthorizationInfo(授权)和AuthenticationInfo(认证)

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
/**
* 自定义Realm继承AuthorizingRealm 重写AuthorizationInfo(授权)和AuthenticationInfo(认证)
*
* @author Luke
* @date 2018/5/31.
*/
public class CustomRealm extends AuthorizingRealm {

@Autowired
private UserDao userDao;

Map<String, String> userMap = new HashMap<>();

{
// userMap.put("luke", "123456");
//加盐
Md5Hash md5 = new Md5Hash("123456", "luke");
userMap.put("luke", md5.toString());
}

/**
* 授权
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户名称
String username = (String) principalCollection.getPrimaryPrincipal();
//获取角色
Set<String> roles = getRolesByUserName(username);
//设置权限列表
Set<String> permissions = getPermissionsByUserName();
//权限信息对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//添加权限列表
info.setStringPermissions(permissions);
//添加权限角色
info.setRoles(roles);
return info;
}

/**
* 认证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String) authenticationToken.getPrincipal();
//若获取到用户名称为空,则未通过认证
if (StringUtils.isEmpty(username)) {
return null;
}

String password = getPasswordByUserName(username);
if (StringUtils.isEmpty(password)) {
return null;
}

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, this.getName());
//加盐,加密
info.setCredentialsSalt(ByteSource.Util.bytes(username));
return info;
}

private Set<String> getPermissionsByUserName() {
Set<String> sets = new HashSet<>();
sets.add("user:delete");
sets.add("user:add");
return sets;
}

private Set<String> getRolesByUserName(String username) {
System.out.println("从数据库中获取角色");
Set<String> sets = userDao.getRolesByUserName(username);
return sets;
}

private String getPasswordByUserName(String username) {
System.out.println("从数据库中获取密码");
User user = userDao.getPasswordByUserName(username);
if (user != null) {
return user.getPassword();
}
return null;
// return userMap.get(username);
}

public static void main(String[] args) {
Md5Hash md5Hash = new Md5Hash("123456", "luke");
System.out.println(md5Hash);
}
}

第四步:配置spring-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
64
65
66
67
68
69
<?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">

<bean id="shiroFilter"
class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"></property>
<property name="loginUrl" value="login.jsp"></property><!-- 没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面 -->
<property name="unauthorizedUrl" value="error.jsp"></property><!-- 没有权限默认跳转的页面 -->
<property name="filterChainDefinitions">
<value><!-- 自上到下 --><!-- anon:表示可以匿名使用。 authc:表示需要认证(登录)才能使用,没有参数. roles["admin,guest"],每个参数通过才算通过,user表示必须存在用户 -->
/login.jsp = anon
/subLogin = anon
/testRoles = roles["admin"]
/testRoles1 = rolesOr["admin","admin1"]
/testPerms = perms["user:delete"]
/testPerms1 = perms["user:delete","user:updata"]
/* = authc
</value>
</property>
<property name="filters">
<map>
<entry key="rolesOr" value-ref="rolesOrFilter" />
</map>
</property>
</bean>

<!-- 自定义权限filter -->
<bean id="rolesOrFilter" class="com.shiro.filter.RolesOrFilter" />

<!-- 创建SecurityManager对象 -->
<bean id="securityManager"
class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="realm" />
<property name="sessionManager" ref="sessionManager" />
<property name="cacheManager" ref="cacheManager" />
<property name="rememberMeManager" ref="cookieRememberMeManager" />
</bean>

<!-- 自定义Realm -->
<bean id="realm" class="com.shiro.realm.CustomRealm">
<property name="credentialsMatcher" ref="credentialsMatcher" ></property>
</bean>

<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5" />
<property name="hashIterations" value="1" />
</bean>
<!-- 自定义SessionManager -->
<bean class="com.shiro.session.CustomSessionManager" id="sessionManager">
<property name="sessionDAO" ref="redisSessionDao"></property>
</bean>

<bean class="com.shiro.session.RedisSessionDao" id="redisSessionDao"></bean>

<bean class="com.shiro.cache.RedisCacheManager" id="cacheManager"></bean>

<!-- 自动登录 -->
<bean class="org.apache.shiro.web.mgt.CookieRememberMeManager" id="cookieRememberMeManager">
<property name="cookie" ref="cookie"></property>
</bean>

<bean class="org.apache.shiro.web.servlet.SimpleCookie" id="cookie">
<constructor-arg name="name" value="rememberMe" />
<property name="maxAge" value="20000000" />
</bean>

</beans>

一些属性的意义:

  • securityManager: 这个属性是必须的。

  • loginUrl: 没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。

  • successUrl: 登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。

  • unauthorizedUrl: 没有权限默认跳转的页面。

Shiro中默认的过滤器:

过滤器名称 过滤器类 描述
anon org.apache.shiro.web.filter.authc.AnonymousFilter 匿名过滤器
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter 如果继续操作,需要做对应的表单验证否则不能通过
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter 基本http验证过滤,如果不通过,跳转屋登录页面
logout org.apache.shiro.web.filter.authc.LogoutFilter 登录退出过滤器
noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter 没有session创建过滤器
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter 权限过滤器
port org.apache.shiro.web.filter.authz.PortFilter 端口过滤器,可以设置是否是指定端口如果不是跳转到登录页面
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter http方法过滤器,可以指定如post不能进行访问等
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 角色过滤器,判断当前用户是否指定角色
ssl org.apache.shiro.web.filter.authz.SslFilter 请求需要通过ssl,如果不是跳转回登录页
user org.apache.shiro.web.filter.authc.UserFilter 如果访问一个已知用户,比如记住我功能,走这个过滤器

第五步:在spring-mvc.xml中配置权限的控制 异常的跳转

1
2
3
4
5
6
<!-- 保证 Shiro内部生命周期 -->
<bean class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>

<!-- 开启Shiro授权生效 -->
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"/>

第六步:在controller中测试使用的验证登录

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
@RequestMapping(
value="/subLogin",
method= RequestMethod.POST,
produces="application/json;charset=utf-8")
@ResponseBody
public String subLogin(User user) {

Subject subject = SecurityUtils.getSubject();

UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());

try {
token.setRememberMe(user.isRememberMe());
subject.login(token);
} catch (Exception e) {
return e.getMessage();
}

// 编码方式判断是否具有管理员身份
if (subject.hasRole("admin")) {
return "有admin权限";
}

return "无admin权限";
}

会话管理SessionManager

这里主要是结合了redis进行Sesssion的管理,自定义了RedisSessionDao
1
2
3
4
5
6
<!-- 自定义SessionManager -->
<bean class="com.shiro.session.CustomSessionManager" id="sessionManager">
<property name="sessionDAO" ref="redisSessionDao"></property>
</bean>

<bean class="com.shiro.session.RedisSessionDao" id="redisSessionDao"></bean>

自定义CustomSessionManager

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

/**
* @author Luke
* @date 2018/6/11.
*/
public class CustomSessionManager extends DefaultWebSessionManager {

@Override
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
Serializable sessionId = getSessionId(sessionKey);
//获取request
ServletRequest request = null;
if (sessionKey instanceof WebSessionKey) {
request = ((WebSessionKey) sessionKey).getServletRequest();
}
//若request不为空,并且sessionID不为空,则从request中获取session
if (request != null && sessionId != null) {
Session session = (Session) request.getAttribute(sessionId.toString());
//获取到的session不为空,则返回
if (session != null) {
return session;
}
}

//否则从redisSessionDao中获取session
Session session = super.retrieveSession(sessionKey);
//若获取的session不为空,则设值到request的作用域中
if (request != null && sessionId != null) {
request.setAttribute(sessionId.toString(), session);
}

return session;
}
}

缓存管理CacheManager,也是结合Redis,从redis中获取缓存数据,提高性能

1
<bean class="com.shiro.cache.RedisCacheManager" id="cacheManager"></bean>
1
2
3
4
5
6
7
8
9
10
public class RedisCacheManager implements CacheManager {

@Autowired
private RedisCache redisCache;

@Override
public <K, V> Cache<K, V> getCache(String s) throws CacheException {
return redisCache;
}
}

只贴出部分源码,详细的请下载源码运行,内含sql脚本(resources/shiro.sql)

-------------本文结束感谢您的阅读-------------
0%