1、权限注解
-
@RequiresAuthentication:表示当前Subject已经通过login 进行了身份验证;即Subject. isAuthenticated() 返回true;
-
@RequiresUser:表示当前Subject 已经身份验证或者通过记住我登录的;
-
@RequiresGuest:表示当前Subject没有身份验证或通过记住我登录过,即是游客身份;
-
@RequiresRoles (value={“admin”, “user”}, logical== Logical.AND):表示当前Subject 需要角色admin 和user,logical表示逻辑关系
-
**@RequiresPermissions **(value={“user:a”, “user:b”}, logical= Logical.OR):表示当前Subject 需要权限user:a或user:b,logical表示逻辑关系
使用:
// Service 类
public class TestService {
@RequiresRoles({"admin"})
public void testShiro(){
System.out.println("testShiro" + new Date());
}
}
<!-- applicationContext.xml -->
<bean id="testService" class="com.xiaojian.shiro.service.TestService"></bean>
@Controller
@RequestMapping("/shiro")
public class LoginController {
@Resource
private TestService testService;
@RequestMapping("/testShiro")
public String testShiroCon(){
testService.testShiro();
return "redirect:/list.jsp";
}
}
<a href="/shiro/testShiro">Test Shiro</a>
在Service方法上使用注解 @Transactional 即在方法开始的时候会有事务,这个时候这个Service已经是一个代理对象
这个是有把 权限注解加到 Service上是不好用的,会发生类型转换异常。需要加到Controller上,因为不能够让Service是代理的代理。
2、从数据库中初始化资源和权限
在我们实现认证、授权的过程中,我们把需要认证或授权的路径都配置在 applicationContext.xml 文件中。
但是如果,需要认证和授权的路径太多,一个一个配置路径太麻烦,文件也会显得臃肿,难维护。
(1). 首先写一个实例工厂类,返回一个 LinkedHashMap<String,String>
/**
* 过滤器映射集合工厂
*/
public class FilterChainDefinitionMapBuilder {
/*
* 这样就可以从数据库中查询所有角色、权限列表,并赋值给过滤器,而不用手动书写了
*/
public LinkedHashMap<String,String> getFilterChainDefinitionMap(){
LinkedHashMap<String,String> hashMap = new LinkedHashMap<>();
hashMap.put("/login.jsp","anon");
hashMap.put("/shiro/login","anon");
hashMap.put("/shiro/logout","anon");
hashMap.put("/user.jsp","roles[user]");
hashMap.put("/admin.jsp","roles[admin]");
hashMap.put("/**","authc");
return hashMap;
}
}
(2). applicationContext.xml,修改Shiro过滤器
<!-- 5.Shiro过滤器
id 必须和web.xml文件中配置DelegatingFilterProxy的<filter-name>一致
因为Shiro会在 IOC容器中查询和 <filter-name> 名字对应的 filter bean
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager"/>
<!-- 身份认证失败,则跳转到登录页面的配置 -->
<property name="loginUrl" value="/login.jsp"/>
<property name="successUrl" value="success.jsp"/>
<property name="unauthorizedUrl" value="unauth.jsp"/>
<!-- Shiro连接约束配置,即过滤链的定义(filterChainDefinitions)
过多路径配置会过于繁琐,改用 FilterChainDefinitionMap 属性
-->
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
</bean>
<!-- 配置一个bean,该bean是一个map,通过实例工厂类方法实现-->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="getFilterChainDefinitionMap"></bean>
<bean id="filterChainDefinitionMapBuilder" class="com.xiaojian.shiro.factory.FilterChainDefinitionMapBuilder"></bean>
3、会话管理
Shiro提供了完整的企业级会话管理功能,不依赖于底层容器(如web容器tomcat),不管JavaSE还是JavaEE环境都可以使用,提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对Web 的透明支持、SSO 单点登录的支持等特性。
为什么使用 Shiro 的会话管理?
在web开发中,service层通常是不能访问 httpSession 的(我们不建议这样做,因为在 service层访问httpSession 是一种潜入式的操作:handler中的api在service层使用)。我们可以通过Shiro的 Subject.getSession() 在任意地点访问到 session。
相关API
-
Subject.getSession():即可获取会话;其等价于Subject.getSession(true),即如果当前没有创建Session 对象会创建一个;Subject.getSession(false),如果当前没有创建Session 则返回null
-
session.setAttribute(key, val) &
session.getAttribute(key) &
session.removeAttribute(key):设置/获取/删除会话属性;在整个会话范围内都可以对这些属性进行操作
-
session.getId():获取当前会话的唯一标识
-
session.getHost():获取当前Subject的主机地址
-
session.getTimeout() & session.setTimeout(毫秒):获取/设置当前Session的过期时间
-
session.getStartTimestamp() & session.getLastAccessTime():获取会话的启动时间及最后访问时间;如果是JavaSE应用需要自己定期调用session.touch() 去更新最后访问时间;如果是Web 应用,每次进入ShiroFilter都会自动调用session.touch() 来更新最后访问时间。
-
session.touch() & session.stop():更新会话最后访问时间及销毁会话;当Subject.logout()时会自动调用stop 方法来销毁会话。如果在web中,调用HttpSession. invalidate() 也会自动调用ShiroSession.stop方法进行销毁Shiro的会话
4、RememberMe
在登录一些网页时,勾选 “记住我”登录,浏览器会把 RememberMe的 cookie 存入客户端并保存下来。
当关闭浏览器,再进网页时此时就不需要登录而直接进入网页。
建议:访问一般网页:个人主页,可使用记住我;访问特殊网页,涉及到金钱、隐私,使用认证。
认证和记住我
- subject.isAuthenticated() 表示用户进行了身份验证登录的,即使有Subject.login进行了登录;
- subject.isRemembered():表示用户是通过记住我登录的,此时可能并不是真正的你(如你的朋友使用你的电脑,或者你的cookie 被窃取)在访问的
- 两者二选一,即subject.isAuthenticated() == true,则subject.isRemembered() == false;反之一样。
使用
身份验证相关的拦截器,user:用户拦截器,用户通过身份验证/记住我登录都可。
Shiro过滤器 (需将 RememberMe 可访问的路径配置 user 过滤器)
public class FilterChainDefinitionMapBuilder {
public LinkedHashMap<String,String> getFilterChainDefinitionMap(){
LinkedHashMap<String,String> hashMap = new LinkedHashMap<>();
// 配置成 user 过滤器
hashMap.put("/list.jsp","user");
hashMap.put("/user.jsp","authc,roles[user]");
hashMap.put("/admin.jsp","authc,roles[admin]");
hashMap.put("/**","authc");
return hashMap;
}
}
LoginController.java
@RequestMapping("/login")
public String login(String username, String password,String checkbox, Model model){
/**
* Shiro 认证
*/
// 1.获取Subject
Subject subject = SecurityUtils.getSubject();
// 2.封装用户信息
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
// 判断是否勾选 "记住我",设置记住我功能
if("1".equals(checkbox)){
token.setRememberMe(true);
}
....
}
applicationContext.xml
<!-- 1.安全管理器SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<!--配置Cookie的实现类 SimpleCookie-->
<bean id="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="120"/>
</bean>
<!--RememberMe 管理器-->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>
<property name="cookie" ref="simpleCookie"/>
</bean>