# Apache Shiro是什么?
apache Shiro是一个强大且易用的Java安全框架,能够执行身份验证、授权、加密和会话管理。
# apache Shiro的主要API
## Subject
Subject即“当前操作用户”。但是,在Shiro中,Subject这一概念并不是"帐户",而是与当前跟shrio交互的应用。
## SecurityManager
SecurityManager是Shiro框架的核心,典Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
## Realm
Realm是安全数据源,其中包含认证和授权数据。
也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,
如果缺省的Realm不能满足需求,可以自定义Realm实现。
# 使用Apache Shiro
## 引入依赖
## 在web.xml配置Shiro的代理过滤器
说明:该过滤器仅仅是过滤器代理,其功能由spring中配置的shrio过滤器实现
## 开发自定义Realm
/**
* 通过继承AuthorizingRealm实现自定义Realm。
*/
public class SaftyRealm extends AuthorizingRealm {
@Resource
private SaftyService saftyService;
/**
* 获取当事人(当前用户)授权信息 参数PrincipalCollection 当事人集合
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
/*
* 获取登录用户帐号
*/
CurrUserDto currUser = (CurrUserDto) principalCollection.getPrimaryPrincipal();// 获取首要(第一)当事人
/*
* 创建授权信息对象
*/
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//
// // 查询角色
// List
//
// // 将角色信息放入授权信息对象
// for (Role role : roleList) {
// simpleAuthorizationInfo.addRole(String.valueOf(role.getRo_id()));
// }
//
/*
* 查询用户权限,并将权限放入授权信息对象中
*/
List
for (Module module : moduleList) {
simpleAuthorizationInfo.addStringPermission(String.valueOf(module.getM_id()));
}
// System.out.println(currUser.getUserId()+"->"+simpleAuthorizationInfo.getStringPermissions());
/*
* 返回授权信息
*/
return simpleAuthorizationInfo;
}
/**
* 获取认证信息 参数 AuthenticationToken 认证令牌(如:一组用户名和密码就是一个认证令牌)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
String userId = (String) token.getPrincipal();// 获得当事人(当前用户)
User user = saftyService.getUser(userId);
/*
* 如果不存在前用户信息,返回null
*/
if (user == null) {
return null;
}
/*
* 创建当前用户
*/
CurrUserDto currUser = new CurrUserDto();
currUser.setUserId(user.getU_id());
currUser.setUserName(user.getU_name());
/*
* 创建认证信息,三个构造参数含义依次如下:
* 参数1:principal当前用户
* 参数2:credentials认证凭证(如:口令、密码等)
* 参数3:realm名称
*/
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(currUser, user.getU_pwd(), this.getName());
/*
* 返回认证信息
*/
return info;
}
}
## 在spring配置文件中配置Shiro
/static/** = anon
/safty/login_to = anon
/safty/login = anon
/** = authc
## 控制器中的登录代码示例
@RequestMapping(value="/login",method=RequestMethod.POST)
public ResultDto login(@RequestBody UserDto userDto,HttpSession session) {
try {
Subject subject = SecurityUtils.getSubject();
//创建登录令牌
UsernamePasswordToken usernamePasswordToken =
new UsernamePasswordToken(userDto.getU_id(),userDto.getU_pwd());
//登录,该方法声明抛出异常,可以捕获异常,并进行应对处理
subject.login(usernamePasswordToken);
//是否通过认证
if(subject.isAuthenticated()) {
//获得当前用户信息
CurrUserDto currUser = (CurrUserDto)subject.getPrincipal();
//将当前用户放入Session。注:这里的Session是由Shiro提供的。
subject.getSession().setAttribute(Constants.SESSION_ATTR_NAME_CURR_USER, currUser);
return ResultDto.successResult();
}
return ResultDto.failResult("登录失败!");
} catch (UnknownAccountException e) {
return ResultDto.failResult("用户名不存在!");
} catch (IncorrectCredentialsException e) {
return ResultDto.failResult("账户密码 不正确!");
} catch (LockedAccountException e) {
return ResultDto.failResult("用户名 被锁定 !");
}catch (Exception e) {
e.printStackTrace();
return ResultDto.failResult("系统错误!");
}
}
## 控制器中的退出代码示例
@DeleteMapping("/logout")
public ResultDto login_authentication() {
try {
//注销
SecurityUtils.getSubject().logout();
return ResultDto.successResult();
} catch (Exception e) {
return ResultDto.failResult("退出失败!");
}
}
## 动态权限控制
上述spring中ShiroFilter配置拦截规则如下:
/static/** = anon
/safty/login_to = anon
/safty/login = anon
/** = authc
显然,这些拦截规则仅包含认证部分,缺少授权部分,授权配置形式如:/docs/** = authc, perms[xxx] 表示需要经过认证和xxx权限。
而系统中的权限往往是动态的,随用户不同而不同,所以这种静态配置拦截规则的方式就不合适了。
在这样的动态权限需求背景下,可以使用ShiroFilter另外一个Map类型属性filterChainDefinitionMap配置拦截规则,
而filterChainDefinitionMap可以通过一个自定义的FactoryBean获得。
### 动态权限控制示例
/**
* 自定义生成filterChainDefinitionMap的工厂bean
*/
public class ShiroDefinitionSectionFactory implements FactoryBean
private static final Logger LOG = LogManager.getLogger(ShiroDefinitionSectionFactory.class);
public static final String PREMISSION_FORMAT = "authc,perms[{0}]";
@Resource
private SecurityManager securityManager;
@Resource
private SaftyService saftyService;
/**
* 注入先于动态权限加载的默认认证授权定义
*/
private String preFilterChainDefinitions;
/**
* 注入后于动态权限加载的默认认证授权定义
*/
private String postFilterChainDefinitions;
@Override
public Ini.Section getObject() throws Exception {
/*
* Ini是Map
* Ini.Section是Map
*/
Ini ini = new Ini();
//加载动态权限前的ini配置
ini.load(preFilterChainDefinitions);
//获取ini配置片段
Ini.Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
/*
* 加载动态权限
*/
List
//由注入的资源管理对象获取所有资源数据,并且资源的authorities的属性是EAGER的fetch类型
for(Module module:moduleList) {
if(StringUtils.isEmpty(module.getM_url())) {
continue;
}
//将动态权限放入ini配置片段中
section.put(module.getM_url(), MessageFormat.format(PREMISSION_FORMAT, String.valueOf(module.getM_id())) );
}
//加载动态权限之后的ini配置
Ini postIni = new Ini();
postIni.load(postFilterChainDefinitions);
Ini.Section postSection=postIni.getSection(Ini.DEFAULT_SECTION_NAME);
//将动态权限之后的ini配置放入统一的ini配置片段中
section.putAll(postSection);
LOG.debug("=====Shiro安全规则=======================================================================");
LOG.debug(section.entrySet());
LOG.debug("=====Shiro安全规则=======================================================================");
return section;
}
public void setPreFilterChainDefinitions(String preFilterChainDefinitions) {
this.preFilterChainDefinitions = preFilterChainDefinitions;
}
public void setPostFilterChainDefinitions(String postFilterChainDefinitions) {
this.postFilterChainDefinitions = postFilterChainDefinitions;
}
@Override
public Class> getObjectType() {
return this.getClass();
}
@Override
public boolean isSingleton() {
return false;
}
}
修改spring配置文件
/static/** = anon
/safty/login_to = anon
/safty/login = anon
/** = authc
注:引入shiro时,shiro自动引入一个日志工具依赖slf4j,
该工具与spring-jcl冲突 导致mybatis日志不输出,需要在pom.xml添加如下依赖消除冲突: