关于shiro在SSM环境下的整合(1)

1.无状态场景下的shiro整合。

这是自己练手的产物,github:https://github.com/Arsenolite/com.qhs.blog

分析思路:

  • 由于是无状态API,只有带Token访问,因此把session相关的全部禁用。
  • 而Realm起到的是userService的作用,也禁用掉,毕竟我不可能每次用户请求API就去查数据库吧。
  • 再加上当时使用了JWT作为Token规范,因此业务需求就成了检验JWT有效性。
  • Filter中直接注入redisDAO和tokenService,对URL中的Token参数检验,有效就放行。

具体写法:

首先我们要有一个maven+ssm的环境,并且导入shiro-web,shiro-core和shiro-spring三个依赖。
由于我们需要将shiro纳入Spring的管理周期,在Web.xml中这么写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- shiro -->
<!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.xml -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<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集成,所以具体Filter的代码不是写在这里,这里只是将shiro的Filter纳入Spring的Filter链。

接下来是重头戏:Spring配置文件,既然有SSM环境,肯定有一份或者多份xml。
定义Filter,每一个都指向具体的类(稍后在后文贴出)
1
2
3
4
5
6
7
<!--验证码Filter-->
<bean id="mailFilter" class="com.qhs.blog.util.shiro.filter.mailFilter"/>
<bean id="captchaFilter" class="com.qhs.blog.util.shiro.filter.captchaFilter"/>
<!--认证Filter-->
<bean id="loginFilter" class="com.qhs.blog.util.shiro.filter.loginFilter"/>
<!--授权Filter-->
<bean id="adminFilter" class="com.qhs.blog.util.shiro.filter.adminFilter"/>
组装Filter工厂,之前web.xml中的shiroFilter实际上是一个Filter工厂。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- shiroFilter工厂 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 构建securityManager环境 -->
<property name="securityManager" ref="securityManager"/>
<property name="filters">
<util:map>
<entry key="mail" value-ref="mailFilter"/>
<entry key="captcha" value-ref="captchaFilter"/>
<entry key="admin" value-ref="adminFilter"/>
<entry key="login" value-ref="loginFilter"/>
</util:map>
</property>
<!-- 设计哪些URL用哪个过滤器 -->
<!--由于具体用户权限还没细化成接口,先定义两个验证码的过滤器-->
<property name="filterChainDefinitions">
<value>
<!-- 邮箱验证码要求网页验证码认证通过 -->
/api/captcha/** = noSessionCreation,anon
/api/mail/** = noSessionCreation,captcha
<!-- 用户模块 -->

</value>
</property>
</bean>
接下来将shiro的会话相关功能全部禁用掉,StatelessDefaultSubjectFactory需要手动撰写。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- Subject工厂,写一个禁用会话的subjectFactory -->
<bean id="subjectFactory" class="com.qhs.blog.util.shiro.StatelessDefaultSubjectFactory"/>

<!-- 禁用掉会话调度器 -->
<bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
<property name="sessionValidationSchedulerEnabled" value="false"/>
</bean>

<!--解决报错,组装默认的subjectDAO-->
<bean id="subjectDAO" class="org.apache.shiro.mgt.DefaultSubjectDAO">
<property name="sessionStorageEvaluator" ref="sessionStorageEvaluator"/>
</bean>

<bean id="sessionStorageEvaluator" class="org.apache.shiro.mgt.DefaultSessionStorageEvaluator">
<property name="sessionStorageEnabled" value="false"/>
</bean>
将配置为禁用Session的subjectDAO、subjectFactory和sessionManager注入安全管理器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--<property name="realm" ref="userRealm"/>-->
<property name="subjectDAO" ref="subjectDAO"/>
<property name="subjectFactory" ref="subjectFactory"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>

<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod"
value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean>

最后构建一个迭代7次的SHA-512 Hash服务,用来在类里调用。
1
2
3
4
5
<!--构建一个Hash服务,在此指定叠加方式和采用的算法-->
<bean id="defaultHashService" class="org.apache.shiro.crypto.hash.DefaultHashService">
<property name="hashAlgorithmName" value="SHA-512"/>
<property name="hashIterations" value="7"/>
</bean>
前文提到的StatelessDefaultSubjectFactory:
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.qhs.blog.util.shiro;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;

public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
public Subject createSubject(SubjectContext context) {
//不创建session
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}
接下来就是编写具体Filter了。

先贴一段代码,逐步讲解作用。

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
package com.qhs.blog.util.shiro.filter;


import com.qhs.blog.serviceImpl.tokenServiceImpl;
import net.minidev.json.JSONObject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
* Created by QHS on 2017/5/31.
*/
public class captchaFilter extends AccessControlFilter {
@Autowired
private tokenServiceImpl tokenService;

JSONObject resp = new JSONObject();

@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
boolean flag = false;
HttpServletRequest req = WebUtils.toHttp(servletRequest);
String token = req.getParameter("token");
Map<String, Object> resultMap = tokenService.validToken(token);
switch ((String) resultMap.get("state")) {
case "EXPIRED":
resp.put("warning", "授权已过期");
break;
case "VALID":
JSONObject jo = (JSONObject) resultMap.get("data");
String value = (String) jo.get("value");
if (value.equals("captchaAuthed") && value.equals("getMailCode")) {
flag = true;
}
break;
case "INVALID":
resp.put("msg", "请输入验证码");
break;
}

return flag;
}

