安全权限框架 —— Shiro(二)

·  阅读 1673
安全权限框架 —— Shiro(二)

这是我参与8月更文挑战的第27天,活动详情查看:8月更文挑战

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>
复制代码
分类:
后端