Spring Boot 中集成 Shiro

InfoQ 2021-01-24 13:49:20
shiro spring boot 集成 中集


{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Shiro 是一个强大、简单易用的 Java 安全框架,主要用来更便捷的认证,授权,加密,会话管等等,可为任何应用提供安全保障。本课程主要来介绍 Shiro 的认证和授权功能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1. Shiro 三大核心组件","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Shiro 有三大核心的组件:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Subject","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"SecurityManager","attrs":{}}],"attrs":{}},{"type":"text","text":" 和 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Realm","attrs":{}}],"attrs":{}},{"type":"text","text":"。先来看一下它们之间的关系。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d7/d725f5bbb43c71695870b29bab8064de.webp","alt":"三大核心组件的关系","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Subject:认证主体。它包含两个信息:Principals 和 Credentials。看一下这两个信息具体是什么。","attrs":{}}]}],"attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Principals:身份。可以是用户名,邮件,手机号码等等,用来标识一个登录主体身份; ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Credentials:凭证。常见有密码,数字证书等等。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"说白了,就是需要认证的东西,最常见的就是用户名密码了,比如用户在登录的时候,Shiro 需要去进行身份认证,就需要 Subject 认证主体。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"2","normalizeStart":"2"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"SecurityManager:安全管理员。这是 Shiro 架构的核心,它就像 Shiro 内部所有原件的保护伞一样。我们在项目中一般都会配置 SecurityManager,开发人员大部分精力主要是在 Subject 认证主体上面。我们在与 Subject 进行交互的时候,实际上是 SecurityManager 在背后做一些安全操作。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"Realm:Realm 是一个域,它是连接 Shiro 和具体应用的桥梁,当需要与安全数据交互的时候,比如用户账户、访问控制等,Shiro 就会从一个或多个 Realm 中去查找。我们可以把 Realm 看成 DataSource,即安全数据源,一般我们会自己定制 Realm,在自定义 Realm 中,我们一般会从数据库中获取和认证相关的信息,这在下文自定义 Realm 部分会详细说明。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1. Shiro 身份和权限认证","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.2 Shiro 身份认证","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们来分析一下 Shiro 身份认证的过程,看一下官方的一个认证图:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8d/8d81e16b08fcd5c36b824fedbc180f31.png","alt":"认证过程","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Step1:应用程序代码在调用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Subject.login(token)","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法后,传入代表最终用户的身份和凭证的 AuthenticationToken 实例 token。 ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Step2:将 Subject 实例委托给应用程序的 SecurityManager(Shiro的安全管理)来开始实际的认证工作。这里开始真正的认证工作了。 ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Step3,4,5:然后 SecurityManager 就会根据具体的 realm 去进行安全认证了。 从图中可以看出,realm 可以自定义(Custom Realm)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.3 Shiro 权限认证","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"权限认证,也就是访问控制,即在应用中控制谁能访问哪些资源。在权限认证中,最核心的三个要素是:权限,角色和用户。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"权限(permission):即操作资源的权利,比如访问某个页面,以及对某个模块的数据的添加,修改,删除,查看的权利; ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"角色(role):指的是用户担任的的角色,一个角色可以有多个权限; ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用户(user):在 Shiro 中,代表访问系统的用户,即上面提到的 Subject 认证主体。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"它们之间的的关系可以用下图来表示: ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/67/6782d33681a7cc5d43e52955ba70c7f6.png","alt":"用户、角色和权限的关系","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个用户可以有多个角色,而不同的角色可以有不同的权限,也可由有相同的权限。比如说现在有三个角色,1是普通角色,2也是普通角色,3是管理员,角色1只能查看信息,角色2只能添加信息,管理员都可以,而且还可以删除信息,类似于这样。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2. Spring Boot 集成 Shiro 过程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.1 依赖导入","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Spring Boot 2.0.3 集成 Shiro 需要导入如下 starter 依赖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"xml"},"content":[{"type":"text","text":"\n org.apache.shiro\n shiro-spring\n 1.4.0\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.2 数据库表数据初始化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里主要涉及到三张表:用户表、角色表和权限表,其实在 demo 中,我们完全可以自己模拟一下,不用建表,但是为了更加接近实际情况,我们还是加入 mybatis,来操作数据库。下面是数据库表的脚本。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"sql"},"content":[{"type":"text","text":"CREATE TABLE `t_role` (\n `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',\n `rolename` varchar(20) DEFAULT NULL COMMENT '角色名称',\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8\n\nCREATE TABLE `t_user` (\n `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户主键',\n `username` varchar(20) NOT NULL COMMENT '用户名',\n `password` varchar(20) NOT NULL COMMENT '密码',\n `role_id` int(11) DEFAULT NULL COMMENT '外键关联role表',\n PRIMARY KEY (`id`),\n KEY `role_id` (`role_id`),\n CONSTRAINT `t_user_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`)\n) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8\n\nCREATE TABLE `t_permission` (\n `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',\n `permissionname` varchar(50) NOT NULL COMMENT '权限名',\n `role_id` int(11) DEFAULT NULL COMMENT '外键关联role',\n PRIMARY KEY (`id`),\n KEY `role_id` (`role_id`),\n CONSTRAINT `t_permission_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`)\n) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中,t","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"user,t","attrs":{}},{"type":"text","text":"role 和 t_permission,分别存储用户信息,角色信息和权限信息,表建立好了之后,我们往表里插入一些测试数据。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"t_user 表:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|id|username|password|role_id|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|:--:|:--:|:--:|:--:|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|1|csdn1|123456|1|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|2|csdn2|123456|2|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|3|csdn3|123456|3|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"t_role 表:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|id|rolename|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|:--:|:--:|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|1|admin|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|2|teacher|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|3|student|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"t_permission 表:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|id|permissionname|role_id|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|:--:|:--:|:--:|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|1|","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"user:*","attrs":{}}],"attrs":{}},{"type":"text","text":"|1|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|2|","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"student:*","attrs":{}}],"attrs":{}},{"type":"text","text":"|2|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解释一下这里的权限:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"user:*","attrs":{}}],"attrs":{}},{"type":"text","text":"表示权限可以是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"user:create","attrs":{}}],"attrs":{}},{"type":"text","text":" 或者其他,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"*","attrs":{}}],"attrs":{}},{"type":"text","text":" 处表示一个占位符,我们可以自己定义,具体的会在下文 Shiro 配置那里说明。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.2 自定义 Realm","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了数据库表和数据之后,我们开始自定义 realm,自定义 realm 需要继承 AuthorizingRealm 类,因为该类封装了很多方法,它也是一步步继承自 Realm 类的,继承了 AuthorizingRealm 类后,需要重写两个方法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"doGetAuthenticationInfo()","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法:用来验证当前登录的用户,获取认证信息 ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"doGetAuthorizationInfo()","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法:用来为当前登陆成功的用户授予权限和角色","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具体实现如下,相关的解释我放在代码的注释中,这样更加方便直观:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * 自定义realm\n * @author shengwu ni\n */\npublic class MyRealm extends AuthorizingRealm {\n\n @Resource\n private UserService userService;\n\n @Override\n protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {\n // 获取用户名\n String username = (String) principalCollection.getPrimaryPrincipal();\n SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();\n // 给该用户设置角色,角色信息存在t_role表中取\n authorizationInfo.setRoles(userService.getRoles(username));\n // 给该用户设置权限,权限信息存在t_permission表中取\n authorizationInfo.setStringPermissions(userService.getPermissions(username));\n return authorizationInfo;\n }\n\n @Override\n protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {\n // 根据token获取用户名,如果您不知道该该token怎么来的,先可以不管,下文会解释\n String username = (String) authenticationToken.getPrincipal();\n // 根据用户名从数据库中查询该用户\n User user = userService.getByUsername(username);\n if(user != null) {\n // 把当前用户存到session中\n SecurityUtils.getSubject().getSession().setAttribute(\"user\", user);\n // 传入用户名和密码进行身份认证,并返回认证信息\n AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), \"myRealm\");\n return authcInfo;\n } else {\n return null;\n }\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从上面两个方法中可以看出:验证身份的时候是根据用户输入的用户名先从数据库中查出该用户名对应的用户,这时候并没有涉及到密码,也就是说到这一步的时候,即使用户输入的密码不对,也是可以查出来该用户的,然后将该用户的正确信息封装到 authcInfo 中返回给 Shiro,接下来就是Shiro的事了,它会根据这里面的真实信息与用户前台输入的用户名和密码进行校验, 这个时候也要校验密码了,如果校验通过就让用户登录,否则跳转到指定页面。同理,权限验证的时候也是先根据用户名从数据库中获取与该用户名有关的角色和权限,然后封装到 authorizationInfo 中返回给 Shiro。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.3 Shiro 配置","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"自定义的 realm 写好了,接下来需要对 Shiro 进行配置了。我们主要配置三个东西:自定义 realm、安全管理器 SecurityManager 和 Shiro 过滤器。如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"配置自定义 realm:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Configuration\npublic class ShiroConfig {\n\n private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);\n\n /**\n * 注入自定义的realm\n * @return MyRealm\n */\n @Bean\n public MyRealm myAuthRealm() {\n MyRealm myRealm = new MyRealm();\n logger.info(\"====myRealm注册完成=====\");\n return myRealm;\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"配置安全管理器 SecurityManager:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Configuration\npublic class ShiroConfig {\n\n private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);\n\n /**\n * 注入安全管理器\n * @return SecurityManager\n */\n @Bean\n public SecurityManager securityManager() {\n // 将自定义realm加进来\n DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myAuthRealm());\n logger.info(\"====securityManager注册完成====\");\n return securityManager;\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"配置 SecurityManager 时,需要将上面的自定义 realm 添加进来,这样的话 Shiro 才会走到自定义的 realm 中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"配置 Shiro 过滤器:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Configuration\npublic class ShiroConfig {\n\n private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);\n \n /**\n * 注入Shiro过滤器\n * @param securityManager 安全管理器\n * @return ShiroFilterFactoryBean\n */\n @Bean\n public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {\n // 定义shiroFactoryBean\n ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();\n\n // 设置自定义的securityManager\n shiroFilterFactoryBean.setSecurityManager(securityManager);\n\n // 设置默认登录的url,身份认证失败会访问该url\n shiroFilterFactoryBean.setLoginUrl(\"/login\");\n // 设置成功之后要跳转的链接\n shiroFilterFactoryBean.setSuccessUrl(\"/success\");\n // 设置未授权界面,权限认证失败会访问该url\n shiroFilterFactoryBean.setUnauthorizedUrl(\"/unauthorized\");\n\n // LinkedHashMap是有序的,进行顺序拦截器配置\n Map filterChainMap = new LinkedHashMap<>();\n\n // 配置可以匿名访问的地址,可以根据实际情况自己添加,放行一些静态资源等,anon表示放行\n filterChainMap.put(\"/css/**\", \"anon\");\n filterChainMap.put(\"/imgs/**\", \"anon\");\n filterChainMap.put(\"/js/**\", \"anon\");\n filterChainMap.put(\"/swagger-*/**\", \"anon\");\n filterChainMap.put(\"/swagger-ui.html/**\", \"anon\");\n // 登录url 放行\n filterChainMap.put(\"/login\", \"anon\");\n\n // “/user/admin” 开头的需要身份认证,authc表示要身份认证\n filterChainMap.put(\"/user/admin*\", \"authc\");\n // “/user/student” 开头的需要角色认证,是“admin”才允许\n filterChainMap.put(\"/user/student*/**\", \"roles[admin]\");\n // “/user/teacher” 开头的需要权限认证,是“user:create”才允许\n filterChainMap.put(\"/user/teacher*/**\", \"perms[\\\"user:create\\\"]\");\n\n // 配置logout过滤器\n filterChainMap.put(\"/logout\", \"logout\");\n\n // 设置shiroFilterFactoryBean的FilterChainDefinitionMap\n shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);\n logger.info(\"====shiroFilterFactoryBean注册完成====\");\n return shiroFilterFactoryBean;\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"配置 Shiro 过滤器时会传入一个安全管理器,可以看出,这是一环套一环,reaml -> SecurityManager -> filter。在过滤器中,我们需要定义一个 shiroFactoryBean,然后将 SecurityManager 添加进来,结合上面代码可以看出,要配置的东西主要有:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"默认登录的 url:身份认证失败会访问该 url","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"认证成功之后要跳转的 url","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"权限认证失败会访问该 url","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要拦截或者放行的 url:这些都放在一个 map 中","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从上述代码中可以看出,在 map 中,针对不同的 url,有不同的权限要求,这里总结一下常用的几个权限。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|Filter|说明|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|:--:|:--:|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|anon|开放权限,可以理解为匿名用户或游客,可以直接访问的|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|authc|需要身份认证的|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|logout|注销,执行后会直接跳转到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"shiroFilterFactoryBean.setLoginUrl();","attrs":{}}],"attrs":{}},{"type":"text","text":" 设置的 url,即登录页面|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|roles[admin]|参数可写多个,表示是某个或某些角色才能通过,多个参数时写 roles[\"admin,user\"],当有多个参数时必须每个参数都通过才算通过|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"|perms[user]|参数可写多个,表示需要某个或某些权限才能通过,多个参数时写 perms[“user, admin”],当有多个参数时必须每个参数都通过才算通过|","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2.4 使用 Shiro 进行认证","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到这里,我们对 Shiro 的准备工作都做完了,接下来开始使用 Shiro 进行认证工作。我们首先来设计几个接口:","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接口一: 使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"http://localhost:8080/user/admin","attrs":{}}],"attrs":{}},{"type":"text","text":" 来验证身份认证","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接口二: 使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"http://localhost:8080/user/student","attrs":{}}],"attrs":{}},{"type":"text","text":" 来验证角色认证","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接口三: 使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"http://localhost:8080/user/teacher","attrs":{}}],"attrs":{}},{"type":"text","text":" 来验证权限认证","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接口四: 使用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"http://localhost:8080/user/login","attrs":{}}],"attrs":{}},{"type":"text","text":" 来实现用户登录","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后来一下认证的流程:","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"流程一: 直接访问接口一(此时还未登录),认证失败,跳转到 login.html 页面让用户登录,登录会请求接口四,实现用户登录功能,此时 Shiro 已经保存了用户信息了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"流程二: 再次访问接口一(此时用户已经登录),认证成功,跳转到 success.html 页面,展示用户信息。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"流程三: 访问接口二,测试角色认证是否成功。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"流程四: 访问接口三,测试权限认证是否成功。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.4.1 身份、角色、权限认证接口","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Controller\n@RequestMapping(\"/user\")\npublic class UserController {\n\n /**\n * 身份认证测试接口\n * @param request\n * @return\n */\n @RequestMapping(\"/admin\")\n public String admin(HttpServletRequest request) {\n Object user = request.getSession().getAttribute(\"user\");\n return \"success\";\n }\n\n /**\n * 角色认证测试接口\n * @param request\n * @return\n */\n @RequestMapping(\"/student\")\n public String student(HttpServletRequest request) {\n return \"success\";\n }\n\n /**\n * 权限认证测试接口\n * @param request\n * @return\n */\n @RequestMapping(\"/teacher\")\n public String teacher(HttpServletRequest request) {\n return \"success\";\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这三个接口很简单,直接返回到指定页面展示即可,只要认证成功就会正常跳转,如果认证失败,就会跳转到上文 ShrioConfig 中配置的页面进行展示。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.4.2 用户登录接口","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Controller\n@RequestMapping(\"/user\")\npublic class UserController {\n\n /**\n * 用户登录接口\n * @param user user\n * @param request request\n * @return string\n */\n @PostMapping(\"/login\")\n public String login(User user, HttpServletRequest request) {\n\n // 根据用户名和密码创建token\n UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());\n // 获取subject认证主体\n Subject subject = SecurityUtils.getSubject();\n try{\n // 开始认证,这一步会跳到我们自定义的realm中\n subject.login(token);\n request.getSession().setAttribute(\"user\", user);\n return \"success\";\n }catch(Exception e){\n e.printStackTrace();\n request.getSession().setAttribute(\"user\", user);\n request.setAttribute(\"error\", \"用户名或密码错误!\");\n return \"login\";\n }\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们重点分析一下这个登录接口,首先会根据前端传过来的用户名和密码,创建一个 token,然后使用 SecurityUtils 来创建一个认证主体,接下来开始调用 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"subject.login(token)","attrs":{}}],"attrs":{}},{"type":"text","text":" 开始进行身份认证了,注意这里传了刚刚创建的 token,就如注释中所述,这一步会跳转到我们自定义的 realm 中,进入 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"doGetAuthenticationInfo","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,所以到这里,您就会明白该方法中那个参数 token 了。然后就是上文分析的那样,开始进行身份认证。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"2.4.3 测试一下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,启动项目,测试一下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"浏览器请求 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"http://localhost:8080/user/admin","attrs":{}}],"attrs":{}},{"type":"text","text":" 会进行身份认证,因为此时未登录,所以会跳转到 IndexController 中的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"/login","attrs":{}}],"attrs":{}},{"type":"text","text":" 接口,然后跳转到 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"login.html","attrs":{}}],"attrs":{}},{"type":"text","text":" 页面让我们登录,使用用户名密码为 csdn1/123456 登录之后,我们在浏览器中请求 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"http://localhost:8080/user/student","attrs":{}}],"attrs":{}},{"type":"text","text":" 接口,会进行角色认证,因为数据库中 csdn1 的用户角色是 admin,所以和配置中的吻合,认证通过;我们再请求 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"http://localhost:8080/user/teacher","attrs":{}}],"attrs":{}},{"type":"text","text":" 接口,会进行权限认证,因为数据库中 csdn1 的用户权限为 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"user:*","attrs":{}}],"attrs":{}},{"type":"text","text":",满足配置中的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"user:create","attrs":{}}],"attrs":{}},{"type":"text","text":",所以认证通过。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下来,我们点退出,系统会注销重新让我们登录,我们使用 csdn2 这个用户来登录,重复上述操作,当在进行角色认证和权限认证这两步时,就认证不通过了,因为数据库中 csdn2 这个用户存的角色和权限与配置中的不同,所以认证不通过。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3. 总结","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本节主要介绍了 Shiro 安全框架与 Spring Boot 的整合。先介绍了 Shiro 的三大核心组件已经它们的作用;然后介绍了 Shiro 的身份认证、角色认证和权限认证;最后结合代码,详细介绍了 Spring Boot 中是如何整合 Shiro 的,并设计了一套测试流程,逐步分析 Shiro 的工作流程和原理,让读者更直观地体会出 Shiro 的整套工作流程。Shiro 使用的很广泛,希望读者将其掌握,并能运用到实际项目中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"课程源代码下载地址:","attrs":{}},{"type":"link","attrs":{"href":"https://gitee.com/eson15/springboot_study","title":""},"content":[{"type":"text","text":"戳我下载","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://xie.infoq.cn/article/8f84b0698ef04ba2ae547e9d1?utm_source=rss&utm_medium=article

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云