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
|
<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
| <bean id="mailFilter" class="com.qhs.blog.util.shiro.filter.mailFilter"/> <bean id="captchaFilter" class="com.qhs.blog.util.shiro.filter.captchaFilter"/>
<bean id="loginFilter" class="com.qhs.blog.util.shiro.filter.loginFilter"/>
<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
| <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <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> <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
| <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>
<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="subjectDAO" ref="subjectDAO"/> <property name="subjectFactory" ref="subjectFactory"/> <property name="sessionManager" ref="sessionManager"/> </bean>
<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
| <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) { 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;
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;