Shiro程序员ssh

Hello shiro+springmvc实例--shiro加强

2018-04-22  本文已影响72人  牵手生活

转载请注明出处:
牵手生活--简书:笔记是整理思路方式,分享是一个美德,牵手是我的生活方式

注:此文承接上一文:Hello shiro基础知识整理---shiro基础篇下面开始我们的工作

知识要点:

  • spring + springmvc
  • shiro(自定义realm、SecurityManager环境、HashedCredentialsMatcher加密、>提交认证授权请求subject)
  • spring jdbc访问数据库
  • 阿里数据源Druid(DruidDataSource)
  • spring 的aop操作需要引入aspectj切面框架支持的包aspectjweaver
  • shiro自定义过滤器
  • Redis知识及Redis的访问工具包Jedis
  • 反序列化SerializationUtils.deserialize方法&序列化SerializationUtils.serialize
  • Redis桌面管理工具Redis Desktop Manager

shiro授权过程

shiro授权过程

shiro知识简图

shiro知识简图

Shiro集成Spring

Shiro集成Spring

配置maven web工程的pom.xml,添加对spring mvc和shiro的支持



      <!--spring 的包-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--springmvc的包-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>${spring.version}</version>
      </dependency>


    <!--shiro部分-->
    <!--导入shiro的核心包-->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.4.0</version>
    </dependency>

    <!--导入shiro的spring包-->

    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.4.0</version>
    </dependency>
    <!--导入shiro的web包-->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.4.0</version>
    </dependency>

配置maven的web工程的web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
    http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <display-name>Archetype Created Web Application</display-name>

    <!--shiro的配置-->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>


  <!-- Spring应用上下文, 理解层次化的ApplicationContext -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:spring/spring.xml</param-value>
  </context-param>

  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>


  <!-- DispatcherServlet, Spring MVC的核心 -->
  <servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class> org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- DispatcherServlet对应的上下文配置, 默认为/WEB-INF/$servlet-name$-servlet.xml
     -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath*:/spring/springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>mvc-dispatcher</servlet-name>
    <!-- mvc-dispatcher拦截所有的请求-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>




  
</web-app>


集成Shiro 和Springmvc后的目录结构
集成Shiro 和Springmvc后的目录结构
Spring的配置文件Spring.xml(shiro的Filter、SecurityManager、自定义realm、加密设置HashedCredentialsMatcher)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <!--创建shiro 的filter对象-->
    <!--<bean id="shiroFilter" class="org.apache.shiro.web.ShiroFilterFactoryBean"-->

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" >
        <!--设置登录页的,未登录页及securityManager对象;及过滤器链-->
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="login.html"/>
        <property name="unauthorizedUrl" value="403.html"/>
        <!--过滤器链,从上往下进行匹配,这时不需要认证anon与需要认证authc-->
        <property name="filterChainDefinitions" >
            <value>
                /login.html = anon
                /subLogin = anon
                /* = authc
            </value>

        </property>
        
        
    </bean>

    <!--创建shiro所需要的SecurityManager对象,并设置realm属性为自定义realm-->
    <bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
        <!--这时为自定义realm-->
        <property name="realm" ref="realm"/>

    </bean>
    <!--创建自定义realm类CustomRealm-->
    <bean id="realm" class="com.younghare.shiro.realm.CustomRealm">
        <!--设置自定义的realm采用的加密算法-->
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
        
    </bean>

    <!--创建加密算法-->
    <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher" id="credentialsMatcher">
        <!--设置加密算法-->
        <property name="hashAlgorithmName" value="md5"/>
        <!--设置加密次数-->
        <property name="hashIterations" value="1"/>
        
    </bean>

</beans>
SpringMVC的配置文件springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 启用Spring基于annotation的DI, 使用户可以在Spring MVC中使用Spring的强大功能。 激活 @Required
@Autowired,JSR 250's @PostConstruct, @PreDestroy and @Resource 等标注 -->
    <context:annotation-config />

    <!-- DispatcherServlet上下文, 只管理@Controller类型的bean, 忽略其他型的bean, 如@Service -->
    <!--确定扫描路径-->
    <context:component-scan base-package="com.younghare.controller">
<!--

        <context:include-filter type="annotation"
                                expression="org.springframework.stereotype.Controller" />
-->

    </context:component-scan>
    
    <!-- 扩充了注解驱动,可以将请求参数绑定到控制器参数 -->
    <mvc:annotation-driven />

    <!-- spring mvc 静态文件排除 -->
    <!--这个在hello world项目中没有 后面的location="/resources/" 通常用于存放css、js、image、html-->
    <mvc:resources mapping="/*" location="/" />

</beans>
创建springmvc中的Controller

@Controller /*注解这是一个Controller*/
public class UserController {

