最近一段时间刚好遇到了权限控制的问题,今天写一下自己的感悟与理解。
首先说一下什么是Shiro,ApacheShiro是一个功能强大、灵活的,开源的安全框架,它内部集成了很多安全机制,只需要我们配置,利用就好。下面说一下Shiro架构的三个主要的理念:
Subject:当前用户,Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。
Realms:用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)
了解了这三个重要的理念之后就开始说一下项目的整体思路吧:
项目通过赋予角色权限增加角色的访问权利,又通过赋予用户不同的角色来控制登录用户所拥有的权限。
首先肯定是必须要用到shiro框架给我集成好的一些方法和技术,所以就开始shiro的配置,shiro对权限的控制使用的是过滤器,还必须实现登录认证、权限控制的方法以及通过realm来获取数据。配置好这些之后呢,就可以开始代码的编写了,这里还需要强调几个点:
第一呢,为了保证框架的性能,使用的是redis进行缓存,
第二呢,给密码加密的方法是使用了MD5,为了保证登录的时候顺利通过登录认证,必须在用户增加的时候给密码进行加密,在登录认证的时候也需要给密码加密以验证数据库中的密码。
Shiro能做的东西很多,这里我只是进行了简单的登录认真和权限控制,下面详细说一下代码吧,相信结合上面我的思路大家可以更清晰的学习代码。
`首先是pom依赖,里面有挺多不认识的依赖的话,多上网查一下,有助于理解代码
4.0.0
com.study
springboot-shiro
0.0.1-SNAPSHOT
jar
springboot-shiro
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
1.5.2.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-thymeleaf
com.github.pagehelper
pagehelper-spring-boot-starter
1.1.0
tk.mybatis
mapper-spring-boot-starter
1.1.1
org.apache.shiro
shiro-spring
1.3.2
com.alibaba
druid
1.0.29
mysql
mysql-connector-java
net.sourceforge.nekohtml
nekohtml
1.9.22
com.github.theborakompanioni
thymeleaf-extras-shiro
1.2.1
org.crazycake
shiro-redis
2.4.2.1-RELEASE
shiro-core
org.apache.shiro
org.springframework.boot
spring-boot-maven-plugin
org.mybatis.generator
mybatis-generator-maven-plugin
1.3.5
${basedir}/src/main/resources/generator/generatorConfig.xml
true
true
mysql
mysql-connector-java
${mysql.version}
tk.mybatis
mapper
3.4.0
接着就是shiro需要的配置
1.redis的配置
`package com.study.config;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.pool.max-wait}")
private long maxWaitMillis;
@Bean
public JedisPool redisPoolFactory() {
Logger.getLogger(getClass()).info("JedisPool注入成功!!");
Logger.getLogger(getClass()).info("redis地址:" + host + ":" + port);
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout);
return jedisPool;
}
}
`
2.shiro的配置
package com.study.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.github.pagehelper.util.StringUtil;
import com.study.model.Resources;
import com.study.service.ResourcesService;
import com.study.shiro.MyShiroRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Created by yangqj on 2017/4/23.
*/
@Configuration
public class ShiroConfig {
@Autowired(required = false)
private ResourcesService resourcesService;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.password}")
private String password;
@Bean
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* ShiroDialect,为了在thymeleaf里使用shiro的标签的bean
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
*
Filter Chain定义说明
1、一个URL可以配置多个Filter,使用逗号分隔
2、当设置多个过滤器时,全部验证通过,才视为通过
3、部分过滤器可指定参数,如perms,roles
*
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/usersPage");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
//拦截器.
Map filterChainDefinitionMap = new LinkedHashMap();
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/css/**","anon");
filterChainDefinitionMap.put("/js/**","anon");
filterChainDefinitionMap.put("/img/**","anon");
filterChainDefinitionMap.put("/font-awesome/**","anon");
//:这是一个坑呢,一不小心代码就不好使了;
//
//自定义加载权限资源关系
List resourcesList = resourcesService.queryAll();
for(Resources resources:resourcesList){
if (StringUtil.isNotEmpty(resources.getResurl())) {
String permission = "perms[" + resources.getResurl()+ "]";
filterChainDefinitionMap.put(resources.getResurl(),permission);
}
}
// filterChainDefinitionMap.put("/**", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm.
securityManager.setRealm(myShiroRealm());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码;
* )
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 配置shiro redisManager
* 使用的是shiro-redis开源插件
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setExpire(1800);// 配置缓存过期时间
redisManager.setTimeout(timeout);
// redisManager.setPassword(password);
return redisManager;
}
/**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/**
* shiro session的管理
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
}
3.Realm的配置
package com.study.shiro;
import com.study.model.Resources;
import com.study.model.User;
import com.study.service.ResourcesService;
import com.study.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.util.ByteSource;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Resource;
import java.util.*;
/**
* Created by yangqj on 2017/4/21.
*/
public class MyShiroRealm extends AuthorizingRealm {
@Resource
private UserService userService;
@Resource
private ResourcesService resourcesService;
@Autowired
private RedisSessionDAO redisSessionDAO;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//查出来用户之后,根据id查看用户的角色和权限
User user= (User) SecurityUtils.getSubject().getPrincipal();//User{id=1, username='admin', password='3ef7164d1f6167cb9f2658c07d3c2f0a', enable=1}
Map map = new HashMap();
map.put("userid",user.getId());
List resourcesList = resourcesService.loadUserResources(map);
// 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
for(Resources resources: resourcesList){
info.addStringPermission(resources.getResurl());
}
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户的输入的账号.
String username = (String)token.getPrincipal();
User user = userService.selectByUsername(username);
if(user==null) throw new UnknownAccountException();
// 判断用户的状态是否为可用
if (0==user.getEnable()) {
throw new LockedAccountException(); // 帐号锁定
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, //用户
user.getPassword(), //密码
ByteSource.Util.bytes(username),
getName() //realm name
);
// 当验证都通过后,把用户信息放在session里
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("userSession", user);
session.setAttribute("userSessionId", user.getId());
return authenticationInfo;
}
/**
* 根据userId 清除当前session存在的用户的权限缓存
* @param userIds 已经修改了权限的userId
*/
public void clearUserAuthByUserId(List userIds){
if(null == userIds || userIds.size() == 0) return ;
//获取所有session
Collection sessions = redisSessionDAO.getActiveSessions();
//定义返回
List list = new ArrayList();
for (Session session:sessions){
//获取session登录信息。
Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if(null != obj && obj instanceof SimplePrincipalCollection){
//强转
SimplePrincipalCollection spc = (SimplePrincipalCollection)obj;
//判断用户,匹配用户ID。
obj = spc.getPrimaryPrincipal();
if(null != obj && obj instanceof User){
User user = (User) obj;
System.out.println("user:"+user);
//比较用户ID,符合即加入集合
if(null != user && userIds.contains(user.getId())){
list.add(spc);
}
}
}
}
RealmSecurityManager securityManager =
(RealmSecurityManager) SecurityUtils.getSecurityManager();
MyShiroRealm realm = (MyShiroRealm)securityManager.getRealms().iterator().next();
for (SimplePrincipalCollection simplePrincipalCollection : list) {
realm.clearCachedAuthorizationInfo(simplePrincipalCollection);
}
}
}
重点说了一下配置,具体的代码呢大家参考一下github
https://github.com/ityouknow/spring-boot-examples
参考:
https://blog.csdn.net/ityouknow/article/details/73836159