Hello shiro+springmvc实例--shiro加强
转载请注明出处:
牵手生活--简书:笔记是整理思路方式,分享是一个美德,牵手是我的生活方式
注:此文承接上一文: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)
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)
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