    @RequestMapping(value = "/subLogin",method = RequestMethod.POST,
    produces = "application/json;charset=utf-8")
    @ResponseBody
    public String subLogin(User user){//通过实体对象传递参数
        /*获得主体*/
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken taken = new UsernamePasswordToken(user.getUsername(),user.getPassword());

        try {
            subject.login(taken);
        } catch (AuthenticationException e) {
            //e.printStackTrace();
            return e.getMessage();
        }

        return "登录成功";
        
    }
}

部署并运行web项目

url(用户名:younghare;密码:123456)

http://localhost:8080/login.html
登录界面

登录成功界面


登录成功界面

登录失败界面
提示:Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - younghare, rememberMe=false] did not match the expected credentials.

登录失败界面

Shiro集成Spring 从数据库中获取数据对象(spring-jdbc、数据源DruidDataSource)

配置pom.xml(添加mysql驱动包、druid数据源、spring-jdbc)

spring核心包在前面已经导入了

      <!--引入数据源-->
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.1.6</version>
      </dependency>
      <!-- mysql驱动包-->
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>6.0.6</version>
      </dependency>

    <!--spring jdbc访问数据库-->

      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>${spring.version}</version>
      </dependency>

创建spring jdbc的配置文件spring-jdbc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--Spring 数据源对象,并设置url用户名、密码-->
    <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
        <property name="url" value="jdbc:mysql://47.**.**.27:3306/auth?serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="你的密码"/>

    </bean>

    <!--创建JdbcTemplate对象,并设置属性-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
        
    </bean>

</beans>
修改Spring.xml(引入Spring-jdbc.xml配置文件、设置扫描路径)
    <!--引入spring-jdbc的配置文件-->
    <import resource="spring-jdbc.xml"/>
    <!--设置扫描路径-->
    <context:component-scan base-package="com.younghare"/>

引入其他Spring配置文件

创建Dao,DaoImpl、修改Controller、修改realm后的结构

创建Dao,DaoImpl、修改Controller、修改realm后的结构
UserDao(访问数据库)
public interface UserDao {
    User getUserByUsername(String userName);

    List<String> queryRolesByUserName(String userName);
}

UserDaoImpl(访问数据库实现类)
package com.younghare.dao.impl;

import com.younghare.dao.UserDao;
import com.younghare.vo.User;
import org.apache.shiro.util.CollectionUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

@Component /*让他实现成对象*/
public class UserDaoImpl implements UserDao{
    @Resource/*注入jdbcTemplate*/
    private JdbcTemplate jdbcTemplate;
    @Override
    public User getUserByUsername(String userName) {
        String sql = "select username,password from users where username = ?";
        List<User> list = jdbcTemplate.query(sql, new String[]{userName}, new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet resultSet, int i) throws SQLException {
                User user = new User();
                user.setUsername(resultSet.getString("username"));
                user.setPassword(resultSet.getString("password"));
                return user;
            }
        });

        if (CollectionUtils.isEmpty(list)){
            return null;
            
        }
        return list.get(0); //直接返回第一条
    }

    @Override
    public List<String> queryRolesByUserName(String userName) {
        String sql = "select role_name from test_user_role where user_name = ?";
        return  jdbcTemplate.query(sql, new String[]{userName}, new RowMapper<String>() {
            @Override
            public String mapRow(ResultSet resultSet, int i) throws SQLException {
                return resultSet.getString("role_name");
            }
        });

    }
}