@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {

servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("application/json; charset=utf-8");
servletResponse.getWriter().write(resp.toJSONString());
return false;
}

}

这个Filter继承了AccessControllFilter,shiro有几个内置Filter,有的用于认证,有的用于鉴权。而在这里,我只需要一个验证码,没必要处理用户登录,因此直接用了AccessControllFilter。

继承了类就需要继承方法,AccessControllFilter带了isAccessAllowed和onAccessdenied方法。

前者如果return了True,就允许用户访问Filter保护下的url,如果return False,那就会进入后面的方法,如果后面的方法Return True也允许用户访问(意义不明啊)。

因此我在类里定义变量JSONObject,在isAccessAllowed方法中对传入Token进行验证,验证不通过则在JSONObject内写入相应的错误信息并返回false。

进入onAccessDenied方法,在方法体内将JSONObject写入Servlet流输出到网页上,这样就完成了一个基本的无状态应用集成。
(其实我的做法和手写Filter差不多,只是引入shiro作为练手,和后续功能扩展。)

2.普通Web应用中使用shiro

分析思路:

现在在单位做的项目,计划中是一个由服务端渲染JSP的项目,因此可以使用Session和Cookie。

这时候引入shiro,就可以使用realm的相关特性,将userService注入realm中,由shiro接管用户认证、授权的成功/失败后的场景。可以说是充分发挥了功能。
Web.xml内容不变,Spring配置中去掉那些关于Session的设置,并且也不用写StatelessDefaultSubjectFactory。

具体写法:

修改XML配置
1
2
3
4
5
6
7
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"/>
</bean>

<bean id="userRealm" class="com.shengting.store.util.shiro.realm.userRealm">
<property name="cachingEnabled" value="false"/>
</bean>
新建userRealm

由于这个Realm类需要同时完成认证/鉴权工作(认证(Authc)指的是判断用户是否能登录,而鉴权(Authz)指的是用户有没有权限执行某项操作。),我们让它继承AuthorizingRealm类。

继承了类就要继承方法,我们的类看起来是这样子的:

1
2
3
4
5
6
7
8
9
10
public class userRealm extends AuthorizingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}

后面的大体思路,就是调用userMapper在数据库中查找信息(也可以使用封装好的userService)
由于工作进度问题,Controller还没有写完,暂时先更新到这里;w;

关于shiro在SSM环境下的整合(1)

http://blog.mothership.top/posts/6d8af3e.html

作者

Mother Ship

发布于

2017-07-21

更新于

2023-02-13

许可协议

评论