- 本篇博客知识点:
- 引入 Shiro 的 maven 依赖
- 实现自己的 Realm
- 配置 Shiro 过滤器
准备工作
- 接上篇博客: springboot与shiro实现权限管理系统(二)——引入mybatis生成Entity,Dao和Mapper
- new 一个 modules,新建springboot子项目,命名为code3,然后将code2中所有内容复制到code3,code3将在code2的基础上进行增加与修改。
- 此篇博客对应代码:code3
- Gitee地址指路:https://gitee.com/alinxi/ShiroTest
1. 配置
-
引入 Shiro 的 maven 依赖
在最初创建项目时我们在 pom.xml 中引入了下面的 Shiro 依赖:<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.5.3</version> </dependency>
这是使用原生 Shiro 进行集成的方式,针对 Spring Boot 应用,Shiro 官方提供了专门的 Starter:
shiro-spring-boot-web-starter
来帮助开发者简化在 Spring Boot 应用中的集成过程。这里我们将采用 Starter 的方式,将上面的原生依赖替换为下面的 Starter 依赖:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.4.0</version> </dependency>
-
在
.../main/resources/application.yml
配置文件中对 Shiro 进行配置。shiro: web: enabled: true sessionManager: sessionIdCookieEnabled: true sessionIdUrlRewritingEnabled: true loginUrl: /login.html
-
实现
ACSRealm
在 …/com/code3/admin/config 目录下定义 ACSRealm 类,用来实现我们自己的 Realm,ACSRealm 将继承 AuthorizingRealm 。AuthorizingRealm 定义了两个抽象方法:doGetAuthorizationInfo 和 doGetAuthenticationInfo ,这两个方法是 Shiro 进行身份认证和授权的关键。
// ... @Autowired private SysUserDao sysUserDao; @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); SysUser user = sysUserDao.findByUsername(username); if (user == null) { throw new UnknownAccountException(); } String pwd = user.getPassword(); String password = new String(((char[]) token.getCredentials())); if (!pwd.equals(password)) { throw new IncorrectCredentialsException(); } return new SimpleAuthenticationInfo(username, pwd, getName()); } // ... // ... @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); Collection<Permission> permissions = loadPermissions(principals); authorizationInfo.addObjectPermissions(permissions); return authorizationInfo; } private Collection<Permission> loadPermissions(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); List<String> ps = sysUserDao.findAllPermissions(username); if (CollectionUtils.isEmpty(ps)) { return null; } return ps.stream().map(WildcardPermission::new).collect(Collectors.toList()); } // ...
对上述doGetAuthenticationInfo代码进行解析:
-
doGetAuthenticationInfo
是 Shiro 的一个回调,用于获取用户的身份信息,期间需要先将用户输入的凭证数据和用户预先保存数据库中的凭证数据进行比较,以确定是用户“本人”,该步骤决定了用户登录(SecurityUtils.getSubject().login()
方法调用)是否能够成功。 -
参数
AuthenticationToken
中封装了用户输入的凭证数据,在doGetAuthenticationInfo
方法中,首先要做的是找到用户预先保存在数据库中的凭证数据,这里借助用户输入的 username 进行查找(sysUserDao.findByUsername(username)
),实际开发中也可能是其它信息,如手机号,邮箱等,而无论实际载体是什么,只要能通过它唯一找到保存在数据库中的用户身份凭证数据即可。如果没找到,说明用户不存在,此时需要抛出 Shiro 为我们定义好的UnknownAccountException
异常,通过在SecurityUtils.getSubject().login()
的调用方法中捕获这些异常,我们就能知道登录失败的原因。如果在数据库中成功找到凭证数据,紧接着就要对用户输入的凭证数据和数据库中查询到的凭证数据进行比较,以得出用户是否输入正确 password 的结论,用户输入错误的密码时抛出IncorrectCredentialsException
异常。最后,如果登录成功,将用户身份信息封装成一个AuthenticationInfo
返回,同时将控制权交回给 Shiro,让 Shiro 处理之后的事情。 -
在此系统中,我们将会把密码以明文的方式保存到数据库中,以节省开发时间,把更多时间放在 Shiro 的使用上,实际开发中应该对密码进行加密,避免明文存储,此外 Shiro 提供了
CredentialsMatcher
接口类规范了密码校验过程,有PasswordMatcher
,HashedCredentialsMatcher
等实现可供使用。
对上述doGetAuthorizationInfo代码进行解析:
doGetAuthorizationInfo
方法用于获取用户的权限信息;- 方法
findAllPermissions
的位置:resources/mapper/SysUserMapper.xml
<select id="findAllPermissions" resultType="java.lang.String"> SELECT sp.permission_code FROM sys_user su LEFT JOIN sys_user_role sur ON su.id = sur.user_id LEFT JOIN sys_role_permission srp ON sur.role_id = srp.role_id LEFT JOIN sys_permission sp ON srp.permission_id = sp.id WHERE username = #{username} </select>
-
-
配置 SecurityManager :
.../com/code3/admin/config/ShiroConfig.java
//将自己的ACSRealm配置给 SecurityManager @Bean DefaultWebSecurityManager securityManager(ACSRealm realm) { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(realm); return manager; }
-
配置shiro过滤器
// Shiro 过滤器可以指定对 URL 路径应用什么样的访问控制策略,如控制某一 URL 允许匿名访问,或者需要完成身份认证(登录)后才能访问等. @Bean ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); // swagger 文档相关的接口允许匿名访问 chainDefinition.addPathDefinition("/swagger**", "anon"); chainDefinition.addPathDefinition("/webjars**", "anon"); // 登录接口允许匿名访问 chainDefinition.addPathDefinition("/api/login", "anon"); // 所有 api 开头的接口都需要登录 chainDefinition.addPathDefinition("/api/**", "authc"); return chainDefinition; }
我们使用 Swagger 作为文档和接口测试工具,所以将 Swagger-UI 需要访问的两类接口
(/swagger** 和 /webjars**)
设置为允许匿名访问;登录接口 URL 为/api/login
,允许匿名访问(下一节实验中我们会进行实现),剩余的所有以/api/
开头的接口需要登录后才能访问。接下来开发接口时我们将为所有接口的 URL 添加一个统一的
/api
前缀。
2. 本篇知识点补充
01 | Shiro的配置
- web.enabled 控制是否启用 Shiro 框架,true 表示启用,在项目开发阶段为了跳过安全检查可以设置为 false。
- sessionManager.sessionIdCookieEnabled:启用或停用通过 cookie 的会话状态保持,false 表示不允许将 sessionID(实际上是 JSESSIONID)放到 cookie 中。
- sessionManager.sessionIdUrlRewritingEnabled:是否允许将 sessionID 放到 URL 中,以 URL 参数的方式进行传递,当sessionManager.sessionIdCookieEnabled 设置为 true 时,该项可以设为 false。
- loginUrl 参数用来指定登录页面,当用户访问没有权限的 URL 时将自动跳转到该页面,login.html 的内容我们将在下一节实验中实现。
02 | 关于Realm
Realm
是 Shiro 的核心组件,是用户身份信息,权限以及角色信息的提供者,从数据库中获取数据,然后转交给 Shiro 进行身份认证和权限管理,开发中一般基于其抽象子类AuthorizingRealm
进行扩展,以减少工作量。
03 | 关于过滤器
- 常见的过滤器有下面几种:
- anon:允许匿名访问。
- authc:需要完成身份认证(登录)后才能访问。
- perms:可指定访问者必须具备的权限,如:“perms[role:add, user:list]”,表示访问者需要同时具备 “role:add” 和 “user:list” 权限才能访问当前路径。
- user:必须存在用户,身份认证通过或通过“记住我”认证通过的都可以访问。
完成。