Shiro 提供了与 Web 集成的支持,其通过一个 ShiroFilter 入口来拦截需要安全控制的URL,然后进行相应的控制
ShiroFilter 类似于如 Strut2/SpringMVC 这种 web 框架的前端控制器,是安全控制的入口点,其负责读取配置(如ini 配置文件;springboot可使用注解配置类),然后判断URL 是否需要登录/权限等工作。

一、拦截请求链接

1.shiro提供了一系列的链接过滤器:

注:过滤器一般实现org.apache.shiro.web.filter.authc.AuthenticatingFilter类

2.注入Shiro拦截器工厂类(ShiroFilterFactoryBean),配置链接

拦截器类入口方法是createInstance(),该类的主要作用是:

一、 创建了FilterChainManager,即过滤器管理类,包括2个重要属性

1.1 filters:管理全部链接过滤器,包括身份验证的过滤器,有anon,authcBasic,auchc,user和权限验证的过滤器,有perms,roles,ssl,rest,port。同时自定的过滤器也在FilterChainManager里。值得注意的是,过滤器都是单例的。
1.2 filterChains:过滤链。是一个Map对象,其中key为请求的url,value是一个NamedFilterList对象,存放与该url对应的一系列过滤器

二、将过滤器管理类设置到PathMatchingFilterChainResolver类里,该类负责路径和过滤器链的解析与匹配。根据url找到过滤器链。