修改CustomRealm

public class CustomRealm extends AuthorizingRealm {

    @Resource /*注入userDao*/
    private UserDao userDao;//用户的数据库信息


    @Override //授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String userName = (String) principalCollection.getPrimaryPrincipal();
        //从数据库或缓冲中获得角色数据
        Set<String> roles = getRolesByUserName(userName);
        //从数据库或缓冲中获得权限数据
        Set<String> permissions = getPermissionsByUserName(userName);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setStringPermissions(permissions);
        simpleAuthorizationInfo.setRoles(roles);

        return simpleAuthorizationInfo;
    }

    /**
     * 模拟用户权限数据
     * @param userName
     * @return
     */
    private Set<String> getPermissionsByUserName(String userName) {
        Set<String> sets = new HashSet<>();
        sets.add("user:myAdd");
        sets.add("user:delete");
        sets.add("user:update");
        return sets;

    }

    /**
     * 角色数据
     * @param userName
     * @return
     */
    private Set<String> getRolesByUserName(String userName) {

        List<String> list = userDao.queryRolesByUserName(userName);
        Set<String> sets = new HashSet<>(list);

        return sets;
    }

    @Override //认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        /*从主体传来的认证信息中,获得用户名*/
        String userName = (String) authenticationToken.getPrincipal();
        /*通过用户名到数据库中获取凭证*/
        String password = getPasswordByUserName(userName);
        if (password == null){
            return null;
        }

        SimpleAuthenticationInfo authenticationInfo
                = new SimpleAuthenticationInfo(userName,password,"customRealm");

        //如果有对shiro加密并加盐了,则需要添加加盐的处理
        authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userName));

        return authenticationInfo;
    }

    /**
     * 数据库操作
     * @param userName
     * @return
     */
    private String getPasswordByUserName(String userName) {
        /*通过Dao到数据库中获取用户信息*/
        User user = userDao.getUserByUsername(userName);
        if (user !=null){
            System.out.println(userName+":"+user.getPassword());
            return user.getPassword();

        }

        return null;
    }


}

修改UserController

@Controller /*注解这是一个Controller*/
public class UserController {

    @RequestMapping(value = "/subLogin",method = RequestMethod.POST,
    produces = "application/json;charset=utf-8")
    @ResponseBody
    public String subLogin(User user){//通过实体对象传递参数
        /*获得主体*/
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken taken = new UsernamePasswordToken(user.getUsername(),user.getPassword());

        try {
            subject.login(taken);
        } catch (AuthenticationException e) {
            //e.printStackTrace();
            return e.getMessage();
        }

        if (subject.hasRole("admin")){
            return "登录成功--有admin权限***";
        }

        return "登录成功-无admin权限";
        
    }
}

重新部署web应用,并登陆的效果
成功登录

失败的效果没有变化

通过注解配置授权(aspectjweaver包)

修改pom.xml(引入aspectjweaver包)
      <!--通过注解配置授权 aspectjweaver包===shiro可能会用到 (属于spring aop操作用到的的包,属于的基础知识)-->
      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.8.9</version>
      </dependency>
修改spring.xml(添加对aspectjweaver的支持)
    <aop:config proxy-target-class="true"/>
    <!--创建一个LifecycleBeanPostProcessor对象-->
    <bean class="org.apache.shiro.spring.LifecycleBeanPostProcessor">
        
    </bean>
    <!--创建一个aspectjweaver授权生效的对象,并注入securityManager-->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
        
    </bean>
