1.概念说明
2.使用流程说明
3.配置原理说明
4.DefaultFilter系统自带过滤器说明
1.概念说明
subject:登录主体,一般为用户;
realm:相当于数据源,里面实现用户认证与授权逻辑;可实现多数据源配置(一个项目中连接多种类型的数据库),本文暂不说明.
principals:主体的属性标识,可以是用户名、邮箱、用户id等;
credentials:证明/凭证,只有主体才知道的安全信息, 比如说密码;
SecurityManager.java :shiro核心权限管理器,主要方法:
登录:利用主体以及认证信息进行认证,首先从缓存中取;
登出:从session中删除主体信息并关闭session;
创建subject对象:创建主体对象并加入到session中;
2.使用流程说明
1.配置信息(可参考下面代码);
2.用户认证,可以理解成是登录,shiro框架会校验用户名密码;
3.用户访问授权,访问带有权限注解的接口,shiro会进行授权并校验权限;授权一般是从数据库中查询用户拥有的权限,接口上权限注解用于校验用户是否拥有访问路径的权限.如果权限认证失败可进入到全局异常处理中,对异常信息进行自定义处理,返回前端.
参考代码:
自定义realm:
package com.kawaxiaoyu.admin.admin_manage.common.shiro;
import com.kawaxiaoyu.admin.admin_manage.dao.ExclusiveShareConfigDao;
import com.kawaxiaoyu.admin.admin_manage.domain.User;
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
/**
* @ClassName: CustomRealm
* @Desc: 自定义realm
* @Author: txm
* @Date: 2021/4/20 11:27
**/
@Component
public class CustomRealm extends AuthorizingRealm {
@Autowired
private ExclusiveShareConfigDao exclusiveShareConfigDao; // 持久层dao,用于从数据库获取用户信息
// 授权(用户访问方法时调用)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 获取用户角色集合
// 模拟从数据库查询用户拥有的角色
Set<String> roleSet=new HashSet<>();
roleSet.add("admin");
roleSet.add("guest");
simpleAuthorizationInfo.setRoles(roleSet);
// 获取用户权限集合
// 模拟从数据库查询用户拥有的权限
Set<String> permissionSet =new HashSet<>();
permissionSet.add("/goods/findAllGoods"); // 官方推荐写法:goods:findAllGoods
simpleAuthorizationInfo.setStringPermissions(permissionSet);
return simpleAuthorizationInfo;
}
// 认证(用户登录时调用)
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (authenticationToken.getPrincipal() == null) {
return null;
}
//获取用户名
String name = authenticationToken.getPrincipal().toString();
User user = exclusiveShareConfigDao.findUserIdByLogin(name);
if (user == null) {
//这里返回后会报出对应异常
return null;
} else {
//这里验证authenticationToken和simpleAuthenticationInfo的信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword().toString(), getName());
return simpleAuthenticationInfo;
}
}
}
shiro配置类:
package com.kawaxiaoyu.admin.admin_manage.common.shiro;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @ClassName: ShiroConfig
* @Desc: 权限配置类
* @Author: txm
* @Date: 2021/4/20 11:36
**/
@Configuration
public class ShiroConfig {
/**
* 过滤器设置:创建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean( DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加Shiro内置过滤器
/**
* Shiro内置过滤器,可以实现权限相关的拦截器
* 常用的过滤器(具体对应配置参考DefaultFilter.java,末尾有说明):
* anon: 无需认证(登录)可以访问
* authc: 必须认证才可以访问
* user: 如果使用rememberMe的功能可以直接访问
* perm: 该资源必须得到资源权限才可以访问
* role: 该资源必须得到角色权限才可以访问
*/
Map filterMap = new LinkedHashMap();
// 将登陆的接口放出来,不然没权限访问登陆的接口
filterMap.put("/user/login", "anon");
// 将登陆的接口放出来,不然没权限访问退出的接口
filterMap.put("/user/logout", "anon");
// 保证静态资源不用认证
filterMap.put("/static/**", "anon");
// anon即为所有的不用认证即可访问,过滤器所有的URL都是从上到下过滤执行,将所有的/都不进行认证,是保证从方法上添加权限校验.
filterMap.put("/**", "anon");
// 设置认证通过之后跳转的页面,否则会默认跳转到web项目下的login.jsp
shiroFilterFactoryBean.setLoginUrl("/index");
//设置未授权提示页面,不用设置,全局捕获异常直接跳转到指定页面
//shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
/**
* 核心权限控制器:创建DefaultWebSecurityManager
*/
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(CustomRealm customRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(customRealm);
return securityManager;
}
/**
* 开启注解支持:@RequiresRoles、@RequiresPermissions、@RequiresAuthentication、@RequiresUser、 @RequiresGuest
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);
return authorizationAttributeSourceAdvisor;
}
}
异常处理器(主要用于优化异常提示,不配置不影响使用):
package com.kawaxiaoyu.admin.admin_manage.excption;
import com.kawaxiaoyu.admin.admin_manage.common.util.ResultVoUtil;
import com.kawaxiaoyu.admin.admin_manage.vo.ResultVo;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* @ClassName: GloableExcption
* @author: txm
* @Description: 全局异常处理类
* @date: 2020/4/22 0022 下午 2:57
*/
//此注解表示会对controller层的异常进行处理
@ControllerAdvice
public class GloableException {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ExceptionHandler(Exception.class)
@ResponseBody
public ResultVo toError(Exception e, HttpServletRequest request) {
logger.debug(e.getMessage());
// 权限异常信息处理,还可优化
if(e instanceof UnauthenticatedException | e instanceof UnauthorizedException){
logger.error("业务异常:"+request.getRequestURL().toString()+":"+e.getMessage());
return ResultVoUtil.error("业务异常:权限不足");
}
}
登录接口权限认证(实际项目登录接口中添加,这里仅展示重要部分):
//添加用户认证信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName,password);
// shiro框架实现用户登陆信息验证
subject.login(usernamePasswordToken);
注解方式进行权限控制,展示内容仅在方法上使用权限控制注解:
@RequiresPermissions("/news/findAll")
@RequestMapping("/findAll")
public ResultVo findAll(Integer pageCurrent, Integer pageNum, String title) throws Exception{
业务处理逻辑.....
}
3.配置原理说明
shiro配置类说明:
配置ShiroFilterFactoryBean主要作用:主要是设置过滤指定资源,不做展开说明;
配置DefaultWebSecurityManager作用:过滤器设置以AuthorizationAttributeSourceAdvisor设置中需要使用权限核心控制器.
配置AuthorizationAttributeSourceAdvisor原因:
主要是用来配置五种权限注解,在项目启动过程中加载自定义配置类时触发.具体过程如下:
AuthorizationAttributeSourceAdvisor构造方法:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210423140714808.png)
AopAllianceAnnotationsAuthorizingMethodInterceptor构造方法中设置注解:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210423140848104.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzQwMTM4MA==,size_16,color_FFFFFF,t_70)
以@RequiresRoles为例说一下加载过程:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210423141530214.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzQwMTM4MA==,size_16,color_FFFFFF,t_70)
该构造方法中需要先创建RoleAnnotationHandler
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210423141653719.png)
创建RoleAnnotatioinHandler,执行构造方法就是将@RequiresRoles对应的class对象加载到shiro框架中顶级注解处理器AnnotationHandler.java中
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210423141824759.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210423172001611.png)
自定义CustomRealm配置说明:
自定义CustomRealm继承AuthorizingRealm,主要实现两个方法:doGetAuthorizationInfo(授权)、doGetAuthenticationInfo(认证);
首先说doGetAuthenticationInfo,用户执行登录操作调用subject.login(usernamePasswordToken);执行就会调用该方法。
doGetAuthorizationInfo执行逻辑:以@RequiresPermissions为例说明权限校验的主要逻辑:
AnnotationsAuthorizingMethodInterceptor.java中assertAuthorized方法,shiro框架定义了对应五种注解的方法拦截器,此方法用来判断进行访问方法中是否包含五种shiro权限注解中的一种,如果有则进入到是否满足权限的校验中.没有注解则不进行权限校验.
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210423133515604.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzQwMTM4MA==,size_16,color_FFFFFF,t_70)
getMethodInterceptors()中五种方法拦截器关联的五种注解:@RequiresRoles(需要指定角色访问)、@RequiresPermissions(需要指定权限访问)、@RequiresAuthentication(是否经过认证或者登陆)、@RequiresUser(需要认证登录)、@RequiresGuest(未认证或者叫未登陆).
访问接口中有@RequiresPermissions注解,DelegatingSubject.java中首先判断用户是否有principals,如果没有说明没有进行过登录认证,直接异常提示然后结束逻辑;如果有则判断用户是否拥有访问该方法的权限,主要进行校验权限的方法是AuthorizingRealm.java中isPermitted,此方法中执行的逻辑是:首先获取被访问的方法需要的权限信息(即@RequiresPermissions("/news/findAll")中的"/news/findAll"),然后调用自定义realm中doGetAuthorizationInfo执行方法获取用户拥有的权限信息,两者进行比较,如果没有则说明没有访问方法的权限,抛出异常.
权限校验:
![在这里片描述](https://img-blog.csdnimg.cn/20210423161134791.png)
没有用户属性标识信息抛出的异常信息:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210423161409505.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzQwMTM4MA==,size_16,color_FFFFFF,t_70)
权限校验失败异常信息:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210423163251737.png)
shiro项目启动信息说明:
每次请求都会被shiro框架过滤一次,主要的逻辑是DefaultSecurityManager.java中createSubject,创建一个subject的代理对象DelegatingSubject,然后将相关信息存入缓存中,此时的subject代理对象中不包含principals(主体的属性标识,可以是用户名、邮箱、用户id,但是需要保证唯一)、credentials(认证信息,比如说密码)信息;用户登录成功之后会将自定义realm中的principals存入到subject中。
4.DefaultFilter系统自带过滤器说明
shiro配置中可以对指定路径进行指定对应的过滤器,常用的有anno、authc、logout等,这里简单说一下系统是如何根据简称指定对应的过滤器。
默认过滤器:DefaultFilter.java
![在这里插入图片描述](https://img-blog.csdnimg.cn/34c22139b2af47a3b017fd7639b77abc.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzQwMTM4MA==,size_16,color_FFFFFF,t_70)
下面简单说一下过滤器执行过程:
项目启动过程中会通过默认过滤器管理器DefaultFilterChainManager将默认管理器(DefaultFilter中过滤器)添加到集合中.用户发出请求,每个请求都会进入到路径匹配过滤器PathMatchingFilter,preHandle中的逻辑就是将shiro自定义配置请求与过滤器集合进行遍历,进入到指定过滤器中执行对应的过滤操作.
![在这里插入图片描述](https://img-blog.csdnimg.cn/1e50b4d52e62464aad75b37d81c4c816.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzQwMTM4MA==,size_16,color_FFFFFF,t_70)