其他
这篇文章不会涉及 Session、Cache 等……下文是我对 Shiro 简单的理解,也许能让我这样的新手尽可能快地利用 Shiro 实现自己的需求……
详见 config/ShiroConfig
与 security/
下的代码。
Shiro
搜索任何一篇关于 Shiro 的资料,几乎都会在文章前面列出 Authentication、Authorization……我们先放下这些,关注权限控制的核心。
身份验证
首先,Shiro 把身份验证抽象为 principals(身份)和 credentials(凭证)。具体下来其实就对应我们平时使用的 username 与 password。简单来讲,「身份验证」其实也就对应「登录」。
登陆(身份验证)完成后,Shiro 首先确定了你是我们系统中的人(否则,你的状态就是游客),然后 Shiro 会通过某种方式去查询该用户「能做什么事」。
授权
「某用户能做什么事」在 Shiro 中对应的是「授权」的概念。要实现「授权」,了解 Shiro 中的四个概念即可。
Subject(主体)、Resource(资源)、Permission(权限)、Role(角色)。
Subject 就是你通过 username 和 password 登录进来的用户。
Resource 就是你要操作(增删改访问)的任何东西。比如一篇文章、一首歌……
Permission 是 Shiro 中的原子单位,指我们对某个 Resource 进行增删改访的权限。比如播放周杰伦的新专辑(你不买可不能播放哦)、比如访问某篇付费文章……
Role 其实就是 Permission 的集合。一般在用户层面被称为:普通用户、VIP、管理员、超级管理员……
就这些
要使用 Shiro 的话,了解这俩概念就行了。一个是身份验证,需要 principals 和 credentials。对应我们通常意义上的 username 和 password。一个是授权,核心是 Subject(主体)、Resource(资源)、Permission(权限)、Role(角色)。
身份验证其实就是 Authentication、授权信息则通过 Authorization 得到。
我们到配置 Shiro 的核心代码里看一下。
Realm 样例
暂且抛开配置,Shiro 的核心只需要我们写一个自己的 Realm。
简单来说,Realm 配置的就是如何身份验证以及如何返回权限信息。虽然说的是「如何去……」,实际上我们只需要配置从哪获得用户的真实 credentials,从哪获得用户的权限信息即可。因为 Shiro 已经把身份验证抽象为「对比用户登录时输入的 username、password 和注册时存储的信息」。
也就是说,我们只需要写一个自己的 Realm,配置身份验证和授权数据的来源就行了!
看看 Realm 的模板:
Realm 模板
1 | public class MyRealm extends AuthorizingRealm { |
如上,doGetAuthorizationInfo()、doGetAuthenticationInfo() 俩方法……
我们看看这俩方法的简单实现。
Realm 简单实现
权限验证
1 | protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { |
这里其实本来应该是一个简单的 username 与 password 的判断(我的 username 使用的是 email)。我的数据是保存在数据库里的,通过服务 userService 获取。
用户信息也可以不放在数据库,放内存或就保存到硬盘也是可以的嘛……
如果数据库保存的是加密后的信息(明文保存当然也行——如果你愿意),可想而知这里的判断逻辑大概就是这样:
1 | // 伪代码 |
但我这里用的是 jwt 控制权限。jwt 做的事情其实可以简单归结为:把用户名和密码加密为一个 token(字符串),不过这个 token 是可以解密的。我们可以通过给定的解密方式(提供的方法)逆获取 username 和 password。
授权信息
1 |
|
如上,先通过 user 获取 role,再通过 role 获取 permission。
这就是上面授权里提到的 Subject(主体)、Permission(权限)、Role(角色)。
而授权里的 Resource(资源),具体就可以是某个 Controller 里的 API,我们可以在 API 对应的方法上通过注解配置来控制。
例如:
1 | // 一个通过 Role 控制,一个通过 Permission 控制。 |
从这里看 user、role、permission,也很容易看出它们应该是多对多的关系。
一个 user 可以有多个 role,一个 role 也可以有多个 permisson。
补充——Shiro + JWT
Realm
上面的 Realm 模板并不完整,还需要重写一个 supports() 方法。
1 | public class MyRealm extends AuthorizingRealm { |
JWTToken 是自己定义的一个 token 类,即便不懂我们其实也能大概猜出这里 supports() 的作用:用自己定义的 JWTToken 来进行权限验证。
shiro + jwt 实现 RESTful API 认证方式
程序逻辑:
- POST 用户名与密码到 /login 进行登入,如果成功返回一个加密 token,失败的话直接返回 401 错误。
- 之后用户访问每一个需要权限的网址请求,必须在 header 中添加 Authorization 字段,例如 Authorization: token,token 为密钥。
- 后台会进行 token 的校验,如果不通过直接返回 401。
换种方式解释:
用户输入 用户名、密码
PS:需要前端加密吗?前端 RSA 加密
加密后的密码 与 通过 用户名获取的密码对比
成功 返回 token,失败 返回
header 中添加 Authorization 字段。例如 Authorization: token,token 为密钥。
配置
Shiro 怎么知道应该通过 token 来判断呢?
这个问题对应的是 Shiro 在 doGetAuthenticationInfo() 中传入的 auth 里的 Credential 为啥获取得到的是 token。(按上面的理解 Credential 不本该是密码吗……)
1 | protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { |
是通过在 Shiro 的配置里注入自己实现的 JWTFilter 实现的。配置后就会放弃普通的用户名、密码鉴权方式而使用 token,即 JWT 来鉴权了。
这也是为什么 Shiro 要用 Credential 这个概念而不直接弄个 password……
类似于 Spring MVC 里通过 DispatcherServlet 来控制请求,我们可以通过自己配置的 Filter 来进行权限控制。
具体不谈了。