添加对aspectjweaver支持
为UserController.java 添加2个方法(注解的方式配置授权)

    @RequiresRoles("admin") /*表示当前的主体必须具备admin权限才能访问,数组可以传入多个参数*/
    @RequestMapping(value = "/testRole",method = RequestMethod.GET)
    @ResponseBody /*信息放回,http 访问或ajax访问*/
    public String testRole(){
        return "testRole success";
    }

    @RequiresRoles("admin1") /*表示当前的主体必须具备admin权限才能访问*/
    @RequiresPermissions("user:add") /*表示必须具备对user的add权限*/
    @RequestMapping(value = "/testRole1",method = RequestMethod.GET)
    @ResponseBody /*信息放回,http 访问或ajax访问*/
    public String testRole1(){
        return "testRole success";
    }

用younghare用户登录成功后,在浏览器中直接访问

http://localhost:8080/testRole

问题:Caused by: java.lang.NoClassDefFoundError: org/aspectj/util/PartialOrder$PartialComparable
解决办法:原来是我的2个方法都是 @RequestMapping(value = "/testRole",method = RequestMethod.GET)

测试testRole结果

Shiro过滤器与自定义过滤器

认证相关的过滤器

  • anon:不需要任何认证
  • authBasis
  • authc:认证之后才可以访问
  • user:需要当前存在用户才可以访问
  • logout:退出

授权相关的过滤器:

  • perms:具备相关的一些权限才可以进行访问"["+参数+"[]"
  • roles:同时具备相关的一些角色才可以进行访问"["+参数+"[]"
  • rolesOr:只要具备相关的角色的一个就可以进行访问"["+参数+"[]"
  • ssl:要求安全的协议https
  • port:要求端口"[]"中写的是端口
在Spring.xml中配置shiro的过滤器
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" >
        <!--设置登录页的,未登录页及securityManager对象;及过滤器链-->
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="login.html"/>
        <property name="unauthorizedUrl" value="403.html"/>
        <!--过滤器链,从上往下进行匹配,这时不需要认证anon与需要认证authc-->
        <property name="filterChainDefinitions" >
            <value>
                /login.html = anon
                /subLogin = anon
                /testMyRoles = roles["admin"]
                /testMyRoles2 = roles["admin","admin1]
                /testPerms = perms["user:delete"]
                /testPerms2 = perms["user:delete","user:update"]
                /* = authc
            </value>

        </property>        
        
    </bean>
在Spring.xml中配置shiro的过滤器 image.png

shiro自定义filter过滤器

创建shiro自定义filter过滤器RolesOrFilter.java

需要继承的filter来源


需要继承的filter来源

创建自定义过滤器RolesOrFilter.java


public class RolesOrFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed(javax.servlet.ServletRequest servletRequest
            , javax.servlet.ServletResponse servletResponse, Object object) throws Exception {
        
        /*获得当前的主体*/
        Subject subject = getSubject(servletRequest,servletResponse);
        String[] roles = (String[]) object;
        if (roles == null || roles.length == 0){
            return true;
        }
        for (String role :roles){
            if (subject.hasRole(role)){
                return  true;
            }
        }

        return false;
    }
}
在spring.xml的配置文件中配置自定义过滤器

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" >
        <!--设置登录页的,未登录页及securityManager对象;及过滤器链-->
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="login.html"/>
        <property name="unauthorizedUrl" value="403.html"/>
        <!--过滤器链,从上往下进行匹配,这时不需要认证anon与需要认证authc-->
        <property name="filterChainDefinitions" >
            <value>
                /login.html = anon
                /subLogin = anon
                /testMyRoles = roles["admin","admin1"]
                /testMyRoles2 = rolesOr["admin","admin1"]
                /* = authc
            </value>
        </property>
        <property name="filters">
            <util:map>
                <!--roleOr的key值指定我们自定义的shiro的filter-->
                <entry key="rolesOr" value-ref="rolesOrFilter"/>
            </util:map>
        </property>
        
        
    </bean>

    <!--创建自定义shiro过滤器的实例-->
    <bean class="com.younghare.filter.shiro.RolesOrFilter" id="rolesOrFilter"/>
自定义shiro 的filter配置
重新部署web,并按younghare登录(具有admin,user角色)

登录成功后访问url

http://localhost:8080/login.html
http://localhost:8080/testMyRoles

![必须登录成功](https://img.haomeiwen.com/i5438896/
767bdeefd5af93ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

rolesOr拥有角色访问的情况 roles同时拥有角色
image.png
补上我们的403.html资源文件即可

省略

Shiro会话管理(session)和缓冲管理

Shiro会话管理

在pom.xml manen配置文件中添加redis的访问工具jedis
    <!--添加redis的访问工具jedis-->
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>2.9.0</version>
      <type>jar</type>
      <scope>compile</scope>
    </dependency>
通过Redis访问工具,需要修改增删改查的操作。创建RedisSessionDao并继承AbstractSessionDAO,同时实现对应的抽象方法
创建spring 配置文件管理spring-redis.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <!--创建JedisPool实例-->
    <bean class="redis.clients.jedis.JedisPool" id="jedisPool">
        <!--配置方法1:不知道什么原因这样配置会,产生一大堆的错误循环创建之类-->
        <!--<constructor-arg ref="jedisPoolConfig"/>
        <constructor-arg value="127.0.0.1"/>
        <constructor-arg value="6379"/>-->

        <!--配置方法2:后来修改为这方法配置-->
        <constructor-arg name="poolConfig" ref="jedisPoolConfig" /> <!-- 加载jedisPool配置信息 -->
        <constructor-arg name="host" value="127.0.0.1" /><!-- redis主机地址 -->
        <constructor-arg name="port" value="6379"/> <!-- redis连接端口 -->
    </bean>
    <!--创建JedisPoolConfig对象,在JedisPool中需要用到-->
    <bean class="redis.clients.jedis.JedisPoolConfig" id="jedisPoolConfig"/>

</beans>
修改spring.xml(引入spring-redis.xml)

    <!--引入spring-redis的配置文件-->
    <import resource="spring-redis.xml"/>
创建访问Redis的工具类JedisUtil

JedisUtil.java

/**
 * Redis的访问工具包,主要是Jedis的一些操作方法
 */

