当前位置 博文首页 > 快乐的小三菊的博客:shiro授权(四)
? ? ? ?我们可以使用 shiro 进行授权操作,下面粘贴的是?LoginController?的代码,模拟用户登录的请求操作:
@Controller
@Slf4j
public class LoginController {
@RequestMapping("/login")
public String login(User user) {
if (StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassword())) {
return "error";
}
//用户认证信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
user.getUserName(),
user.getPassword());
usernamePasswordToken.setRememberMe(true);
try {
// 用户账号和密码进行验证
subject.login(usernamePasswordToken);
// 进行授权校验
if(subject.hasRole("admin")) {
// todo
}else {
// todo
}
if(subject.isPermitted("normalUser:query")) {
// todo
}else {
// todo
}
} catch (UnknownAccountException e) {
log.error("用户名不存在!", e);
return "error";
} catch (AuthenticationException e) {
log.error("账号或密码错误!", e);
return "error";
} catch (AuthorizationException e) {
log.error("没有权限!", e);
return "error";
}
return "shiro_index";
}
}
? ? ? ?这里面有 hasRole() 和 isPermitted() 两个方法,我们今天只对 hasRole() 方法来分析下,其实这两个方法底层调用的原理都是类似的。调用?subject.login() 方法之后,若用户输入的账号和密码没有任何问题,我们可以进行用户的角色判断或者是权限判断,说白了就是不同的角色所拥有的权限是不一样的,我们来看下底层的代码是如何实现的。
? ? ? ?我们进入源码查看下?hasRole()?方法底层是如何实现的,可以看到调用了?securityManager?安全管理器的 hasRole()?方法,其中?hasPrincipals() 方法是判断系统是否拥有主体用户,即系统是否有用户, getPrincipals() 方法是获取当前系统的主体用户。
public boolean hasRole(String roleIdentifier) {
return hasPrincipals() && securityManager.hasRole(getPrincipals(), roleIdentifier);
}
? ? ? ?我们进入源码查看下 securityManager.hasRole() 方法是如何实现的,我们可以发现,系统又调用了?getAuthorizationInfo() 方法。
public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
AuthorizationInfo info = getAuthorizationInfo(principal);
return hasRole(roleIdentifier, info);
}
? ? ? ?我们进入源码查看下 getAuthorizationInfo() 方法是如何实现的。由于是用户第一次登录,所以 getAvailableAuthorizationCache() 方法肯定返回 null 值,所以后面会调用?doGetAuthorizationInfo() 方法。
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
return null;
}
AuthorizationInfo info = null;
if (log.isTraceEnabled()) {
log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
}
Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
if (cache != null) {
if (log.isTraceEnabled()) {
log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
}
Object key = getAuthorizationCacheKey(principals);
info = cache.get(key);
if (log.isTraceEnabled()) {
if (info == null) {
log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");
} else {
log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");
}
}
}
if (info == null) {
// Call template method if the info was not found in a cache
// 主要看下这块的代码
info = doGetAuthorizationInfo(principals);
// If the info is not null and the cache has been created, then cache the authorization info.
if (info != null && cache != null) {
if (log.isTraceEnabled()) {
log.trace("Caching authorization info for principals: [" + principals + "].");
}
Object key = getAuthorizationCacheKey(principals);
cache.put(key, info);
}
}
return info;
}
? ? ? ?我们来看下 doGetAuthorizationInfo() ?这个方法是如何实现的,我们发现其实现类有如下几个,其中?CustomRealm?就是我们自定义实现的。
? ? ? ?我们再来看一下,我们自定义的?CustomRealm?中的?doGetAuthorizationInfo()?代码?,这个方法就是需要查询数据库中的用户、角色和权限,最后封装成?SimpleAuthorizationInfo?对象再返回去。
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取登录用户名
String name = (String)principalCollection.getPrimaryPrincipal();
// 查询用户名称
User user = userService.selectByUserName(name);
// 添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
List<String> roleNameList = new ArrayList<>();
List<String> permissionNameList = new ArrayList<>();
for (Role role : user.getRoles()) {
roleNameList.add(role.getRoleName());
for (Permission permission : role.getPermissions()) {
permissionNameList.add(role.getRoleName()+":"+permission.getPermissionName());
}
}
// 添加角色
simpleAuthorizationInfo.addRoles(roleNameList);
// 添加权限
simpleAuthorizationInfo.addStringPermissions(permissionNameList);
return simpleAuthorizationInfo;
}
? ? ? ?截至到目前为止,我们算是获取到了所有用户的角色和权限数据了,接下来就是看下 shiro 是怎么进行授权的,我们返回去再看下?AuthorizingRealm 类的?hasRole() 方法,我们可以看到,获取完信息之后会调用?hasRole() 方法。
public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
AuthorizationInfo info = getAuthorizationInfo(principal);
return hasRole(roleIdentifier, info);
}
? ? ? ?我们再来看一下 hasRole()? 方法是如何实现的,可以看见就是一个简单的 contains() 方法校验,看当前用户是否拥有判断条件中的角色。
protected boolean hasRole(String roleIdentifier, AuthorizationInfo info) {
return info != null && info.getRoles() != null && info.getRoles().contains(roleIdentifier);
}
? ? ? ?shiro 支持三种方式的授权,编码实现、注解实现以及 JSP?Taglig实现,下面分别介绍下
? ? ? ?通过写?if/else?授权代码块完成。
if(subject.hasRole("admin")) {
// 有权限
}else {
// 无权限
}
? ? ? ?通过在执行的?Java?方法上放置相应的注解完成,没有权限将抛出相应的异常。
@RequiresRoles("admin")
public void hello() {
// 有权限
}
? ? ? ?在?JSP/GSP?页面通过相应的标签完成。这种方式需要在 jsp 页面引入 shiro 的标签,如下所示:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
<shiro:hasRole name="admin">
<!--有权限-->
</shiro:hasRole>
规则:
???????资源标识符冒号(:)操作,冒号(:)对象实例 ID 即对哪个资源的哪个实例可以进行什么操作,其默认支持通配符权限字符串,冒号(:)表示资源/操作/实例的分割。
???????逗号(,)表示操作的分割。
???????星号(*)表示任意资源/操作/实例。
多层次管理:
? ? ? ?例如:user:query,user:edit
???????冒号是一个特殊字符,它用来分隔权限字符串的下一部件:第一部分是权限被操作的领域(打印机),第二部分是被执行的操作。
???????多个值:每个部件能够保护多个值。因此,除了授予用户 user:query 和 user:edit 权限外,也可以简单地授予它们一个:user:query,edit
???????还可以用 * 号代替所有的值,如:user.* ,也可以写:*:query,表示某个用户在所有的领域都有 query 的权限。
实例级访问控制,这种情况通常会使用三个部件:域、操作、被付诸实施的实例。如:user:edit:manager
???????也可以使用通配符来定义,如:user:edit:*、user:*:*、user:*:manager
???????部分省略通配符:缺少的部件意味着用户可以访问所有与之匹配的值,比如:user:edit 等价于 user:edit:* 、user 等价于 user:*:*
???????注意:通配符只能从字符串的结尾处省略部件,也就是说 user:edit 并不等价于?user:*:edit
? ? ? ?Shiro 提供了 JSTL 标签用于在 JSP 界面进行权限控制,如根据登录用户显示相应的页面按钮。下面简单的介绍几个标签。
? ? ? ?guest?标签:用户没有身份验证时显示相应信息,即游客访问信息。
<shiro:guest>
欢迎游客访问,<a href="login.jsp">登录</a>
</shiro:guest>
? ? ? ?user 标签:用户已经经过认证/记住我登录后显示相应的信息。
<shiro:user>
欢迎[<shiro:principal/>]登录,<a href="logout">退出</a>
</shiro:user>
? ? ? ?authenticated 标签:用户已经身份验证通过,即 Subject.login 登录成功,不是记住我登录的。
<!--如果在 CustomRealm 类的 doGetAuthenticationInfo() 中
SimpleAuthenticationInfo 的构造方法传入的是用户名,
可以采用下面这种写法获取当前登录用户-->
<shiro:authenticated>
用户[<shiro:principal/>]身份已验证通过
</shiro:authenticated>
<!--如果在 CustomRealm 类的 doGetAuthenticationInfo() 中
SimpleAuthenticationInfo 的构造方法传入的是对象,
可以采用下面这种写法获取当前登录用户。相当于((User)Subject.getPrincipals()).getName()-->
<shiro:authenticated>
用户[<shiro:principal property="userName"/>]身份已验证通过
</shiro:authenticated>
? ? ? ?notAuthenticated 标签:用户未进行身份验证,即没有调用 Subject.login 进行登录,包括记住我自动登录的也属于未进行身份验证。
<shiro:notAuthenticated>
未身份验证(包括记住我)
</shiro:notAuthenticated>
? ? ? ?principal 标签:显示用户身份信息,默认调用 Subject.getPrincipal() 获取,即 Primary Principal。
<shiro:principal property="username"/>
? ? ? ?hasRole 标签:如果当前 Subject 有角色将显示 body 体内容。
<shiro:hasRole name="admin">
用户[<shiro:principal/>]拥有角色admin
</shiro:hasRole>
? ? ? ?hasAnyRoles 标签:如果当前 Subject 有任意一个角色(或者关系)将显示在 body 体内容。
<shiro:hasAnyRoles name="admin,user">
用户[<shiro:principal/>]拥有角色admin或user</br>
</shiro:hasAnyRoles>
? ? ? ?lacksRole 标签:如果当前 Subject 没有角色将显示 body 体内容。
<shiro:lacksRole name="admin">
用户[<shiro:principal/>] 没有角色admin </br>
</shiro:lacksRole>
? ? ? ?hasPermission?标签:如果当前 Subject 有权限将显示 body 体内容。
<shiro:hasPermission name="user:create">
用户[<shiro:principal/>]拥有权限user:create </br>
</shiro:hasPermission>
? ? ? ?lacksPermission?标签:如果当前 Subject 没有权限将显示 body 体内容。
<shiro:lacksPermission name="org:create">
用户[<shiro:principal/>]没有权限org:create </br>
</shiro:lacksPermission>
?
?
?
cs