概念:
Subject :任何可以和应用交互的“用户”。
SecurityManager: 相当于SpringMVC中的DispatcherServlet;是shiro的心脏。所有具体交互都通过它进行。它管理着所有Subject,且负责进行认证,授权,会话和缓存管理。
Authenticator:负责Subject的认证。
Authorizer:授权器,控制用户能访问应用中哪些功能。
Realm:一个或者多个realm,可以认为是安全的数据源,用户获取安全实体的。需要自己实现Realm。
SessionManager:管理Session生命周期组件。
CacheManager:缓存控制器,用于管理用户、角色、权限等缓存。这些数据较少改变,放缓存提高访问性能。
Cryptography:密码模块,shiro提供的常见加密组件用于加密、解密。
【shiro认证/登录流程:】
1.获取当前subject,调用SecurityUtils.getSubject();
2.测试当前用户是否已经被认证,即是否已经登录,调用Subject的isAuthenticated()
3.若没有被认证,则把用户名和密码封装为UsernamePasswordToken对象。
4.执行登录,调用Subject的login(token);方法
前4步代码,Controller登录方法中
Subject subject = SecurityUtils.getSubject(); if (!subject.isAuthenticated()) { UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword()); try { subject.login(token); //SecurityUtils.getSubject().isPermitted(user.getUserName()); } catch (AuthenticationException e) { return "login"; } } return "page/list"; }
5.自定义Realm的方法,从数据库获取对应的记录,返回给shiro
1)继承这个类
org.apache.shiro.realm.AuthorizingRealm;
2)实现方法
doGetAuthenticationInfo
Realm中认证方法:获取UsernamepasswordToken中的用户名,然后从数据库比对,没有用户则抛出异常,也可抛出账户
被锁定异常。有则交给SimpleAuthenticationInfo完成密码比对。
String userName = (String)token.getPrincipal(); User user = userService.queryUserByName(userName); if (user==null){ //没有找到账号 throw new UnknownAccountException(); } Integer status=0; if(status.equals(user.getStatus())) { //账号锁定 throw new LockedAccountException(); } /** * shiro完成密码的比对,三个参数分别是 : * 1.认证的实体信息,可以是username,也可以是数据库对应的实体类对象 * 2.数据库中的密码 * 3.盐值 * 4.当前realm的name,调用父类getName()方法 * */ SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUserName(),user.getPassword() , ByteSource.Util.bytes(user.user.getId()),getName()); return info; }
6.由shiro完成密码的比对。
认证相关filter
authc | FormAuthenticationFilter | 基于表单的认证器;如"/**=authc",如果没有登录会跳到登录页面登录 主要属性: usernameParam:表单提交的用户名参数名(username) passwordParam:表单提交的密码参数名(password) rememberMeParam:表单提交的密码参数名(rememberMe) loginUrl:登录地址(login.jsp) successUrl:登录成功重定向地址 failureKeyAttribute:登录失败后错误信息存储key(shiroLoginFailure) |
authcBasic | BasicHttpAuthenticationFilter | Basic HTTP身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application) |
logout | LogoutFilter | 退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/)示例: "/logout=logout" |
user | UserFilter | 用户拦截器,认证和记住我都可以。示例"/**=user"; |
anon | AnonymousFilter | 匿名拦截器,即不需要登录也能访问,一般用户静态资源过滤,示例: "/static/**=anon" |
【shiro授权步骤:】
实现方法:
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)1.从PrincipalCollection中获取登录用户的信息
if (principals == null) { throw new AuthorizationException("PrincipalCollection方法参数不能为空."); }
2.利用登录用户信息来获取当前用户的角色或权限(可能需要查询数据库)
3.创建SimpleAuthorizationInfo,并设置roles属性。
4.返回SimpleAuthorizationInfo对象
234代码
String userName = (String)principals.getPrimaryPrincipal(); User user = userService.queryUserByName(userName); Integer userId=user.getId(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //根据userID获取roles集合 info.setRoles(userRolesService.queryRolesByUser(userId)); //根据userId获取资源permission集合 info.setStringPermissions(resourceService.findMenuByUserId(userId)); return info;
【shiro授权详解:】
授权三种方式:
1.编程式:if...else授权代码块实现
2.注解式:在对应的service或者controller放置相应注解完成,没有权限抛出异常
3.JSP/GSP标签:在页面用标签完成
跟授权相关的filter
roles | RolesAuthroizationFilter | 角色授权器,验证用户是否拥有所有角色 主要属性: loginUrl:登录地址(/login.jsp); unauthorizedUrl:未授权重定向的地址; 示例:"/admin/**=roles[admin]" |
perms | PermissionsAuthroizationFilter | 权限授权拦截器,验证用户是否拥有权限,属性和 roels一样;示例: "/user/**=perms["user:create"]" |
port | PortFilter | 端口拦截器,主要属性port(80),示例:"/test=port[80]" 如果用户的访问页面是非80,将重定向到80端口 |
rest | HttpMethodPermissionFilter | rest风格拦截器,自动根据请求方法创建权限字串 GET=read,Post=create,PUT=update,DELETE=delete,HEAD=read TRACE=read,OPTIONS=read,MLCOL=create 示例:"/user/=rest[user]",会自动拼出"user:read,user:creat,user:update, user:delete",权限字符串进行权限匹配(所有都得匹配,isPermittedAll) |
ssl | SslFilter | SSL拦截器,只有https请求才通过,否则自动跳转会https端口443;其他和port一样 |
@RequiresAuthtication:表示当前Subject已经通过login进行了身份验证,即:Subject.isAuthenticated()返回true
@RequiresUser:表示当前Subject已经身份验证或者通过记住我登录
@RequiresGuest:表示当前Subject没有登录或者记住我,是游客身份
@RequiresRoles(value={"admin","user"},logical=logincal.AND):表示当前Subject需要身份admin和user
@RequiresPermissions(value={"user:a""user:b"},logical=logincal.OR):表示当前Subject需要权限user:a或者user:b
注意点:开发的时候service有可能加@transactional注解,这时候加权限注解会出现错误,应该把注解加到controller层
多Realm授权,只要有一个过就return true.
从数据库中初始化权限和资源:
新建一个类FilterChainDefinitionMapBuilder,以后权限在map中获取,需要注意顺序,和filterChainDefinitions配置一样。
public class FilterChainDefinitionMapBuilder { public LinkedHashMap<String,String> buildFilterChainDefinitionMap(){ LinkedHashMap<String,String> map=new LinkedHashMap<>(); //TODO 查询数据库 return map; }spring-shiro.xml中,取消filterChainDefinitions配置,加入了filterChainDefinitionMap 这个property,配置实例工厂,引入
上面新建的类FilterChainDefinitionMapBuilder。
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/> <!-- <property name="filterChainDefinitions"> <value> /kaptcha.jpg=anon /statics/**=anon /login.jsp=anon /login=anon /shiro/logout = logout /**=authc </value> </property>--> </bean> <!--配置一个bean,该bean实际上是一个map,通过实例工厂的方式--> <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"/> <bean id="filterChainDefinitionMapBuilder" class="com.queen.manage.shiro.FilterChainDefinitionMapBuilder"></bean>
【shiro的加密和密码比对】
通过AuthenticatingRealm 的credentialMatcher属性来进行密码比对
1.如何加密字符串:
四个属性分别是:加密方法,加密的字符串,盐,加密次数
SimpleHash simpleHash=new SimpleHash("MD5",user.getPassword(),user.getUserName(),1024);
2.替换当前Realm的credentialMatcher属性,直接使用HashedCredentialMatcher对象,并设置加密算法。
xml配置代码如下:
<bean id="userRealm" class="com.queen.manage.realm.UserRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="MD5"/> <property name="hashIterations" value="1024"/> </bean> </property> </bean>
配置的作用是自动把前台输入的密码变成MD5加密
【多Realm验证】
spring-shiro.xml中定义多个Realm,通过ModularRealmAuthenticator把多个自定义Realm注入,在securityManager中引入
以前是securityManager直接引入一个Realm,多个Realm中间多了一层
把realms放入securityManager中,便于授权进行。
多Realm下的认证策略:<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"> <property name="authenticationStrategy"> <bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"/> </property> </bean><!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="authenticator" ref="authenticator"/><property name="realms"> <list> <ref bean="userRealm"/> <!--<ref bean="userRealm2"/>--> </list> </property><!--缺少缓存--> </bean>
AuthenticationStrategy接口的默认实现:
FirstSuccessfulStrategy:只要一个realm认证成功即可,只返回第一个成功的realm身份认证信息,其他忽略。
AtLeastOneSuccessfulStrategy:只要一个realm认证成功即可,将返回所有认证成功的realm身份信息。
AllSuccessfulStrategy:所有realm认证成功才算成功,一个错误就失败。
ModularRealmAuthenticator 的默认策略是AtLeastOneSuccessfulStrategy
修改认证策略参照上面。
【shiro会话管理】
shiro的会话管理功能,不依赖底层容器(如web的tomcat),不管javaSE还是javaEE环境都能使用,提供了会话管理,会话事件监听。会话存储/持久化,容器无关的集群,失效/过期支持,对web的透明支持,SSO单点登录支持等特性。
会话相关的API:
Subject.getSession():获取对话,等价于Subject.getSession(true),如果没有session创建一个,如果Subject.getSession(false),没有session则返回null
session.getId():获取对话的唯一标识。
session.getHost();获取当前Subject主机地址。
session.getTimeOut()&session.setTimeOut(毫秒):获取/设置当前session过期时间。
session.getStartTimestamp()&session.getLastAccessTime():获取对话的启动时间和最后访问时间,javaSE需要手动调用
session.touch()更新最后访问时间,javaEE自动调用
session.touch()&session.stop():更新会话最后访问时间,销毁会话;当subject.logout()时会自动调用stop方法。如果web中
调用HttpSession.invalidate()也会调用stop销毁会话。
session.setAttribute(key&value);
session.getAttribute(key);
session.removeAttribute(key);设置、获取,移除会话属性;整个会话范围内都能对属性操作。
注:controller还是建议使用HttpSession,如果要在service层调用会话属性,可以再使用shiro的Session操作
【SessionDAO】
可以把session写入数据库,然后进行crud的操作。
AbstractSessionDAO:提供SessionDAO基础实现,如生成会话ID等。
CachingSessionDAO:提供了对开发者透明的会话缓存功能,需要设置想要的CacheManager
MemorySessionDAO:直接在内存中进行会话维护
EnterpriseCacheSessionDAO:提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap
保存会话管理。
【会话验证】
shiro提供了会话验证调度器,用于定期检查会话是否过期,如果过期停止会话。
出于性能考虑,一般情况下都是获取会话来验证会话是否过期,但是在web环境中,如果用户不主动退出,是不知道会话是否过期的,
因此要定期检查,shiro提供了会话验证调度器:SessionValidationScheduler
shiro也提供了使用Quartz会话验证调度器:QuartzSessionValidationScheduler
【缓存】
CacheManagerAware接口
shiro内部相应的组件(DefaultSecurityManager)会自动检测相应对象(如Realm)是否实现了CacheManagerAware
并自动注入相应的CacheManager
Realm缓存
shiro提供了CachingRealm,其实现了CacheManagerAware接口,提供了 缓存的基础实现;AuthenticatingRealm及
AuthorizingRealm也分别提供了对AuthenticationInfo和AuthorizationInfo信息的缓存
【remenberMe】
在登录页面选择记住我,是把RemenberMe的cookie写到客户端并保存,关掉页面再打开能访问,某些页面需要认证。
认证和记住我区别:
subject.isAuthenticated():表示用户进行了身份验证登录的,即有subject.login进行登录
subject.isRemembered():表示用户通过记住我登录的,此时可能不是真正的你
二者二选一,即subject.isAuthenticated()==true,则subject.isRemembered()==false;反之一样。
访问建议:
访问一般网页,我们使用user拦截器即可。即认证或者记住我登录的都可以访问
访问一些敏感信息,我们使用authc拦截器,authc会判断用户是否认证登录的,如果不是则返回登录页面。
if (rember!=null) { token.setRememberMe(true); }如果对rememberMe时长什么有具体要求,可以再xml中调用rememberMeManager具体设置