@Component
public class JedisUtil {
    //JedisPool jedisPool = new JedisPool(config,"127.0.0.1",6379);
    @Autowired /*自动注入与@Resource注入的区别*/
    private JedisPool jedisPool;

    /*放回获取连接Redis的方法*/
    private Jedis getResource(){
        return jedisPool.getResource();
    }

    public byte[] set(byte[] key, byte[] value) {
        Jedis jedis = getResource();

        try {
            jedis.set(key,value);
            return  value;
        } finally {
            jedis.close();
        }

        
    }

    /**
     *
     * @param key
     * @param expire 设置过期时间,单位秒
     */
    public void expire(byte[] key, int expire) {
        Jedis jedis = getResource();

        try {
            jedis.expire(key,expire);
        } finally {
            jedis.close();
        }
    }

    public byte[] get(byte[] key) {
        Jedis jedis = getResource();

        try {
            return jedis.get(key);
        } finally {
            jedis.close();
        }
    }

    public void del(byte[] key) {
        Jedis jedis = getResource();

        try {
            jedis.del(key);
        } finally {
            jedis.close();
        }
    }

    public Set<byte[]> keys(String shiro_session_prefix) {
        Jedis jedis = getResource();

        try {
            return jedis.keys((shiro_session_prefix+"*").getBytes());
        } finally {
            jedis.close();
        }
    }
}
创建RedisSessionDao用于管理session,继承AbstractSessionDAO并实现对应的抽象方法

RedisSessionDao.java


public class RedisSessionDao extends AbstractSessionDAO {
    
    @Resource /*注入jedisUtil对象*/
    private JedisUtil jedisUtil;
    /*定义session的前缀*/
    private  final String shiro_session_prefix ="younghare-session";
    private byte[] getKey(String key){
        return (shiro_session_prefix+key).getBytes();
    }

