获取课程代码
-
什么是Shiro
- Apache的强大灵活的开源安全框架
- 认证、授权、企业会话管理、安全加密
-
Shiro 与 Spring Security 比较
- Apache Shiro
- 简单、灵活
- 可脱离Spring
- 粒度较粗
- Spring Security
- 复杂、笨重
- 不可脱离Spring
- 粒度更细
- Apache Shiro
Authenticator认证器:登录登出
Authorizer授权器:赋予主体有哪些权限
Session Manager - Session管理器:Shiro自身实现的Session管理机制,可以在不借助任何Web容器的情况下使用Session。
Session DAO - Session的操作:CRUD
Cache Manager缓存管理器:缓存角色数据和权限数据
Realms:Shiro和数据库、数据源的一个桥梁,Shiro获取认证信息、权限数据、角色数据都是通过该部分。
主体提交请求,到Security Manager调用Authenticator认证;Authenticator获取认证数据是通过Realms获取,从数据库获取的认证信息和主体提交的认证数据去做比对;
主体赋予权限也是通过Realms从数据库或者缓存中来获取我们的角色数据或权限数据。
Cryptography加密:可以使用其快速加密数据。
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;
public class AuthenticationTest {
// 指定我们的Reamls
SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm() ;
@Before
public void addUser() {
simpleAccountRealm.addAccount("Mark", "123456"); // 模拟查询出用户名
simpleAccountRealm.addAccount("Admin", "123456","admin"); // 模拟查询出用户名及权限,角色可以有多个
}
@Test
public void loginAuthentication() {
// 1、构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm); // 将Realm设置到SecurityManager环境中
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要设置SecurityManager环境
// 2、主体提交认证请求(Subject)
Subject subject = SecurityUtils.getSubject() ;
UsernamePasswordToken token = new UsernamePasswordToken("Mark","123456") ;
subject.login(token);
// 3、通过SecurityManager认证
boolean flag = subject.isAuthenticated() ; // 是否通过认证,SecurityManager通过Authenticator 认证,Authenticator获取Realm,通过Realm 取认证数据
System.out.println("isAuthenticated:" + flag);
subject.logout(); // 退出
System.out.println("logout isAuthenticated:" + subject.isAuthenticated());
}
}
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;
/**
*
* @author Administrator
*
*/
public class AuthenticationTest {
// 指定我们的Reamls
SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm() ;
@Before
public void addUser() {
simpleAccountRealm.addAccount("Mark", "123456"); // 模拟查询出用户名
simpleAccountRealm.addAccount("Admin", "123456","admin"); // 模拟查询出用户名及权限,角色可以有多个
}
@Test
public void powerAuthentication() {
// 1、构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm); // 将Realm设置到SecurityManager环境中
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要设置SecurityManager环境
// 2、主体提交认证请求(Subject)
Subject subject = SecurityUtils.getSubject() ;
UsernamePasswordToken token = new UsernamePasswordToken("Admin","123456") ;
subject.login(token);
// 3、通过SecurityManager认证
boolean flag = subject.isAuthenticated() ; // 是否通过认证,SecurityManager通过Authenticator 认证,Authenticator获取Realm,通过Realm 取认证数据
System.out.println("isAuthenticated:" + flag);
subject.checkRole("admin"); // 检查用户是否具备该角色,可以检查多个
}
}
- Shiro自定义Realm
- 内置Realm:
- IniRealm
- JdbcRealm
- 内置Realm:
user.ini文件
[users]
Mark=123456,admin // 用户名=密码,角色
[roles]
admin=user:delete // 角色=拥有删除用户的权限
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
public class IniRealmTest {
@Test
public void iniRealmAuthentication() {
IniRealm iniRealm = new IniRealm("classpath:user.ini"); // ini文件的路径
// 1、构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(iniRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要设置SecurityManager环境
// 2、主体提交认证请求(Subject)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("Mark", "123456");
subject.login(token);
// 3、通过SecurityManager认证
boolean flag = subject.isAuthenticated(); // 是否通过认证,SecurityManager通过Authenticator
// 认证,Authenticator获取Realm,通过Realm 取认证数据
System.out.println("isAuthenticated:" + flag);
subject.checkRole("admin"); // 验证角色
subject.checkPermission("user:delete "); // 验证权限
subject.checkPermission("user:update");
}
}
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import com.alibaba.druid.pool.DruidDataSource;
public class JdbcRealmTest {
DruidDataSource dataSource = new DruidDataSource();
{
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("123456");
}
@Test
public void jdbcRealmAuthentication() {
JdbcRealm jdbcRealm = new JdbcRealm(); // 需要访问数据库
// 设置数据源
jdbcRealm.setDataSource(dataSource);
jdbcRealm.setPermissionsLookupEnabled(true); // 使用JDBC需要设置权限开关,否则即使配置权限仍不能认证。默认为false。
// 自定义sql查询认证与权限,不设置则使用jdbcRealm本身的查询语句
String sql = "select password from test_user where user_name = ?";
jdbcRealm.setAuthenticationQuery(sql); // 使用自定义sql
String roleSql = "select role_name from test_role_user where user_name = ?";
jdbcRealm.setUserRolesQuery(roleSql);
// 1、构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(jdbcRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要设置SecurityManager环境
// 2、主体提交认证请求(Subject)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("Mark", "123456");
subject.login(token);
// 3、通过SecurityManager认证
boolean flag = subject.isAuthenticated(); // 是否通过认证,SecurityManager通过Authenticator
// 认证,Authenticator获取Realm,通过Realm 取认证数据
System.out.println("isAuthenticated:" + flag);
subject.checkRole("admin"); // 验证角色
subject.checkPermission("user:delete"); // 验证权限
subject.checkPermission("user:update");
}
}
Shiro加密
- Shiro散列配置
- HashedCredentialsMatcher
- 自定义Realm中使用散列
- 盐的使用
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
/**
* 自定义Realm
*
* @author Administrator
*
*/
public class CustomRealm extends AuthorizingRealm {
Map userMap = new HashMap<>();
{
userMap.put("Mark", "283538989cef48f3d7d8a1c1bdf2008f"); // Md5解密后的密文
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String) principals.getPrimaryPrincipal();
// 从数据库或缓存中获取角色数据
Set roles = getRelosByUserName(userName);
Set permissions = getPermissionsByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
/**
* 模拟数据库获取角色数据
*
* @param userName
* @return
*/
private Set getRelosByUserName(String userName) {
Set roles = new HashSet<>();
roles.add("admin");
roles.add("user");
return roles;
}
/**
* 模拟数据库获取权限数据
*
* @param userName
* @return
*/
private Set getPermissionsByUserName(String userName) {
Set permissions = new HashSet<>();
permissions.add("user:delete");
permissions.add("user:update");
return permissions;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1、通过主体传来的认证信息中,获取用户名
String userName = (String) token.getPrincipal();
// 2、通过用户名到数据库中获取凭证
String password = getPasswordByUserName(userName);
if (password == null) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("Mark", password, "customRealm");
// 密码加密设置盐,此时需要设置盐
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userName));
return authenticationInfo;
}
/**
* 模拟数据库查询凭证
*
* @param userName
* @return
*/
private String getPasswordByUserName(String userName) {
return userMap.get(userName);
}
public static void main(String[] args) {
// Md5Hash md5Hash = new Md5Hash("123456") ; // 单纯加密
Md5Hash md5Hash = new Md5Hash("123456","Mark") ; // 加密,加盐
System.out.println(md5Hash.toString());
}
}
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import com.shiro.pro.CustomRealm;
public class CustomRealmTest {
@Test
public void customRealmAuthentication() {
CustomRealm customRealm = new CustomRealm();
// 1、构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(customRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager); // 先要设置SecurityManager环境
// 加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher() ;
matcher.setHashAlgorithmName("md5"); // 设置算法名称
matcher.setHashIterations(1); // 设置加密次数
customRealm.setCredentialsMatcher(matcher); // 设置加密的HashedCredentialsMatcher对象
// 2、主体提交认证请求(Subject)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("Mark", "123456");
subject.login(token);
// 3、通过SecurityManager认证
boolean flag = subject.isAuthenticated(); // 是否通过认证,SecurityManager通过Authenticator
// 认证,Authenticator获取Realm,通过Realm 取认证数据
System.out.println("isAuthenticated:" + flag);
subject.checkRoles("admin","user");
subject.checkPermissions("user:delete","user:update");
}
}
Shiro集成Spring
启动项目报:Failed to start component [StandardEngine[Catalina].StandardHost[localhost].看了半天配置文件也没见到毛病,想了个办法:用 maven install 找到了错误,原来是shiro-crypto-cipher-1.4.0.jar没下载完整,删除本地仓库,重新update一下,OK.
项目结构
4.0.0
com.shiro.pro
imooc-shiro
0.0.1-SNAPSHOT
war
org.springframework
spring-context
4.2.4.RELEASE
org.springframework
spring-webmvc
4.2.4.RELEASE
org.apache.shiro
shiro-core
1.4.0
org.apache.shiro
shiro-spring
1.4.0
org.apache.shiro
shiro-web
1.4.0
web.xml
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
shiroFilter
/*
contextConfigLocation
classpath*:spring/spring.xml
org.springframework.web.context.ContextLoaderListener
org.springframework.web.context.request.RequestContextListener
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath*:spring/springmvc.xml
1
dispatcherServlet
/*
spring.xml
/login.html = anon
/subLogin = anon
/* = authc
spring-mvc.xml
User.java
public class User {
private String username;
private String password;
// 省略getter和setter
}
UserController.java
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.imooc.vo.User;
@Controller
public class UserController {
@RequestMapping(value = "/subLogin",method=RequestMethod.POST, produces="application/json; charset=UTF-8")
@ResponseBody // produces="application/json; charset=UTF-8"解决返回的JSON乱码问题
public String subLogin(User user) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword()) ;
try {
subject.login(token);
} catch (AuthenticationException e) {
return e.getMessage() ;
}
return "登录成功";
}
}
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
/**
* 自定义Realm
*
* @author Administrator
*
*/
public class CustomRealm extends AuthorizingRealm {
Map userMap = new HashMap<>();
{
userMap.put("Mark", "123456"); // Md5解密后的密文
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String) principals.getPrimaryPrincipal();
// 从数据库或缓存中获取角色数据
Set roles = getRelosByUserName(userName);
Set permissions = getPermissionsByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
/**
* 模拟数据库获取角色数据
*
* @param userName
* @return
*/
private Set getRelosByUserName(String userName) {
Set roles = new HashSet<>();
roles.add("admin");
roles.add("user");
return roles;
}
/**
* 模拟数据库获取权限数据
*
* @param userName
* @return
*/
private Set getPermissionsByUserName(String userName) {
Set permissions = new HashSet<>();
permissions.add("user:delete");
permissions.add("user:update");
return permissions;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1、通过主体传来的认证信息中,获取用户名
String userName = (String) token.getPrincipal();
// 2、通过用户名到数据库中获取凭证
String password = getPasswordByUserName(userName);
if (password == null) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("Mark", password, "customRealm");
return authenticationInfo;
}
/**
* 模拟数据库查询凭证
*
* @param userName
* @return
*/
private String getPasswordByUserName(String userName) {
return userMap.get(userName);
}
}
Shiro继承Spring-JDBC
pom.xml 添加
org.springframework
spring-jdbc
4.2.4.RELEASE
spring-dao.xml
spring.xml 添加
UserController.java
@RequestMapping(value = "/subLogin",method=RequestMethod.POST, produces="application/json; charset=UTF-8")
@ResponseBody // produces="application/json; charset=UTF-8"解决返回的JSON乱码问题
public String subLogin(User user) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword()) ;
try {
subject.login(token);
} catch (AuthenticationException e) {
return e.getMessage() ;
}
if(subject.hasRole("admin")) { // 判断是否有admin权限
return "登录成功";
}
return "登录失败";
}
UserDaoImpl.java
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
import javax.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import com.imooc.dao.UserDao;
import com.imooc.vo.User;
@Repository
public class UserDaoImpl implements UserDao{
@Resource
private JdbcTemplate jdbcTemplate ;
@Override
public User getUserByUserName(String userName) {
final String sql = "SELECT username,password FROM users WHERE username = ? " ;
User user = jdbcTemplate.queryForObject(sql, new String[] {userName}, new RowMapper() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User() ;
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}
});
return user ;
/*
List userList = jdbcTemplate.query(sql, new String[] {userName}, new RowMapper(){
@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(userList)) {
return null ;
}
return userList.get(0);
*/
}
@Override
public List queryRelosByUserName(String userName) {
final String sql = "SELECT role_name FROM user_roles WHERE username = ? " ;
return jdbcTemplate.query(sql, new String[] {userName}, new RowMapper(){
@Override
public String mapRow(ResultSet resultSet, int i) throws SQLException {
return (resultSet.getString("role_name"));
}
});
}
}
CustomRealm.java
@Resource
private UserDao userDao ;
/**
* 模拟数据库获取角色数据
*
* @param userName
* @return
*/
private Set getRelosByUserName(String userName) {
List list =userDao.queryRelosByUserName(userName);
Set roles = new HashSet<>(list);
return roles;
}
/**
* 模拟数据库查询凭证
*
* @param userName
* @return
*/
private String getPasswordByUserName(String userName) {
User user = userDao.getUserByUserName(userName);
if(user != null) {
return user.getPassword() ;
}
return null ;
}
通过注解配置授权
pom.xml添加
org.aspectj
aspectjweaver
1.8.9
springmvc.xml
UserController.java
@RequiresRoles("admin") // 只有admin权限才可以访问这个链接;可以传入多个权限值
@RequestMapping(value="/testAnnoRole",method=RequestMethod.GET)
@ResponseBody
public String testAnnoRole() {
return "@RequiresRoles注解控制权限" ; // 页面中文乱码
}
@RequiresPermissions("user:delete") // 可以传入多个操作权限值,表示当前主体具备括号中相应的操作权限时,才可以访问相应的方法
@RequestMapping(value="/testAnnoRole2",method=RequestMethod.GET)
@ResponseBody
public String testAnnoRole2() {
return "@RequiresPermissions注解控制权限" ;
}
Shiro过滤器
- Shiro内置过滤器
- anon、authBasic、authc、user、logout :和认证相关的过滤器
- anon:不需要认证直接访问
- authBasic:HTTP Basic
- authc:需要认证后才可以访问
- user:需要当前存在用户才可以访问
- logout:退出
- perms、roles、ssl、port:授权相关的过滤器
- perms:需要具备相关权限才可访问
- roles:和perms差不多,需要满足其中括号后面一些的角色才可以访问
- ssl:要求安全的协议,即HTTPS
- port:要求端口时其后面的设定的端口
- anon、authBasic、authc、user、logout :和认证相关的过滤器
spring.xml
/login.html = anon
/subLogin = anon
/testRoles = roles["admin"]
/testRoles1 = roles["admin","admin1"]
/testPerms = perms["user:delete"]
/testPerms1 = perms["user:delete","user:update"]
/* = authc
UserController.java
@RequestMapping(value="/testRoles",method=RequestMethod.GET)
@ResponseBody
public String testRoles() {
return "testRoles success" ;
}
@RequestMapping(value="/testRoles1",method=RequestMethod.GET)
@ResponseBody
public String testRoles1() {
return "testRoles1 success" ;
}
@RequestMapping(value="/testPerms",method=RequestMethod.GET)
@ResponseBody
public String testPerms() {
return "testPerms success" ;
}
@RequestMapping(value="/testPerms1",method=RequestMethod.GET)
@ResponseBody
public String testPerms1() {
return "testPerms1 success" ;
}
自定义filter
pom.xml添加
javax.servlet
javax.servlet-api
4.0.0
provided
- 不引入该包,继承AuthorizationFilter会报The hierarchy of the type RoleOrFilter is inconsistent,由于isAccessAllowed()方法的参数ServletRequest 和ServletResponse本地没有依赖
RoleOrFilter.java
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
/**
* 实现权限or的验证
* @author Administrator
*
*/
public class RoleOrFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception{
Subject subject = getSubject(request, response);
String[] roles = (String[]) mappedValue ;
if(roles ==null || roles.length == 0) {
return true;
}
for (String role : roles) {
if(subject.hasRole(role)) {
return true ;
}
}
return false;
}
}
- 授权相关的Filter继承AuthorizationFilter
- 认证相关的Filter继承AuthenticationFilter
spring.xml
/login.html = anon
/subLogin = anon
/testRoles = roles["admin","admin1"]
/testRoles1 = rolesOr["admin","admin1"]
/* = authc
Shiro会话管理
- Shiro Session管理:Shiro自己实现了一套Session管理体系,再不借助任何Web容器或Servlet的情况下,可以使用Session
- SessionManager、SessionDAO
- Redis实现Session共享
- Redis实现Session共享存在的问题
pom.xml添加
redis.clients
jedis
2.8.0
spring-redis.xml
spring.xml加入
JedisUtil.java
import java.util.Set;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
@Component
public class JedisUtil {
@Resource
private JedisPool jedisPool ;
/**
* 获取连接
*/
private Jedis getResource() {
return jedisPool.getResource() ;
}
/**
* 设置session
* @param key
* @param value
* @return
*/
public byte[] set(byte[] key, byte[] value) {
Jedis jedis = getResource() ;
try {
jedis.set(key, value);
return value ;
}finally {
jedis.close();
}
}
/**
* 通过key获取序列化的session值
* @param key
* @return
*/
public byte[] get(byte[] key) {
Jedis jedis = getResource() ;
try {
return jedis.get(key);
}finally {
jedis.close();
}
}
/**
* 设置指定key的超时时间
* @param key
* @param i
*/
public void expire(byte[] key, int i) {
Jedis jedis = getResource() ;
try {
jedis.expire(key, i);
}finally {
jedis.close();
}
}
/**
* 删除key
* @param key
*/
public void del(byte[] key) {
Jedis jedis = getResource() ;
try {
jedis.del(key);
}finally {
jedis.close();
}
}
/**
* 获取指定前缀的所有key
* @param shiro_session_prefix
* @return
*/
public Set keys(String shiro_session_prefix) {
Jedis jedis = getResource() ;
try {
return jedis.keys((shiro_session_prefix + "*").getBytes());
}finally {
jedis.close();
}
}
}
RedisSessionDao.java
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.springframework.util.SerializationUtils;
import com.imooc.util.JedisUtil;
public class RedisSessionDao extends AbstractSessionDAO{
@Resource
private JedisUtil jedisUtil ;
private final String SHIRO_SESSION_PREFIX = "imooc-session:" ;
/**
* 在Redis中以二进制的形式存储
* @param key
* @return
*/
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); // 将Session对象保存为byte数组
jedisUtil.set(key,value) ;
jedisUtil.expire(key,600);
}
}
@Override
public void update(Session session) throws UnknownSessionException {
saveSession(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 getActiveSessions() {
Set keys = jedisUtil.keys(SHIRO_SESSION_PREFIX) ; // 通过之前添加的前缀获取所有的key
Set sessions = new HashSet<>() ;
if(CollectionUtils.isEmpty(keys)) {
return sessions ;
}
for (byte[] key : keys) {
Session session = (Session) SerializationUtils.deserialize( jedisUtil.get(key));
sessions.add(session) ;
}
return sessions ;
}
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
saveSession(session) ;
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
// 这块会连续读取很多遍,影响性能;需要改造
System.out.println("Read Session");
if(sessionId == null ) {
return null ;
}
byte[] key = getKey(sessionId.toString());
byte[] value = jedisUtil.get(key) ;
return (Session) SerializationUtils.deserialize(value); // 将byte数组反序列化成session对象
}
}
解决多次访问Redis的问题
CustomSessionManager.java
import java.io.Serializable;
import javax.servlet.ServletRequest;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.WebSessionKey;
/**
* 优化连续读取session的带来的性能差
* @author Administrator
*
*/
public class CustomSessionManager extends DefaultWebSessionManager {
@Override
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
Serializable sessionId = getSessionId(sessionKey);
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.xml
Shiro缓存管理
RedisCacheManager.java
import javax.annotation.Resource;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
public class RedisCacheManager implements CacheManager {
@Resource
private RedisCache redisCache ;
@Override
public Cache getCache(String arg0) throws CacheException {
return redisCache;
}
}
RedisCache.java
import java.util.Collection;
import java.util.Set;
import javax.annotation.Resource;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.stereotype.Component;
import org.springframework.util.SerializationUtils;
import com.imooc.util.JedisUtil;
@Component
public class RedisCache implements Cache {
@Resource
JedisUtil jedisUtil ;
private final String CACHE_PREFIX = "imooc-cache:" ;
private byte[] getKey(K k) {
if(k instanceof String) {
return (CACHE_PREFIX + k).getBytes() ;
}
return SerializationUtils.serialize(k);
}
@Override
public void clear() throws CacheException {
// TODO Auto-generated method stub
}
@Override
public V get(K k) throws CacheException {
System.out.println("从Redis中获取授权数据"); // 先从Redis中获取权限数据 - 这块可以利用本地的二级缓存
byte[] value = jedisUtil.get(getKey(k)) ;
if(value != null) {
return (V)SerializationUtils.deserialize(value) ;
}
return null;
}
@Override
public Set keys() {
// TODO Auto-generated method stub
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, 600);
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 int size() {
// TODO Auto-generated method stub
return 0;
}
@Override
public Collection values() {
// TODO Auto-generated method stub
return null;
}
}
spring.xml
CustomRealm.java
/**
* 模拟数据库获取角色数据
*
* @param userName
* @return
*/
private Set getRelosByUserName(String userName) {
System.out.println("从数据库中获取授权数据");
List list =userDao.queryRelosByUserName(userName);
Set roles = new HashSet<>(list);
return roles;
}
Shiro自动登录
- Shiro RememberMe
spring.xml
login.html
User.java
private boolean rememberMe ;
UserController.java
try {
token.setRememberMe(user.getRememberMe()); // 设置是否记住
subject.login(token);
} catch (AuthenticationException e) {
return e.getMessage() ;
}