一、基于 Shiro 的权限加载
Controller 方法上贴 Shiro 提供的权限注解
(@RequiresPermissions)
@RequiresRoles("ADMIN")
//权限表达式可以配置多个,默认是&&,改为or相当于||, 要求同时拥有多个权限才能访问该方法
@RequiresPermissions(value = {"role:list","角色列表"},logical = Logical.OR)
开启 Shiro 注解扫描器
需要applicationContext.xml配置<aop:config>才会创建处理对象
shiro.xml
<!-- 开启 Shiro 注解扫描器,对贴了 shiro 注解的类进行代理,利用代理的方式来实现权限拦截的功能
需要applicationContext.xml配置<aop:config>才会创建..advisor这样的处理bean的对象 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
生成权限信息方法
@Override
public void reload(){
// 获取数据库中所有的权限表达式
List<String> expressions = permissionMapper.selectAllExpression();// role:delete role:list
// 从spring 容器中获取所有的controller
Map<String, Object> beans = ctx.getBeansWithAnnotation(Controller.class);
// 获取所有的value值
Collection<Object> values = beans.values();
// 遍历循环获取每一个controller
for (Object controller : values){
// 判断实例是否是Cglib的代理对象
if (!AopUtils.isCglibProxy(controller)) {
continue; // 执行下一次循环
}
// 获取controller 的字节码对象
// isCglibProxy已解决=>SuperClass获取该类的父类代理类,就可以获取到代理的注解,其他类获取到父类Object,就拿不到注解,annotation=null
Class<?> clazz = controller.getClass().getSuperclass();
// 获取controller 的所有方法
Method[] methods = clazz.getDeclaredMethods();
// 遍历出每一个方法
for (Method method:methods) {
// 判断是否有贴自定义的权限注解
RequiresPermissions annotation = method.getAnnotation(RequiresPermissions.class);
// 如果有贴,封装成对象,并插入数据库
if(annotation != null){
// 获取权限表达式
String expression = annotation.value()[0];
// 通过反射拼接权限表达式
/*String expression = clazz.getSimpleName(); // DepartmentController
expression = expression.replace("Controller",""); // Department
expression = StringUtils.uncapitalize(expression); // department
expression = expression + ":" + method.getName(); // department:list*/
// 判断该表达式是否已经存在数据库中,不存在就插入
if(!expressions.contains(expression)) {
Permission permission = new Permission();
permission.setName(annotation.value()[1]); // 中文的权限名称
permission.setExpression(expression); // 英文 权限表达式
permissionMapper.insert(permission);
}
}
// 如果没有就不处理
}
}
}
二、Shiro 基于web 环境的授权
hiro.java
获取授权进行处理
/**
* 授权,代码中需要 判断授权 hasxxx的时候才会执行
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("=======================");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 获取当前登录用户
Subject subject = SecurityUtils.getSubject();
// 获取主体的身份信息
Employee employee = (Employee) subject.getPrincipal();// new SimpleAuthenticationInfo(传第一个参数)
// 判断是否是管理员
if (employee.isAdmin()) {
info.addRole("ADMIN");// 方便后续判断是否管理员可以直接用submit来判断
info.addStringPermission("*:*"); // *:* 表示所有权限,即不做任何限制
} else {
// 根据用户的id查询该用户拥有的角色
List<Role> roles = roleMapper.selectByEmpId(employee.getId());
ArrayList<String> roleSnList = new ArrayList<>();
for (Role role : roles) {
roleSnList.add(role.getSn()); // 放入角色的编码
}
info.addRoles(roleSnList);
// 根据用户的id查询该用户拥有的权限
List<String> permission = permissionMapper.selectByEmpId(employee.getId());
info.addStringPermissions(permission);
}
return info;
}
登录控制器
LoginController.java
@RequestMapping("/login")
@ResponseBody
public JsonResult login(String username, String password){
try {
// 封装令牌
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 利用shiro的api来进行登录
SecurityUtils.getSubject().login(token);
return new JsonResult(); // 默认为true
}catch (UnknownAccountException e){
e.printStackTrace();
return new JsonResult(false,"账号不存在");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
return new JsonResult(false,"密码错误");
}catch (DisabledAccountException e){
e.printStackTrace();
return new JsonResult(false,"账号已禁用,请联系管理员");
}catch (Exception e){
e.printStackTrace();
return new JsonResult(false,"登录异常,请联系管理员");
}
}
/* @RequestMapping("/logout")
在 shiro过滤器中配置
/logout.do=logout
*/
异常捕获
- @ControllerAdvice:Controller 增强器
通常和@ExceptionHandler 注解配合使用,其中最常用的是@ExceptionHandler,对 Controller 中
的异常作特定处理。 - @ExceptionHandler:异常处理器
贴在方法上,当 Controller 中配置指定异常时,会执行贴了该注解的方法。
ContrllerExceptionHandler.java
/**
* 对控制器进行处理
* 利用aop
*/
// mvc.xml 的扫描控制器 上一定要扫到
@ControllerAdvice
public class ContrllerExceptionHandler {
/**
* 当前的方法用于捕获指定的异常
*/
@ExceptionHandler(RuntimeException.class) // 规定为运行时异常
public String handler(RuntimeException e, HttpServletResponse response, HandlerMethod handlerMethod) throws IOException {
// 打印错误信息方便debug
e.printStackTrace();
// 判断如果是ajax对应的方法(判断有没有ResponseBody注解),有就返回JsonResult
if(handlerMethod.hasMethodAnnotation(ResponseBody.class)){
response.setContentType("application/json;charset=utf-8"); // 为了适配浏览器,显式的告诉浏览器数据类型和编码方式
response.getWriter().print(JSON.toJSONString(new JsonResult(false, "操作失败")));
return null;
}else {
// 如果不是,就返回错误的视图页面
return "common/error";
}
}
/**
* 捕获没有权限的异常
* @param e
* @param response
* @param handlerMethod
* @return
* @throws IOException
*/
@ExceptionHandler(UnauthorizedException.class) // 规定为运行时异常
public String handlerUnauthorized(RuntimeException e, HttpServletResponse response, HandlerMethod handlerMethod) throws IOException {
// 打印错误信息方便debug
e.printStackTrace();
// 判断如果是ajax对应的方法(判断有没有ResponseBody注解),有就返回JsonResult
if(handlerMethod.hasMethodAnnotation(ResponseBody.class)){
response.setContentType("application/json;charset=utf-8"); // 为了适配浏览器,显式的告诉浏览器数据类型和编码方式
response.getWriter().print(JSON.toJSONString(new JsonResult(false, "您没有该权限")));
return null;
}else {
// 如果不是,就返回错误的视图页面
return "common/nopermission";
}
}
}
三、 Shiro 标签
依赖
<dependency>
<groupId>net.mingsoft</groupId>
<artifactId>shiro-freemarker-tags</artifactId>
<version>1.0.0</version>
</dependency>
注册 shiro 的标签
MyFreeMarkerConfig.java
public class MyFreeMarkerConfig extends FreeMarkerConfigurer {
@Override
public void afterPropertiesSet() throws IOException, TemplateException {
//继承之前的属性配置,这不不能省
super.afterPropertiesSet();
Configuration cfg = this.getConfiguration();
cfg.setSharedVariable("shiro", new ShiroTags());//shiro 标签
}
}
配置到当前环境
将 shiro 的标签设置成当前环境中使用的配置对象
mvc.xml
<!-- 注册FreeMarker配置类(配置自定义的配置类,带有shiro标签的功能) -->
<bean class="cn.wolfcode.shiro.MyFreeMarkerConfig">
<!-- 配置 freemarker 的文件编码 -->
<property name="defaultEncoding" value="UTF-8"/>
<!-- 配置 freemarker 寻找模板的路径 -->
<property name="templateLoaderPath" value="/WEB-INF/views/"/>
</bean>
shiro 的 freemarker 常用标签:
- authenticated 标签:已认证通过的用户。
<@shiro.authenticated> </@shiro.authenticated>
- notAuthenticated 标签:未认证通过的用户。与 authenticated 标签相对。
<@shiro.notAuthenticated></@shiro.notAuthenticated>
- principal 标签:输出当前用户信息,通常为登录帐号信息
<@shiro.principal property="name" />
后台是直接将整个员工对象作为身份信息的
return new SimpleAuthenticationInfo(employee,employee.getPassword(),ByteSource.Util.bytes(username),"CrmRealm");
<@shiro.authenticated>
<@shiro.principal property="name"/>
</@shiro.authenticated>
- hasRole 标签:验证当前用户是否属于该角色 ,
<@shiro.hasRolename=”admin”>Hello admin!</@shiro.hasRole>
- hasAnyRoles 标签:验证当前用户是否属于这些角色中的任何一个,角色之间逗号分隔 ,
<@shiro.hasAnyRoles name="admin,user,operator">Hello admin! </@shiro.hasAnyRoles>
- hasPermission 标签:验证当前用户是否拥有该权限 ,
<@shiro.hasPermissionname="/order:*">订单/@shiro.hasPermission>
<ul class="treeview-menu">
<@shiro.hasPermission name="department:list">
<li name="department"><a href="/department/list.do"><i class="fa fa-circle-o"></i> 部门管理</a></li>
</@shiro.hasPermission>
<@shiro.hasPermission name="employee:list">
<li name="employee"><a href="/employee/list.do"><i class="fa fa-circle-o"></i> 员工管理</a></li>
</@shiro.hasPermission>
<@shiro.hasPermission name="permission:list">
<li name="permission"><a href="/permission/list.do"><i class="fa fa-circle-o"></i> 权限管理</a></li>
</@shiro.hasPermission>
<@shiro.hasPermission name="role:list">
<li name="role"><a href="/role/list.do"><i class="fa fa-circle-o"></i> 角色管理</a></li>
</@shiro.hasPermission>
<li name="classinfo"><a href="/classinfo/list.do"><i class="fa fa-circle-o"></i> 班级管理</a></li>
</ul>
四、集成 EhCache
在请求中一旦进行权限的控制都去到 Realm 中的 doGetAuthorizationInfo 方法进行授权,我们授权信息应该要从数据库中查询的。 如果每次授权都要去查询数据库就太频繁了,性能不好,所以需要用到缓存
依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.8</version>
</dependency>
配置缓存管理器
shiro.xml
<!-- 安全管理器 分发调度-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 配置数据源 -->
<property name="realm" ref="crmRealm"/>
<!-- 注册缓存管理器 -->
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- 缓存管理器 权限 角色 shiro有关的数据会被缓存 (清除缓存:注销,清除自己的 关闭服务器,清除所有)-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<!-- 设置配置文件 -->
<property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
</bean>
添加 ehcache 配置文件
shiro-ehcache.xml
<ehcache>
<defaultCache
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
- maxElementsInMemory: 缓存最大个数。
- eternal:对象是否永久有效,一但设置了,timeout 将不起作用。
- timeToIdleSeconds: 设置对象在失效前的允许闲置时间(单位:秒)。仅当 eternal=false 对象不是永久有效时使用,可选属性,默认值是 0,也就是可闲置时间无穷大。
- timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当 eternal=false 对象不是永久有效时使用,默认是 0.,也就是对象存活时间无穷大。
- memoryStoreEvictionPolicy:当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。默认策略是 LRU(最近最少使用)。你可以设置为 FIFO(先进先出)或是 LFU(较少使用)。
- clearOnFlush:内存数量最大时是否清除。
- overowToDisk:当内存中对象数量达到 maxElementsInMemory 时,Ehcache 将会对象写到磁盘中。