Shiro(2)

六、自定义Realm

存在的问题:目前所有的 用户、角色、权限数据都在ini文件中,不利于管理。

实际项目开发中这些信息,应该在数据库中。所以需要为这3类信息建表

6.1 建表

用户表,角色表,权限表

create table t_user(
	id int primary key auto_increment,
    username varchar(20) not null unique,
    password varchar(100) not null
)engine=innodb default charset=utf8;

create table t_role(
	id int primary key auto_increment,
	role_name varchar(50) not null unique,
	create_time timestamp not null
)engine=innodb default charset=utf8;
  
create table t_permission(
	id int primary key auto_increment,
	permission_name varchar(50) not null unique,
	create_time timestamp
)engine=innodb default charset=utf8;
  
create table t_user_role(
	id int primary key auto_increment,
	user_id int references t_user(id),
	role_id int references t_role(id),
	unique(user_id,role_id)
)engine=innodb default charset=utf8;

create table t_role_permission(
	id int primary key auto_increment,
	permission_id int references t_permission(id),
	role_id int references t_role(id),
	unique(permission_id,role_id)
)engine=innodb default charset=utf8;

6.2 自定义Realm

Realm的职责是,为shiro加载 用户,角色,权限数据,以供shiro内部校验。

之前定义在ini中的数据,默认有IniRealm去加载。

现在库中的数据,需要自定义Realm去加载。

ops : 没必要在Realm中定义大量的查询数据的代码,可以为Realm定义好查询数据的DAO和Service。

6.2.1 父类

如下是Realm接口的所有子类,其中IniRealm是默认的Realm,负责加载shiro.ini中的[users]和[roles]信息,当shiro需要用户,角色,权限信息时,会通过IniRealm获得。

自定义realm有两个父类可以选择:

1> 如果realm只负责做身份认证 ,则可以继承:AuthenticatingRealm

2> 如果realm要负责身份认证和权限校验,则可以继承:AuthorizingRealm

在这里插入图片描述

6.2.2 定义Realm

