转载请注明出处:
牵手生活--:笔记是整理思路方式,分享是一个美德,牵手是我的生活方式
注:此文承接上一文: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集成Spring
Shiro集成Spring
配置maven web工程的pom.xml,添加对spring mvc和shiro的支持
org.springframework
spring-context
${spring.version}
org.springframework
spring-webmvc
${spring.version}
org.apache.shiro
shiro-core
1.4.0
org.apache.shiro
shiro-spring
1.4.0
org.apache.shiro
shiro-web
1.4.0
配置maven的web工程的web.xml
Archetype Created Web Application
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
shiroFilter
/*
contextConfigLocation
classpath*:spring/spring.xml
org.springframework.web.context.ContextLoaderListener
mvc-dispatcher
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath*:/spring/springmvc.xml
1
mvc-dispatcher
/
集成Shiro 和Springmvc后的目录结构
Spring的配置文件Spring.xml(shiro的Filter、SecurityManager、自定义realm、加密设置HashedCredentialsMatcher)
/login.html = anon
/subLogin = anon
/* = authc
SpringMVC的配置文件springmvc.xml
创建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核心包在前面已经导入了
com.alibaba
druid
1.1.6
mysql
mysql-connector-java
6.0.6
org.springframework
spring-jdbc
${spring.version}
创建spring jdbc的配置文件spring-jdbc.xml
修改Spring.xml(引入Spring-jdbc.xml配置文件、设置扫描路径)
创建Dao,DaoImpl、修改Controller、修改realm后的结构
UserDao(访问数据库)
public interface UserDao {
User getUserByUsername(String userName);
List 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 list = 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(list)){
return null;
}
return list.get(0); //直接返回第一条
}
@Override
public List queryRolesByUserName(String userName) {
String sql = "select role_name from test_user_role where user_name = ?";
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
public class CustomRealm extends AuthorizingRealm {
@Resource /*注入userDao*/
private UserDao userDao;//用户的数据库信息
@Override //授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String userName = (String) principalCollection.getPrimaryPrincipal();
//从数据库或缓冲中获得角色数据
Set roles = getRolesByUserName(userName);
//从数据库或缓冲中获得权限数据
Set permissions = getPermissionsByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setStringPermissions(permissions);
simpleAuthorizationInfo.setRoles(roles);
return simpleAuthorizationInfo;
}
/**
* 模拟用户权限数据
* @param userName
* @return
*/
private Set getPermissionsByUserName(String userName) {
Set sets = new HashSet<>();
sets.add("user:myAdd");
sets.add("user:delete");
sets.add("user:update");
return sets;
}
/**
* 角色数据
* @param userName
* @return
*/
private Set getRolesByUserName(String userName) {
List list = userDao.queryRolesByUserName(userName);
Set 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包)
org.aspectj
aspectjweaver
1.8.9
修改spring.xml(添加对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的过滤器
/login.html = anon
/subLogin = anon
/testMyRoles = roles["admin"]
/testMyRoles2 = roles["admin","admin1]
/testPerms = perms["user:delete"]
/testPerms2 = perms["user:delete","user:update"]
/* = authc
shiro自定义filter过滤器
创建shiro自定义filter过滤器RolesOrFilter.java
需要继承的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的配置文件中配置自定义过滤器
/login.html = anon
/subLogin = anon
/testMyRoles = roles["admin","admin1"]
/testMyRoles2 = rolesOr["admin","admin1"]
/* = authc
重新部署web,并按younghare登录(具有admin,user角色)
登录成功后访问url
http://localhost:8080/login.html
http://localhost:8080/testMyRoles
![必须登录成功](https://upload-images.jianshu.io/upload_images/5438896-
767bdeefd5af93ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
补上我们的403.html资源文件即可
省略
Shiro会话管理(session)和缓冲管理
Shiro会话管理
在pom.xml manen配置文件中添加redis的访问工具jedis
redis.clients
jedis
2.9.0
jar
compile
通过Redis访问工具,需要修改增删改查的操作。创建RedisSessionDao并继承AbstractSessionDAO,同时实现对应的抽象方法
创建spring 配置文件管理spring-redis.xml配置文件
修改spring.xml(引入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 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 getActiveSessions() {
/*通过前缀获取所以的key值*/
Set keys = jedisUtil.keys(shiro_session_prefix);
Set 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对象)
修改spring.xml(在DefaultWebSecurityManager设置session管理对象)
重新部署运行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连接查看
日志情况
doReadSession被多次调用的优化
问题:根据日志情况发现一次访问,会调用多次的doReadSession
解决:查看DefaultWebSessionManager的父类DefaultSessionManager的retrieveSession方法,看看它是如何处理的
创建一个是定义的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对象修改为我们自己的
重写部署运行web,并访问,查看日志
Shiro缓冲管理(CacheManager接口、Cache)
目标:用来缓冲角色数据和权限数据,这样在每次授权是,就不需要每次都到数据库去查询
RedisCache实现Cache接口
/*K key;V :value*/
public class RedisCache implements Cache {
@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 keys() {
/*还没有开始实现*/
return null;
}
@Override
public Collection values() {
/*还没有开始实现*/
return null;
}
}
修改spring.xml(创建RedisCacheManager对象,在DefaultWebSecurityManager对象中注入RedisCacheManager对象)
重新部署运行web,登录并校验
RedisCacheManager实现CacheManager接口
注意观察从数据库&从redis获取的权限数据
Shiro自动登录
修改spring.xml(创建自动登录对象cookieRememberMeManager,SimpleCookie,配置修改DefaultWebSecurityManager对象的rememberMeManager)