@Configuration
public class ShiroConfig {/*** ShiroFilterFactoryBean 处理拦截资源文件问题。* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager* Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔*                    2、当设置多个过滤器时,全部验证通过,才视为通过*                   3、部分过滤器可指定参数,如perms,roles*/@Beanpublic ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 必须设置 SecurityManagershiroFilterFactoryBean.setSecurityManager(securityManager);shiroFilterFactoryBean.setLoginUrl("/toLogin");// 登录成功后要跳转的链接shiroFilterFactoryBean.setSuccessUrl("/Home");// 未授权界面;shiroFilterFactoryBean.setUnauthorizedUrl("/403");/*** 拦截器.* 定义shiro过滤链 Map结构* Map中key(xml中是指value值)*/Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();// 配置不会被拦截的链接 顺序判断,优先匹配filterChainDefinitionMap.put("/static/**", "anon");//配置需要认证才能访问的链接filterChainDefinitionMap.put("/**", "authc");// 配置退出过滤器filterChainDefinitionMap.put("/logout", "logout");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);}}

3.请求链接解析
Shiro会替代org.springframework.web.filter.DelegatingFilterProxy来实现动态代理。DelegatingFilterProxy过滤器的代理类会实现拦截请求,任何请求都会先经过shiro先过滤,直到成功才会执行javaweb本身的过滤器。源码级讲解。

二、登录认证

1、首先调用Subject.login(token) 进行登录,其会自动委托给SecurityManager
2、SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator 进行身份验证;SecurityManager接口继承了Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm 进行验证,验证规则通过AuthenticationStrategy接口指定
3、Authenticator 才是真正的身份验证者,ShiroAPI 中核心的身份认证入口点,此处可以自定义插入自己的实现;Authenticator 的职责是验证用户帐号,是ShiroAPI 中身份验证核心的入口点:如果验证成功,将返回AuthenticationInfo验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的AuthenticationException异常
4、Authenticator 可能会委托给相应的AuthenticationStrategy进行多Realm 身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm 身份验证;
5、Authenticator 会把相应的token 传入Realm,从Realm 获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。

1.认证思路

程序先获当前用户的Subject对象,然后判断用户是否已经登录,如果登录则不用做认证,若没有登录,则创建 UsernamePasswordToken对象,将用户名密码传入Subject 的login对象进行检验。

@RequestMapping("/toLogin")public String loginLogin(Model model, String username, String password, HttpSession userSession) {// 判断用户名和密码是否为空if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {// 用户名或者密码为空model.addAttribute("errorInfo", "用户名或者密码为空");return "/login";}//通过subject进行登录操作Subject subject = SecurityUtils.getSubject();if(!subject.isAuthenticated()){//创建封装了用户名和密码的UsernamePasswordToken对象UsernamePasswordToken token = new UsernamePasswordToken(username, password);try {subject.login(token);User user = (User) subject.getPrincipal();subject.getSession().setAttribute("user", user);subject.getSession().setAttribute("userRole", userRole);return "redirect:/Home";} catch (AuthenticationException e) {e.printStackTrace(); //打印异常错误// 用户名或者密码为空model.addAttribute("errorInfo", "用户名或者密码不正确");return "/login";}}else return "redirect:/Home";}

2.深入探究

之前的简介中,已经知道了Realm就是shiro与数据库打交道的对象。
Shiro 从 Realm 获取安全数据(如用户、角色、 权限),即 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作。
先讲Realm的认证方面:
简单地说:**subject.login(token);**这句代码调用到最后,就是调用AuthenticationRealm抽象类中的抽象方法doGetAuthenticationInfo方法,doGetAuthenticationInfo方法会返回SimpleAuthenticationInfo对象,源码级讲解。而该方法就是上面的校验过程中,实现认证的自定义Realm需要实现的方法。我们可以通过继承该类,实现该方法达到自定义。
值得注意的是: 一般继承 AuthorizingRealm(授权)即可;其继承了 AuthenticatingRealm(即身份验证),而且也间接继承了 CachingRealm(带有缓存实现)

@Component
public class AuthRealm extends AuthorizingRealm{@Autowiredprivate UserService userService;@Override//登陆认证模块protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {/** 为Shiro提供真实的用户数据* 1.通过token获取用户名和密码* 2.通过用户名和密码查询用户的真实的信息,真实的密码* 3获取数据后通过info对象返回给shiro安全管理器*///强转token为UsernamePasswordToken,才有getUsername等方法//此处的token,就是subject.login(token)中的tokenUsernamePasswordToken logintoken = (UsernamePasswordToken) token;String  username = logintoken.getUsername();//通过用户名查询用户信息User user = userService.finuserByUsername(username);//密码的比对://通过 AuthenticatingRealm 的 credentialsMatcher 属性来进行的密码的比对!//参数:user为数据库得到的对象,user.getUpassword()为数据库中的真实密码,this.getName()为当前的Realm名字,当验证通过时就返回,验证不通过就抛出异常。SimpleAuthenticationInfo还有其他参数,例如设置盐值加密。AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getUpassword(), this.getName()); return info;}
}

还有一点要强调的就是,shiro如何完成密码的比对?
我们知道此时,保存有用户信息的有UsernamePasswordToken和SimpleAuthenticationInfo两个对象,shiro肯定会去取出这两个对象中的信息进行比对。
简单地说:密码的具体比对工作是我们自定义的继承了AuthenticatingRealm父类的自定义
Realm类调用CredentialsMatcher的doCredentialsMatch方法完成的。源码级讲解。

3.多Realm认证

场景:假设某需求涉及使用两个角色分别是:学生、教师。要两者实现分开登录。即需要两个个Realm——StudentRealm和TeacherRealm,分别处理学生、教师的验证功能。
分析:正常情况下,当定义了多个Realm,无论是学生登录,教师登录,都会由这两个Realm共同处理。因为当配置了多个Realm时,我们通常使用的认证器是shiro自带的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中决定使用哪个Realm的是doAuthenticate()方法,该方法中通过getRealms()获取Realm集合,如果realm只有一个,执行的是doSingleRealmAuthentication方法,如果有多个,走的是doMultiRealmAuthentication方法。所以当我们使用ModularRealmAuthenticator类来配置多个Realm的时候,Shiro会使用我们配置的多个Realm进行认证。
补充:modularRealmAuthenticator是shiro提供的realm管理器,在这里可以设置realm的生效。通过setAuthenticationStrategy来设置多realm的使用规则。如果想自己进一步控制多realm,可以自己实现ModularRealmAuthenticator 。

实现方法:创建一个org.apache.shiro.authc.pam.ModularRealmAuthenticator的子类,并重写doAuthenticate()方法,让特定的Realm完成特定的功能。

1.通过创建一个org.apache.shiro.authc.UsernamePasswordToken的子类,在其中添加一个字段loginType,用来标识登录的类型,即是学生登录、教师登录。

enum  LoginType {STUDENT("Student"), TEACHER("Teacher")private String typeprivate LoginType(String type) {this.type = type}@Overridepublic String toString() {return this.type.toString()}
}

2.新建org.apache.shiro.authc.UsernamePasswordToken的子类UserToken

import org.apache.shiro.authc.UsernamePasswordToken
class UserToken extends UsernamePasswordToken {//登录类型,判断是学生登录,教师登录private String loginTypepublic UserToken(final String username, final String password,String loginType) {super(username,password)this.loginType = loginType}public String getLoginType() {return loginType}public void setLoginType(String loginType) {this.loginType = loginType}
}

3.新建org.apache.shiro.authc.pam.ModularRealmAuthenticator的子类UserModularRealmAuthenticator:

/*** 当配置了多个Realm时,我们通常使用的认证器是shiro自带的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中决定使用的Realm的是doAuthenticate()方法** 自定义Authenticator* 注意,当需要分别定义处理学生和教师和管理员验证的Realm时,对应Realm的全类名应该包含字符串“Student”“Teacher”。* 并且,他们不能相互包含,例如,处理学生验证的Realm的全类名中不应该包含字符串"Teacher"。*/
class UserModularRealmAuthenticator extends ModularRealmAuthenticator {private static final Logger logger = LoggerFactory.getLogger(UserModularRealmAuthenticator.class)@Overrideprotected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)throws AuthenticationException {logger.info("UserModularRealmAuthenticator:method doAuthenticate() execute ")// 判断getRealms()是否返回为空assertRealmsConfigured()// 强制转换回自定义的CustomizedTokenUserToken userToken = (UserToken) authenticationToken// 登录类型String loginType = userToken?.getLoginType()// 所有RealmCollection<Realm> realms = getRealms()// 登录类型对应的所有RealmCollection<Realm> typeRealms = new ArrayList<>()for (Realm realm : realms) {if (realm?.getName()?.contains(loginType))typeRealms?.add(realm)}// 判断是单Realm还是多Realmif (typeRealms?.size() == 1){logger.info("doSingleRealmAuthentication() execute ")return doSingleRealmAuthentication(typeRealms?.get(0), userToken)}else{logger.info("doMultiRealmAuthentication() execute ")return doMultiRealmAuthentication(typeRealms, userToken)}}
}

4.创建分别处理学生登录和教师登录的StudentShiroRealm,TeacherShiroRealm (即自定义 Realm,这里自行编写代码):
5.在ShiroConfig类中的SecurityManager方法进行相应的配置,当然,以下只是ShiroConfig类中的少部分配置,还有属性的配置没有展示出来。

@Configuration
public class ShiroConfig {@Beanpublic SecurityManager securityManager(AuthRealm m) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//设置realm.securityManager.setAuthenticator(modularRealmAuthenticator())List<Realm> realms = new ArrayList<>()//添加多个Realmrealms.add(teacherShiroRealm())realms.add(studentShiroRealm())securityManager.setRealms(realms)return securityManager}/*** 系统自带的Realm管理,主要针对多realm* */@Beanpublic ModularRealmAuthenticator modularRealmAuthenticator(){//自己重写的ModularRealmAuthenticatorUserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator()modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy())return modularRealmAuthenticator}@Beanpublic StudentShiroRealm studentShiroRealm() {StudentShiroRealm studentShiroRealm = new StudentShiroRealm()return studentShiroRealm}@Beanpublic TeacherShiroRealm teacherShiroRealm() {TeacherShiroRealm teacherShiroRealm = new TeacherShiroRealm()return teacherShiroRealm}

更多关于多Realm认证的细节,可以参考这位博主的文章。

三、授权(以注解为例)

1、首先调用Subject.isPermitted*/hasRole* 接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;
2、Authorizer是真正的授权者,如果调用如isPermitted(“user:view”),其首先会通过 PermissionResolver把字符串转换成相应的Permission 实例;
3、在进行授权之前,其会调用相应的Realm 获取Subject 相应的角色/权限用于匹配传入的角色/权限;
4、Authorizer 会判断Realm 的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole* 会返回true,否则返回false表示授权失败。

1.开启授权注解使用方式,在shiroconfig类中:

  /*** 开启授权注解使用的方式*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;

2.在自定义的realm中实现授权方法

public class AuthRealm extends AuthorizingRealm{@Autowiredprivate UserService userService;@Override//权限授权模块protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {User user = (User) principals.getPrimaryPrincipal();List<String> listPermission = userService.findAdminRole(user.getUserId());SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//授予admin权限info.addRole("admin");return info;}}

3.编写控制器(测试访问’/user/query’有权限,访问’/user/update’就没有权限)

@Controller
@RequestMapping("/user")
public class UserController{@RequiresRoles(value = {admin})@RequestMapping("/query")public String query(){return "/user";}@RequiresRoles(value = {user})@RequestMapping("/update")public String query(){return "/user";}
}

未完待续。。。

2.shiro工作原理(以集成springboot为例)相关推荐

  1. Shiro(二)通过shiro实现登录 连接数据库+集成Springboot

    第一步:重写Realm import com.baizhi.entity.User; import org.apache.shiro.authc.*; import org.apache.shiro. ...

  2. RabbitMQ消息队列工作原理及集成使用

    消息 指的是两个应用间传递的数据.数据的类型有很多种形式,可能只包含文本字符串,也可能包含嵌入对象. 消息队列(Message Queue) 是在消息的传输过程中保存消息的容器. 在消息队列中,通常有 ...

  3. 一图看懂区块链的工作原理、技术及用例

    对于突然爆火的区块链,虽然很多人能够叫得出名字,但真正了解的却不多.之前我们分享了JAXenter,TechRepublic和TheEnterprisers Project整理的关于区块链知识的一张图 ...

  4. Struts2→拦截器、工作原理、拦截器计算Action执行时间、Struts2自带拦截器、拦截器栈

    工作原理 实现拦截器 样例 Struts2自带拦截器 拦截器栈(先进后出) TOKEN防表单重复提交 文字拦截器 AJAX

  5. 深度学习的工作原理学习方式

    14天学习训练营导师课程: 李立宗<讲给入门者的深度学习> 工作原理 以培育水稻为例,影响水稻生长的因素包括:施肥量.灌溉量.施肥时间.插秧密度等. 想要获得影响生长因素的各项参数的最优值 ...

  6. Shiro(一)之shiro简介与工作原理

    一.shiro简介 这里我先来介绍一下什么是shiro,Apache Shiro 是 Java 的一个安全框架.目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring ...

  7. confluence 编辑器加载_Onlyoffice集成Confluence的工作原理

    onlyoffice 与 confluence集成使用,使用onlyoffice connector for confluence 插件对接confluence 插件在confluence中创建了一个 ...

  8. svn增量打包部署_超详细的jenkins持续集成工作原理、功能、部署方式等介绍

    概述 今天简单整理了一下jenkins的一些概念性内容,归纳如下: 1.概念 jenkins是一个开源项目,提供了一种易于使用的持续集成系统,使开发者从繁杂的集成中解脱出来,专注于更为重要的业务逻辑实 ...

  9. 超详细的jenkins持续集成工作原理、功能、部署方式等介绍

    文章由LinuxBoy分享于2019-08-30 11:08:39 超详细的jenkins持续集成工作原理.功能.部署方式等介绍 1. 概念 jenkins是一个开源项目,提供了一种易于使用的持续集成 ...

最新文章

  1. EZ430 Chronos 如何提高开发调试效率探讨
  2. 微软2013暑期实习笔试题目第5题分析
  3. boost::sort模块实现使用字符串键和索引函子对结构进行排序的示例
  4. gbdt 算法比随机森林容易_随机森林与GBDT
  5. 【渝粤教育】国家开放大学2019年春季 1308外国文学专题 参考试题
  6. Postman转换为Jmeter
  7. python经典书籍推荐:python编码规范
  8. c语言RTK算法,C-RTK 9P定位系统
  9. 二十三种设计模式[6] - 适配器模式(Adapter Pattern)
  10. [转]struct 和typedef struct什么区别
  11. 程序员让开,硅谷将是物理学家的天下,薪水高得离谱
  12. linux内核之系统调用
  13. mysql 查看修改连接数据库_mysql查看最大连接数和修改mysql数据库最大连接数方法...
  14. 智能卡APDU的命令及其解析
  15. Partial Multi-Label Learning with Label Distribution-(PML-LD)-文献翻译
  16. 同时开发两款H5的ARPG游戏的设计和实践
  17. 小程序源码:老人疯狂裂变引流视频推广微信小程序-多玩法安装简单
  18. 贪心算法 Greedy
  19. LitJson输出格式化Json字符串
  20. seatunnel 架构

热门文章

  1. linux scull 的内存使用
  2. leetcode-654-最大二叉树
  3. weex 打包apk
  4. 在线代码编辑器---codemirror插件
  5. Linux_Environment_Red Hat 卸载OpenJDK ,安装Orcl JDK 及 NTFS-3g
  6. static 成员小记
  7. 纯CSS实现提示框小三角
  8. 吴恩达 coursera AI 专项二第一课总结+作业答案
  9. Matlab cell矩阵处理
  10. 浅谈 Python 程序和 C 程序的整合