public class MyRealm extends AuthorizingRealm {
    /**
     * 是否支持某种token
     * @param token
     * @return
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        System.out.println("is support in realm1");
        if(token instanceof UsernamePasswordToken){
            return true;
        }
        return false;
    }

    /**
     * 当subject.login()时,shiro会调用Realm的此方法做用户信息的查询,然后做校验
     * 职责:通过用户传递来的用户名查询用户表,获得用户信息
     * 返回值:将查到的用户信息(用户名+密码)封装在AuthenticationInfo对象中返回
     * 异常:如果没有查到用户可抛出用户不存在异常;如果用户被锁定可抛出用户被锁异常;或其它自定义异常.
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获得用户名
        String username = (String) token.getPrincipal();
        System.out.println("user:"+username+" is authenticating~~");
        UserService userService = 
            (UserService)ContextLoader.getCurrentWebApplicationContext().getBean("userService");
        //身份认证
        User user = userService.queryUser(username);
        System.out.println("user:"+user);
        /**
        如下代码可以省略,如果查询结果为空,直接返回null即可,
        shiro的后续流程已有类似判断逻辑,也会抛出UnknownAccountException
        if(user==null){//如果用户信息非法,则抛出异常
            System.out.println("用户不存在");
            throw new UnknownAccountException("username:"+username+"不存在");
        }
        **/
        //省略如上代码后,可以直接写:
        if(user == null){
            return null;
        }
        // 将 当前用户的认证信息存入 SimpleAuthenticationInfo 并返回
        // 注意此方法的本职工作就是查询用户的信息,所以查到后不用比对密码是否正确,那是shiro后续流程的职责。
        // 如果密码错误,shiro的后续流程中会抛出异常IncorrectCredentialsException
        return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),getName());
        /** 
        补充: 可以在user表中增加一列,用于存储用户是否被锁定,则查询的User对象中会有是否锁定的属性
             如果发现锁定则可以在此方法中抛出异常:LockedAccountException,
        **/
    }

    /**
     * 当触发权限或角色校验时:subject.isPermitted() / subject.checkPermission();
     *                       subject.hasRole() / subject.checkRole() 等。
     * 此时需要数据库中的 权限和角色数据,shiro会调用Realm的此方法来查询
     * 角色和权限信息存入SimpleAuthorizationInfo对象
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //获得username
        String username  = (String)principals.getPrimaryPrincipal();
        //新建SimpleAuthorizationInfo对象
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //查询当前用户的所有 "角色" 和 "权限"
        UserService userService = 
            (UserService)ContextLoader.getCurrentWebApplicationContext().getBean("userService");
        Set<String> roles = userService.queryRolesByUsername(username);
        Set<String> perms = userService.queryPermissionsByUsername(username);
        //“角色” 和 “权限” 存入 SimpleAuthorizationInfo对象
        info.setRoles(roles);
        info.setStringPermissions(perms);
        //返回SimpleAuthorizationInfo
        return info;
    }
}

6.3 配置Realm

shiro.ini中 配置自定义Realm

注意:[users] [roles] 两个部分不再需要

[main]
shiro.loginUrl = /login.jsp
shiro.unauthorizedUrl=/login.jsp
shiro.redirectUrl=/logout.jsp
shiro.postOnlyLogout = true
#注意:此处实在安装自定义Realm 指定realm
#声明Realm 名称 = Realm类路径
realm1 = com.zhj.realm.MyRealm
realm2 = com.zhj.realm.MyRealm2
#安装Reaml 关联到SecurityManager
securityManager.realms=$realm1,$realm2

[urls]
#照旧

web.xml配置不变

Shiro_自定义Realm后的 项目架构

在这里插入图片描述

七 、加密

用户的密码是不允许明文存储的,因为一旦数据泄露,用户的隐私信息会完全暴露。

密码必须结果加密,生成密文,然后数据库中只存储用户的密码的密文。

在这里插入图片描述

在加密过程中需要使用到一些**“不可逆加密”**,如 md5,sha

所谓不可逆是指:

  • 加密函数A, 明文 “abc”, A(“abc”) = “密文”,不能通过 “密文” 反推出 “abc”,即使密文泄露密码仍然安全。

7.1 加密介绍

shiro支持hash(散列)加密,常见的如 md5, sha等

  • 基本加密过程

    md5(明文),sha(明文) 得到明文的密文,但明文可能比较简单导致密文容易被破解。

  • 加盐加密过程

    系统生成一个随机salt=“xxxxxx”, md5(明文+salt) ,sha(明文+salt),则提升了密文的复杂度。

  • 加盐多次迭代加密过程

    如果迭代次数为2,则加密2次: md5(明文+salt)=密文a , md5(密文a+salt)=最终密文

    ​ sha(明文+salt)=密文a , sha(密文a+salt)=最终密文

​ 则进一步提升了密文的复杂度,和被破解的难度。

加密过程中建议使用salt,并指定迭代次数,迭代次数的建议值1000+

实例代码:

String password="abc";//密码明文
String salt=UUID.randomUUID().toString();//盐
Integer iter = 1000;//迭代次数
String pwd = new Md5Hash(password, salt,iter).toString(); //md5加密
String pwd = new Md5Hash(password, salt, iter).toBase64(); //加密后转base64

String pwd = new Sha256Hash(password, salt, iter).toString();//sha256加密
String pwd = new Sha256Hash(password, salt, iter).toBase64();//加密后转base64

String pwd = new Sha512Hash(password, salt, iter).toString();//sha256加密
String pwd = new Sha512Hash(password, salt, iter).toBase64()//加密后转base64

7.2 加密

增加用户,或修改用户密码时,涉及到密码的加密

在注册用户的业务中,对用户提交的密码加密即可。

注意:之前的用户表,并未考虑存储加密相关信息,所以此时需要对用户表做出改进,

加一列【 salt varchar(50) 】,用于存储每个用户的盐。

在这里插入图片描述

	class UserServiceImpl implements UserService{
	    @Autowired
	    private UserDAO userDAO;
	    public void createUser(User user){
	        user.setSalt(UUID.randomUUID().toString());//设置随机盐
	        //设置加密属性:sha256算法,随机盐,迭代1000次
	        Sha256Hash sha256Hash = new Sha256Hash(user.getPassword(),user.getSalt(),1000);
	        //将用户信息 (包括密码的密文 和 盐) 存入数据库
	        user.setPassword(sha256Hash.toBase64());//密文采用base64格式化        
	        userDAO.createUser(user);
	    }
	}

登录认证身份时,涉及到密码 比对 过程

注意,加密过程中使用的加密属性,和此处使用的加密属性 必须一致:

sha256,迭代1000次,使用base64格式化密文

7.3.1 指定比对器

[main]
...
#声明密码比对器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName=sha-256
credentialsMatcher.hashIterations=1000
#true=hex格式  false=base64格式
credentialsMatcher.storedCredentialsHexEncoded=false
#比对器关联给realm,则realm中对用户做身份认证时,可以使用加密比对器,对密文做比对
realm1 = com.zhj.realm.MyRealm
realm1.credentialsMatcher=$credentialsMatcher
#realm关联给securityManager
securityManager.realms=$realm1

7.3.2 修改Realm

doGetAuthenticationInfo方法的返回值需要做修改

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    String username = (String) token.getPrincipal();
    UserService userService = 
        (UserService)ContextLoader.getCurrentWebApplicationContext().getBean("userService");
    User user = userService.queryUser(username);
    System.out.println("user:"+user);
    if(user==null){
        System.out.println("用户不存在");
        throw new UnknownAccountException("username:"+username+"不存在");
    }
    //以上逻辑不变
    //在最后返回用户认证info时,添加一个属性:ByteSource.Util.bytes(user.getSalt()) = 盐
    //用于密码比对
    return new SimpleAuthenticationInfo(user.getUsername(),
                                        user.getPassword(), 
                                        ByteSource.Util.bytes(user.getSalt()),
                                        getName());
}

至此,可以进行注册,注册中已经会加密密码。

然后登陆认证身份,认证时realm会调用比对器比对密文。

八、Spring集成

web项目的核心组件都在spring工厂中管理,利用IOC和AOP,组建了关系松散,稳健的系统。

shiro的诸多组件也需要由spring统一管理,进而可以更好的和其他组件协作。

之前的Realm中一直有如下代码:

//由于Realm还未进入spring工厂,所以无法直接注入工厂内部的DAO组件
UserService userService =(UserService)ContextLoader.getCurrentWebApplicationContext().getBean("xx");

ops:shiro的组件都是pojo组件,非常容易用spring管理,可以方便的从ini迁移到spring

8.1 pom

<!-- 其他依赖和web集成中 一致 ,此处省略-->
<!-- 新增一个依赖 用于在工厂中生产 ShiroFilter-->
<!-- 会传递导入shiro-core 和 shiro-web -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>

8.2 aplicationContext.xml

将SecurityManager和Realm和ShiroFilter 都迁移到applicationContext.xml中

建议将如下配置,单独一个配置文件:shiro-spring.xml,然后在applicationContext.xml中引入:

<import resource="classpath:shiro-spring.xml"/>

<!-- 整合mybaits,事务控制等 配置不变 -->
<!-- 添加配置 -->
<!-- shiro -->
<!-- 声明realm -->
<bean id="realm1" class="com.zhj.realm.MyRealm">
    <property name="userService" ref="userService"/>
    <!-- 此属性如果通过注解注入,则需要将注解加载set方法上,不能用在属性上。
 		 此属性是父类属性,所以只有在set方法上注入,才能覆盖父类属性值。
	-->
    <property name="credentialsMatcher">
        <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
            <property name="hashAlgorithmName" value="SHA-256"/>
            <!-- true means hex encoded, false means base64 encoded -->
            <property name="storedCredentialsHexEncoded" value="false"/>
            <property name="hashIterations" value="1000"/>
        </bean>
    </property>
</bean>
<!-- 声明SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="realm1"/>
</bean>
<!-- 生产SpringShiroFilter
     ( 持有shiro的过滤相关规则,可进行请求的过滤校验,校验请求是否合法 ) 
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/user/login/page"/>
    <property name="unauthorizedUrl" value="/error.jsp"/>
    <property name="filterChainDefinitions">
        <value>
            /user/query=anon
            /user/insert=authc,roles["banfu"]
            /user/update=authc,perms[""student:update""]
            /order/insert=authc,roles["xuewei"]
            /user/logout=logout
        </value>
    </property>
</bean>

8.3 web.xml

<!-- 会从spring工厂中获取和它同名的bean,(id="shiroFilter")
     接到请求后调用bean的doFilter方法,进行访问控制。
-->
<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>
<!-- EnvironmentLoaderListener不再需要,因为shiro环境已由spring初始化 
     springMVC,spring配置不变 -->

九、记住我

在登录后,可以将用户名存在cookie中,下次访问时,可以先不登录,就可以识别身份。

在确实需要身份认证时,比如购买,支付或其他一些重要操作时,再要求用户登录即可,用户体验好。

由于可以保持用户信息,系统后台也可以更好的监控、记录用户行为,积累数据。

9.1 代码

”记住我“ 起点在登录时刻:Subject.login(UsernameAndPasswordToken)

而是否确定要“记住我”,由登录时的token控制开关:token.setRememberMe(true);

Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//如果需要记住我的话,需要在token中设置
token.setRememberMe(true);//shiro默认支持”记住我“,只要有此设置则自动运作。
subject.login(token);

9.2 效果

登录后效果

在这里插入图片描述

9.3 页面中显示

在页面中显示,cookie中记录的用户信息

<shiro:user> 当有记住我信息,或已登录,则显示标签体内容

<shiro:principal> 获取用户信息

注意:首页的访问路径的过滤器 不能是 authc,只能是 user 或 anon

<!-- 首页 xx.jsp -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
    <body>
        <h2>Hello World! </h2>
        <!-- 重点在此:通过如下shiro标签显示 -->
        <shiro:user>
            欢迎您,<shiro:principal/>  <a href="#">退出登录</a>
        </shiro:user>
    </body>
</html>
<!-- 登录页面 login.jsp 自动填充用户名 -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <form action="<c:url value="/user/login"/>" method="post">
            <!-- 重点在此:<shiro:principal/> -->
            username:<input type="text" name="username" value="<shiro:principal/>"> <br>
            password:<input type="password" name="password"><br>
            <input type="submit" value="登录">
        </form>
    </body>
</html>

9.4 自定义

如果需要做自定义,可以明确定义如下两个组件:

SimpleCookie:封装cookie的相关属性,定制cookie写出逻辑(详见:addCookieHeader())

CookieRememberMeManager:接受SecurityManager调度,获取用户信息,加密数据,并调度SimpleCookie写出cookie。

<!-- 之前的配置不变,添加如下配置 -->
<!-- remember me -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    <!-- rememberMe是cookie值中的key,value时用户名的密文
		 cookie["rememberMe":"deleteMe"] 此cookie每次登陆后都会写出,用于清除之前的cookie
         cookie["rememberMe":username的密文] 此cookie也会在登录后写出,用于记录最新的username
		(ops: 如上设计,既能保证每次登陆后重新记录cookie,也能保证切换账号时,记录最新账号)
    -->
    <property name="name" value="rememberMe"/>
    <!-- cookie只在http请求中可用,那么通过js脚本将无法读取到cookie信息,有效防止cookie被窃取 -->
    <property name="httpOnly" value="true"/>
    <!-- cookie的生命周期,单位:秒 -->
    <property name="maxAge" value="2592000"/><!-- 30天 -->
</bean>
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
    <!-- 对cookie的value加密的密钥 建议每个项目都不一样
		加密方式AES(对称加密)
		密钥生成:【KeyGenerator keygen = KeyGenerator.getInstance("AES");
        		   SecretKey deskey = keygen.generateKey();
        		   System.out.println(Base64.encodeToString(deskey.getEncoded()));】
		SpEL:Spring Expression Language  #{表达式}  #{T(类型)...}
    
    <property name="cipherKey"
              value="#{T(org.apache.shiro.codec.Base64).decode('c+3hFGPjbgzGdrC+MHgoRQ==')}"/>
	此配置可以省略,CookieRememberMeManager自动完成秘钥生成
	-->
    <!-- 注入SimpleCookie -->
    <property name="cookie" ref="rememberMeCookie"/>
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
	...
    <!-- CookieRememberMeManager注入给SecurityManager,
         SecurityManager在处理login时,如果login成功,则会通过rememberMeManager做"记住我",
         将用户名存入cookie
	-->
    <property name="rememberMeManager" ref="rememberMeManager"/>
</bean>

十、Session管理

shiro作为一款安全管理框架,对状态保持有很强的需要。

比如最常用的用户认证,就必需状态的保持,以及其他的一些功能实现的需要。

【shiro需要:认证中的 记住我中的用户名 正式登陆的用户名】【 开发者需要:其他功能中需要存入session的值 】

shiro提供了一整套session管理方案.

1. shiro的session方案和任何容器无关(如servlet容器);

2. javaSE也可以使用;相关组件都是pojo对ioc极其友好(方便的管理对象和满足依赖关系,定制参数)

3. 可以方便的扩展定制存储位置(内存,缓存,数据库等)

4. 对web透明支持:用了shiro的session后,项目中关于session的代码完全不用任何改动

5. 提供了全面的session监听机制,和session检测机制,对session可以细粒度操作

即,使用了shiro后,采用shiro的session方案是最优的方案。

10.1 javaSE环境 (了解)

shiro 的session管理方案,可以在javaSE中使用,实用价值不大。

Subject subject = SecurityUtils.getSubject();
//获取session
Session session = subject.getSession();
//session超时时间,单位:毫秒;0,马上过期;正数,则空闲对应毫秒后过期;负数,则不会过期
session.setTimeout(10000); 
//session存、取值
session.setAttribute("name","zhj");
session.getAttribute("name");
//获取sessionID
getSession().getId();
//销毁session
session.stop();

原理,核心对象:

1. SimpleSession

​ Session的实现;类,完成session基本功能。

2. SimpleSessionFactory

​ 生产SimpleSession

3. SessionDAO

​ 默认的实现类:MemorySessionDAO,由SessionManager创建,

负责存储所有session对象,存储位置:内存

4. DefaultSessionManager

​ 由SecurityManager创建,负责创建、管理SessionFactory和SessionDAO。

//核心类演示: ( ops:实际开发不用手动创建,shiro会初始化 )
//通过SecurityManager 获得 SessionManager
DefaultSessionManager sessionManager = (DefaultSessionManager)securityManager.getSessionManager();
//通过SessionManager获得SessionFactory
SimpleSessionFactory sessionFactory = (SimpleSessionFactory) sessionManager.getSessionFactory();
//通过SessionManager获得SessionDAO
MemorySessionDAO sessionDAO = (MemorySessionDAO)sessionManager.getSessionDAO();
//通过SessionFactory获得Session
SimpleSession session1 = (SimpleSession) sessionFactory.createSession(null);
//通过Session做具体操作
session1.setAttribute("name","zhangsan");
System.out.println(session1.getAttribute("name"));

10.2 javaEE环境

10.2.1 applicationContext.xml

<!-- 增加session管理相关配置 -->
<!-- 会话Cookie模板 默认可省-->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    <!-- cookie的 key="sid" -->
    <property name="name" value="JSESSIONID"/>
    <!-- 只允许http请求访问cookie -->
    <property name="httpOnly" value="true"/>
    <!-- cookie过期时间,-1:存活一个会话 ,单位:秒 ,默认为-1-->
    <property name="maxAge" value="-1"/>
</bean>

<bean id="sessionManager" 
      class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <!-- 默认值和配置中给出的一致,所bean:sessionIdCookie 可以省略 -->
    <property name="sessionIdCookie" ref="sessionIdCookie"/>
    <!-- session全局超时时间, 单位:毫秒 ,30分钟 默认值为1800000-->
    <property name="globalSessionTimeout" value="1800000"/>
</bean>

<!-- 将sessionManager关联到SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    ...
    <!-- 增加配置sessionManager -->
    <property name="sessionManager" ref="sessionManager"/>
</bean>

10.3 Session监听

session有三个核心过程:创建、过期、停止

​ **过期:**session的默认过期时间为30分钟。通过比对最近一次使用时间和当前使用时间判断

​ session不会自动报告过期,需检测器检测时,或再次访问时,才可以识别是否过期并移除。

​ **停止:**用户主动logout;主动调用session.stop(); 两种情况会将session标志为停止状态。

// 定义监听类 exentends SessionListenerAdapter
public class MySessionListener extends SessionListenerAdapter{
    //当有session创建时 触发
    @Override
    public void onStart(Session session) {
        System.out.println("session:"+session.getId()+" start");
    }
    //当有session停止时 触发
    @Override
    public void onStop(Session session) {
        System.out.println("session:"+session.getId()+" stop");
    }
    //当有session过期时 触发
    // 但不会主动触发,需要再次访问时,即又要使用session时才会发现session过期,并触发。
    @Override
    public void onExpiration(Session session) {
        System.out.println("session:"+session.getId()+" expired");
    }
}

配置监听类,关联给SessionManager

<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    ...
    <property name="sessionListeners">
        <list>
            <bean class="com.zhj.listener.MySessionListener"></bean>
        </list>
    </property>
    ...
</bean>

10.4 Session检测

用户如果没有主动退出登录,只是关闭浏览器,则session是否过期无法获知,也就不能停止session。

为此,shiro提供了session的检测机制,可以定时发起检测,识别session过期 并停止session

<!-- sessionManager默认开启session检测机制 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    ...
    <!-- 开启检测器,默认开启 -->
    <property name="sessionValidationSchedulerEnabled" value="true"/>
    <!--- 检测器运行间隔,单位:毫秒  默认1小时
		    //检测到过期后,会直接将session删除
		    protected void afterExpired(Session session) {
                if (isDeleteInvalidSessions()) {
                    delete(session);
                }
            }
    -->
    <property name="sessionValidationInterval" value="3600000"/>
    ...
</bean>

如上,通过检测器,定时的检测session,并及时移除无效session,释放资源。

十一、注解开发

shiro提供了一系列的访问控制的注解,可以简化开发过程。

<!-- 注解加载Controller中,原理是会对Controller做增强,切入访问控制逻辑,所以需要如下依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.6.RELEASE</version>
</dependency>

11.1 配置mvc.xml

<!-- enable shiro's annotation-->
<bean id="lifecycleBeanPostProcessor" 		
      class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 自动代理生成器,等价于aop:config;
     aop:config 或 AutoProxyCreator两者选其一,spring官方提醒千万不要同时使用。

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" 
      depends-on="lifecycleBeanPostProcessor"/>
-->
<aop:config></aop:config>
<!-- 在此bean的构建过程中,初始化了一些额外功能和piontcut
     interceptors.add(new RoleAnnotationMethodInterceptor(resolver));
     interceptors.add(new PermissionAnnotationMethodInterceptor(resolver));
     interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver));
     interceptors.add(new UserAnnotationMethodInterceptor(resolver));
     interceptors.add(new GuestAnnotationMethodInterceptor(resolver));
-->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
	<property name="securityManager" ref="securityManager"/>
</bean>

11.2 注解使用

加在类上

@Controller
@RequestMapping("/user")
@RequiresAuthentication //类中的所有方法都需要身份认证
@RequiresRoles(value={"manager","admin"},logical= Logical.OR)//类中的所有方法都需要角色,"或"
public class ShiroController {
	...
}

加在方法上

@Controller
@RequestMapping("/user")
public class ShiroController2{
	...
    @RequiresPermissions({"user:query”,“user:delete"}) //有对应权限,默认是 "且"
    @RequiresUser //记住我 或 已身份认证
    @RequiresGuest //游客身份
    public String hello(){
		...
    }
}

配置修改

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <!-- 不再需要,此时如果身份或权限不通过,会抛出异常,需要异常解析器处理
    <property name="loginUrl" value="/user/login/page"/>
    <property name="unauthorizedUrl" value="/error.jsp"/>
    <property name="filterChainDefinitions">
        <value>
            /user/query=anon  如下不再需要,登出可以保留,也可以自己写handler中subject.logout()
            /user/insert=authc,roles["banfu"]
            /user/update=authc,perms[""student:update""]
            /order/insert=authc,roles["xuewei"]
            /user/logout=logout
        </value>
    </property>-->
</bean>

11.3 异常处理

<!-- mvc.xml中:MVC的自定义异常处理器,用于处理权限或身份认证不通过时的异常处理-->
<bean class="com.zhj.ex.handler.MyExHandler"></bean>
public class MyExHandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse 
                                         response, Object handler, Exception ex) {
        ModelAndView mv = new ModelAndView();
        ex.printStackTrace();
        mv.setViewName("redirect:/login.jsp");
        return mv;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值