shiro学习笔记
1.简介:Apache下,Java的安全(权限)框架;相似的还有Spring security;可适用于SE和EE环境。
2.功能:认证(登录);权限;会话管理(一次登录就是一次会话,可以是SE也可以是EE);加密;Web支持;缓存(用户信息、用户角色/权限不必每次都查);多线程并发验证(一个线程开启另一个,权限传递);测试支持;Run as(一个用户以另一个用户身份);记住我。
3.架构:应用只与subject打交道,是门面;
3.1:外部看:subject;Security manager;Realm;
3.2:Security manager内部的认证/授权通过一个或多个Realm访问持久化数据;会话管理器通过会话DAO访问持久化数据;还有一个缓存管理;密码模块;
3.3:扩展点:subject认证策略可以自定义;realm需要自己实现。
4.第一个项目:shiro提供的,在源码包root-samples-quickstart;
4.1:导jar包:log4j,shiro-all,slf4j-api,slf4j-log4j12;
4.2:复制配置文件shiro.ini和log4j.properties到src下;然后即可运行起来;
4.3:代码解读:登录登出可以直接使用在代码里,但测试角色一般使用注解或配置,很少直接硬编码;
4.3.1:工厂方法获得securityManager,实用类获得subject(代表当前用户);
4.3.2:测试使用session,从当前用户获得,无需web或EJB容器;
4.3.3:登录:使用subject的isAuthenticated()和login(),将用户密码封装为UsernamePasswordToken对象;登录会抛出AuthenticationException;
4.3.4:角色权限:hasRole()测试是否有相应角色;isPermitted()测试具备某行为权限;
4.3.5:logout()。
5.集成Spring:a.加入Spring;b.配置Spring和Spring MVC;c.加入shiro的jar包;d.配置shiro;
5.1:导Spring的jar:4.0.0.RELEASE下required里的都需要;
5.2:配置WEB-INF/lib的web.xml:
5.2.1:配置Spring:<context-param>的contextConfigLocation,给出application.xml位置;ContextLoaderListener;
5.2.2:配置MVC:配置DispatcherServlet;
5.3:配置WEB-INF/lib的spring-servlet.xml:component scan;InternalResourceViewResolver;开启注解;default-servlet-handler;
5.4:加入、配置shiro:先加jar包,然后web环境配置主要参照samples下的web.xml和application.xml;
5.5:在web.xml配置shiroFilter
5.6:复制所有到src下的application.xml,配置shiro核心的:
5.6.1:配置securityMnager(cacheManager,realm);
5.6.2:配置cacheManger:需要加入ehcache的jar包及配置文件,放到src下;
5.6.3:配置realm:需要自己实现Realm接口;
5.7:接上,配置Spring相关的:
5.7.1:配置LifecycleBeanPostProcessor:可以自动调用配置在IOC容器中的shiro bean生命周期方法;
5.7.2:在IOC中启用shiro注解:
5.7.3:配置shiroFilter(securityManager,登录页面,成功页面,未授权页面,以及其他页面与权限):名字必须和web.xml中的一致。
6.Web集成:shiro通过ShiroFilter入口拦截需要安全控制的URL,然后进行控制;类似于MVC框架的前端控制器,是安全控制的入口,还负责读取配置文件。
7.DelegatingFilterProxy:在app.xml里的bean是org.apache.shiro.spring.web.ShiroFilterFactoryBean;web.xml里配置的<filter-name>的class是org.springframework.web.filter.DelegatingFilterProxy,实际上是Filter的一个代理对象,默认情况下,Spring到IOC容器查找<filter-name>对应的bean(即前面配置的)。
8.shiroFilter的filterChainDefinitions属性:格式:URL=拦截器[参数];anon拦截器表示不需要认证;authc拦截器表示需要登录;
8.1:url模式支持Ant风格:?匹配一个字符,*匹配零或多个(不包括目录的/),**匹配路径中的零个或多个路径;
8.2:url匹配是从上到下使用第一个匹配模式的拦截器链;
8.3:其他拦截器(过滤器):logout。
9.认证流程:
9.1:获取当前subject,SecurityUtils.getSubject();
9.2:测试是否登录isAuthenticated(),若没有,则把用户名和密码封装为UsernamePasswordToken对象(实现了AuthenticationToken接口);
9.2.1:用户名密码哪里来:浏览器页面创建表单,提交给MVC的Handler,从中获取;
9.3:登录:login(AuthenticationToken);实际上调用的是securityManager.login();
9.4:从数据库获取用户名密码进行比对:自定义Realm,继承AuthenticatingRealm类抽象类,实现doGetAuthenticationInfo(AuthenticationToken);
9.5:shiro完成比对。
10.认证实现:
10.1:创建表单页面;
10.2:编写login Handler(即controller);在里面获取用户名和密码,使用hello world里的方法login;
10.3:实现Realm:doGetAuthenticationInfo(AuthenticationToken);
10.3.1:类型转换为UsernamePasswordToken;
10.3.2:获取用户名,调用数据库方法查询用户记录;
10.3.3:根据查询情况抛出不同的异常;
10.3.4:构建AuthenticationInfo对象返回(常用实现类是SimpleAuthenticationInfo),需要principal,credentials,realmName;
10.4:因为会缓存密码,还要做一个登出界面,直接使用logout拦截器。
11.明文密码:前台输入密码在UsernamePasswordToken,后台数据库查询的密码在SimpleAuthenticationInfo;通过AuthenticatingRealm的credentialsMatcher属性进行密码比对。
12.密码加密:不可逆加密算法,MD5,SHA1;这里用MD5;
12.1:后台加密:先把字符串加密为MD5存到数据库表,new SimpleHash("MD5",密码,盐值,加密次数);
12.2:前台加密:靠的是realm的凭证匹配器credentialsMatcher,替换为HashedCredentialsMatcher,并设置加密属性(在app.xml设置)。
13.盐值加密:原始密码一样,加密后的密码不一样;
13.1:用new SimpleHash("MD5",密码,盐值,加密次数)计算加盐后的结果存到数据库;
13.2:查询数据库的返回需要把盐带上new SimpleAuthenticationInfo(principal,credentials,Salt,realmName);因为前端密码还是明文,需要盐值;
13.3:获得盐值:ByteSource salt=ByteSource.Util.bytes(唯一字符串或username)。
14.多Realms:安全数据可能采用不同加密算法后存储到不同数据库,就需要用到;
14.1:都需要在app.xml里配置实现类的bean;
14.2:多个realm的bean以<list>属性再配置给authenticator,实现类是org.apache.shiro.authc.pam.ModularRealmAuthenticator;
14.3:authenticator再以属性配置到securityManager。
15.认证策略:AuthenticationStrategy接口默认有3个实现;
15.1:FirstSuccessfulStrategy:只要有一个realm验证成功即可,只返回第一个realm身份验证成功的认证信息;
15.2:AtLeastOneSuccessfulStrategy:只要有一个realm验证成功即可,返回所有realm身份验证成功的认证信息;
15.3:AllSuccessfulStrategy:所有realm验证成功才成功,返回所有realm身份验证成功的认证信息;
15.4:ModularRealmAuthenticator默认是AtLeastOne策略;可在app.xml文件里配置属性修改为以上任意一个。
16.多Realms:也可以直接配置到securityManager
16.1:在授权的时候是从securityManager读realms;所以需要改回去到这里配,而不是配给authenticator;
16.2:打断点跟踪源码发现,有从securityManager获得realms然后设置给authenticator的行为,所以这样配也是可行的。
17.授权:访问控制,即在应用中控制谁访问哪些资源;
17.1:关键对象:主体、资源、权限、角色;
17.2:授权方式:编程式(if-else);注解式(在相应Java方法加上@RequiresRoles());JSP标签;
17.3:DefaultFilter:验证相关的authc、anon、logout;授权相关的roles;
17.4:Permissions权限字符串:资源标识符:操作:对象实例;:表示字符串部件的分割;.表示操作的分割;*表示任一部件的任意字符。
18.授权Realm:认证Realm实现需要继承AuthenticatingRealm并实现doGetAuthenticationInfo()抽象方法;
18.1:授权Realm实现需要继承AuthorizingRealm并实现doGetAuthorizationInfo()抽象方法;
18.2:AuthorizingRealm也是AuthenticatingRealm的子类,需要实现认证和授权两个抽象方法;
18.3:源码分析:hasRole的调用栈,subject->securityManager->AuthorizingRealm(getAuthorizationInfo->doGetAuthorizationInfo())。
19.多Realms授权:从hasRole入手,subject->securityManager->ModularRealmAuthorizer(for循环调用每一个realm的hasRole,回到上面单Realm)。
20.授权Realm实现:
20.1:把前面认证realm的实现改为继承AuthorizingRealm,并增加AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection p)的实现;
20.2:基本流程:
20.2.1:获取登录用户信息:p.getPrimaryPrincipal()->SimplePrincipalCollection(realmPrincipals.values().next(),是一个LinkedHashMap),所以顺序与securityManger里配置的是一样的;
20.2.2:利用用户信息获取当前角色或权限(可能要查数据库):判断前面SimpleAuthenticationInfo放入的principal,查得角色和权限;
20.2.3:创建SimpleAuthorizationInfo,设置roles属性,并返回:roles是HashSet,添加相应角色的字符串,构造对象返回。
21.标签:提供JSTL标签在JSP页面进行权限控制,如根据用户显示相应按钮;
21.1:导入标签库:<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>;
21.2:<shiro:principal>、<shiro:hasRole name="admin">admin角色才能看到的标签内容</shiro:hasRole>。
22.权限注解:可放在controller或service层(注意如果开启了事务,就只能放到controller层,否则注入会发生类型转换异常);
22.1:写一个service,@RequiresRoles({"admin"}),然后放到IOC容器中(即在app.xml配bean);
22.2:在controller里设置私有属性,开启@Autowired自动注入service;并调用service测试方法;
22.3:这里以user访问则会抛UnauthorizedException,可利用spring的声明式异常(@ExceptionHandler)搞一个错误页面。
23.资源和权限定义:开始是在app.xml的filterChainDefinitions属性里以键值对的形式写死;换为在数据库里查询得到会更好;
23.1:源码:通过ShiroFilterFactoryBean#set方法设置,在shiro初始化时传入是LinkedHashMap,那么也可给出filterChainDefinitionMap属性替换;
23.2:思路:问题转为配置一个bean,该bean实际是LinkedHashMap,通过实例工厂方法的方式返回;
23.2.1:构造实例工厂类:MapBuilder,写一个方法,通过查询数据库构造并返回LinkedHashMap<String,String>;
23.2.2:在app.xml配置:a.MapBuilder的bean;b.配置另一个没有实际类对应的Map的bean,写factory-bean和factory-method指向MapBuilder;
23.2.3:filterChainDefinitionMap属性指向Map的bean。
24.会话管理:不依赖于底层容器,提供会话管理,会话事件监听,会话存储/持久化,失效支持,SSO单点登录支持等;
24.1:相关API:getSession();getId();getHost();get/setTimeout();getLastAccessTime();touch()更新会后访问会话时间;stop()销毁会话;以及对属性进行set/get/remove;
24.2:会话监听器:onStart(),onStop(),onExpiration();
24.3:意义:handler与httpSession打交道,service与shiro的session打交道SecurityUtil.getSubject().getSession();获得得到同一session。
25.SessionDao:会话存到数据库里;实际一般继承EnterpriseCacheSessionDao类(doCreate(),doReadSession(),doUpdate(),doDelete());
25.1:需要配置session ID生成器、session DAO(继承EnterpriseCacheSessionDao,还需要配置缓存)、会话管理器(配给securityManager);
25.2:会话序列化利用对象输入输出流ObjectOutputStream进行读写,其包装了字节数组输入输出流ByteArrayOutputStream,然后利用JdbcTemplate写入数据库jT.update(sql,sessionId,序列化后的session);
25.3:读取利用对象输入流反序列化。
26.会话验证:性能考虑一般是获取会话时验证会话是否过期;在web用户不退出的话不知道是否过期,需要一个调度器定期检测SessionValidationScheduler;性能开销大,用得不多。
27.缓存:shiro会自动检测相应对象如Realm是都实现了CacheManagerAware接口并自动注入相应的CacheManager给该对象;实际开发会用redis做缓存。
27.1:认证Realm和授权Realm可自动对认证Info和授权Info缓存,因为其父类已实现这个接口;
27.2:自定义的Realm可指定缓存的名字、是否开启缓存;
27.3:缓存策略配置文件:ehcache.xml;
27.4:session缓存:securityManager会判断sessionManager是否实现了接口;sessionManager会判断sessionDAO是否实现了接口。
28.记住我:即把cookie写到客户端并保存;要么是认证的,要么是记住我;
28.1:访问一般网页,用user拦截器,记住我或认证都可访问;
28.2:访问特殊网页:使用authc拦截器,只能登陆访问。
29.记住我的功能:使用user拦截器才能体现;
29.1:登陆前有token.setRememberMe(true);可在前端页面提供单选框,用户勾上就设置,否则这里不设置;
29.2:过期时间设置:断点debug发现在securityManager里设置,<property name="rememberMeManager.cookie.maxAge" value="?"(秒)>。

浙公网安备 33010602011771号