    //保存session
    private void saveSession(Session session){
        if (session !=null && session.getId() !=null){
            byte[] key = getKey(session.getId().toString());
            /*序列化后才去保存,读取时需要反序列化回来*/
            byte[] value = SerializationUtils.serialize(session);
            jedisUtil.set(key,value);//保存到Redis中
            jedisUtil.expire(key,10*60); //过期时间时间10分钟

        }

    }

    //创建session
    @Override
    protected Serializable doCreate(Session session) {
        /*获得sessionId*/
        Serializable sesssionId = generateSessionId(session);
        saveSession(session);

        return sesssionId;
    }

    //获得session
    @Override
    protected Session doReadSession(Serializable sessionId) {
        if (sessionId == null){
            return null;
        }
        byte[] key = getKey(sessionId.toString());
        System.out.println("读取session:"+new String(key));
        byte[] value = jedisUtil.get(key);
        /*返回系列化对象*/
        return (Session) SerializationUtils.deserialize(value);

    }

    //更新sesssion
    @Override
    public void update(Session session) throws UnknownSessionException {
        saveSession(session);

    }

    //删除session
    @Override
    public void delete(Session session) {
        if (session == null || session.getId() == null){
            return;
        }

        byte[] key = getKey(session.getId().toString());
        jedisUtil.del(key);

    }

    //获得到指定存活的session
    @Override
    public Collection<Session> getActiveSessions() {
        /*通过前缀获取所以的key值*/
        Set<byte[]> keys = jedisUtil.keys(shiro_session_prefix);

        Set<Session> sessions = new HashSet<>();
        if (CollectionUtils.isEmpty(keys)){
            return sessions;
        }

        for(byte[] key:keys){
            /*反序列化为Session对象*/
            Session session = (Session) SerializationUtils.deserialize(jedisUtil.get(key));
            sessions.add(session);
            
        }
        

        return sessions;
    }
}

修改Spring.xml(创建DefaultWebSessionManager对象,并配置到自定义的RedisSessionDao对象)
    <!--创建shiro的DefaultWebSessionManager对象-->
    <bean class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager" id="sessionManager">
        <!--指向我们自定义的RedisSessionDao对象-->
        <property name="sessionDAO" ref="redisSessionDao"/>
        

    </bean>
    <!--创建自定义的RedisSessionDao对象,DefaultWebSessionManager中要指向到这来-->
    <bean class="com.younghare.session.RedisSessionDao" id="redisSessionDao"/>

修改spring.xml(在DefaultWebSecurityManager设置session管理对象)
在DefaultWebSecurityManager设置session管理对象
    <!--创建shiro所需要的SecurityManager对象,并设置realm属性为自定义realm-->
    <bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
        <!--这时为自定义realm-->
        <property name="realm" ref="realm"/>
        <!--指定session管理对象-->
        <property name="sessionManager" ref="sessionManager"/>

    </bean>
重新部署运行web(注意要驱动Redis服务)

用younghare登录,访问之前的一些url

//登录
http://localhost:8080/login.html
//角色测试url2
http://localhost:8080/testMyRoles2
//角色测试url
http://www.localhost.com:8080/testMyRoles
用Redis桌面管理工具Redis Desktop Manager连接查看
image.png 多个浏览器访问的情况

日志情况


日志情况
doReadSession被多次调用的优化

问题:根据日志情况发现一次访问,会调用多次的doReadSession
解决:查看DefaultWebSessionManager的父类DefaultSessionManager的retrieveSession方法,看看它是如何处理的


image.png

创建一个是定义的sessionManager类CustomSessionManager,并重写retrieveSession方法


public class CustomSessionManager extends DefaultWebSessionManager {
    @Override //sessionKey中存储的是request对象
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        Serializable sessionId = getSessionId(sessionKey);

        //先冲request中去session,取不到我们在到redis中取,然后在设置到request中
        ServletRequest request = null;
        if (sessionKey instanceof  WebSessionKey){
            request = ((WebSessionKey) sessionKey).getServletRequest();
        }

        if (request != null && sessionId != null){
            Session session = (Session) request.getAttribute(sessionId.toString());
            if (session !=null){
                return session;
            }
        }

        Session session = super.retrieveSession(sessionKey);
        if (request != null && sessionId !=null){
            request.setAttribute(sessionId.toString(),session);

        }

        return session;
        
    }
}

在spring中创建sessionManager对象修改为我们自己的

用上自己的sessionManager
    <!--创建shiro的DefaultWebSessionManager对象-->
    <!--<bean class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager" id="sessionManager">-->
    <bean class="com.younghare.session.CustomSessionManager" id="sessionManager">
        <!--指向我们自定义的RedisSessionDao对象-->
        <property name="sessionDAO" ref="redisSessionDao"/>

    </bean>

重写部署运行web,并访问,查看日志

用自己的sessionManager后就没有重复打印

Shiro缓冲管理(CacheManager接口、Cache)

目标:用来缓冲角色数据和权限数据,这样在每次授权是,就不需要每次都到数据库去查询

RedisCache实现Cache<K,V>接口
/*K key;V :value*/
public class RedisCache<K,V> implements Cache<K,V> {
    @Resource /*注入jedisUtil*/
    private JedisUtil jedisUtil;
    private final String CACHE_PRIFIX = "younghare:";

    private byte[] getKey(K k){
      if (k instanceof String){
          return (CACHE_PRIFIX+k).getBytes();
      }
      return SerializationUtils.serialize(k);
    };


    @Override
    public V get(K k) throws CacheException {
        byte[] value = jedisUtil.get(getKey(k));
        if (value != null){
            return (V) SerializationUtils.deserialize(value);//反序列化
        }


        return null;
    }

    @Override
    public V put(K k, V v) throws CacheException {
        byte[] key = getKey(k);
        byte[] value = SerializationUtils.serialize(v);
        jedisUtil.set(key,value);
        jedisUtil.expire(key,10*60); //十分钟

        return v;
    }

    @Override
    public V remove(K k) throws CacheException {
        byte[] key = getKey(k);
        byte[] value = jedisUtil.get(key);
        jedisUtil.del(key);
        if (value != null){
            return (V) SerializationUtils.deserialize(value);
        }

        return null;
    }

    @Override
    public void clear() throws CacheException {
        /*还没有开始实现*/

    }

    @Override
    public int size() {
        /*还没有开始实现*/
        return 0;
    }

    @Override
    public Set<K> keys() {
        /*还没有开始实现*/
        return null;
    }

    @Override
    public Collection<V> values() {
        /*还没有开始实现*/
        return null;
    }
}

修改spring.xml(创建RedisCacheManager对象,在DefaultWebSecurityManager对象中注入RedisCacheManager对象)
    <!--创建RedisCacheManager对象-->
    <bean class="com.younghare.cache.RedisCacheManager" id="cacheManager"/>
注入RedisCacheManager对象
重新部署运行web,登录并校验
RedisCacheManager实现CacheManager接口

注意观察从数据库&从redis获取的权限数据

Shiro自动登录

修改spring.xml(创建自动登录对象cookieRememberMeManager,SimpleCookie,配置修改DefaultWebSecurityManager对象的rememberMeManager)
    <!--自动登录部分CookieRememberMeManager对象-->
    <bean class="org.apache.shiro.web.mgt.CookieRememberMeManager" id="cookieRememberMeManager">
        <property name="cookie" ref="cookie"/>

    </bean>
    <!--自动登录SimpleCookie-->

    <bean class="org.apache.shiro.web.servlet.SimpleCookie" id="cookie">
        <!--设置构造器-->
        <constructor-arg value="rememberMe"/>
        <!--cookie存活的时间,秒-->
        <property name="maxAge" value="20000000"/>
    </bean>
修改DefaultWebSecurityManager对象的rememberMeManager
修改UserController中的登录方法subLogin,添加是否记住
登录时taken设置是否记住
重新部署web
image.png 浏览器开发者工具看到 image.png
上一篇 下一篇

猜你喜欢

热点阅读