上篇文章有必要看一下:Shiro 基于角色授权的简单应用与权限过滤器配置含义
一、基于权限授权的简单应用
数据库:
用户admin, 只拥有角色 admin 和 user 与访问 /admin/userlist 的权限资源,如果 pid 为1和2,则两者都可访问。
1、spring.xml 在配置 shiro 的过滤器上,配置权限控制的拦截规则:
注意:规则是有顺序的,从上到下,拦截范围必须是从小到大的
<!-- 配置shiro的一些拦截规则,id必须和web.xml中的 shiro 拦截器名一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager" />
<!-- 身份认证失败,则跳转到登录页面的配置 -->
<property name="loginUrl" value="/login" />
<!-- 登录成功后的页面 -->
<property name="successUrl" value="/admin/index" />
<!-- 权限认证失败,则跳转到指定页面 -->
<property name="unauthorizedUrl" value="/unauthorized" /> <!-- 登录后访问没有权限的页面后跳转的页面 -->
<!-- Shiro连接约束配置,即过滤链的定义 -->
<property name="filterChainDefinitions">
<value>
<!-- 注意:规则是有顺序的,从上到下,拦截范围必须是从小到大的
url = 拦截规则(anon为匿名,authc为要登录后,才能访问,logout登出过滤) -->
/login = anon
/logout = logout
/admin/userlist = perms[userlist]
/admin/addUser = perms[addUser]
/admin/** = authc
/**= anon
</value>
</property>
</bean>
2、自定义 ShiroRealm类的继承 AuthorizingRealm 类:
登录认证方法不用修改,授权认证方法做相应修改
/**
* 在 shiro 中专门做授权认证的方法
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//1 从参数 principals 中获取当前登录成功后的用户信息
User user = principals.oneByType(User.class);
//2 根据第一步中的用户信息,获取角色信息(若用户信息包含角色/权限信息,直接取出,若没有,从数据库中获取)
Set<String> roles = RoleMapper.getRolesByUserid(user.getId());
// 通过用户关联的role信息,获取角色关联的 permisssion信息
Set<String> permissions = permissionMapper.getPermissionsByUserid(user.getId());
//3 把获取到的登录用户关联的角色和权限资源信息注入到返回的SimpleAuthorizationInfo对象中
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roles);
info.addStringPermissions(permissions);
return info;
}
PermissionMapper 方法:
<select id="getPermissionsByUserid" resultType="String">
select
p.pname
from
t_user u,t_role r,t_user_role ur,t_permission p,t_role_permission rp
where
u.id=ur.userid and ur.roleid=r.id and r.id=rp.rid and rp.pid=p.id
and u.id = #{userid}
</select>
3、登录访问项目
结果和数据库分析一致
二、Shiro 的授权注解
1、5个授权注解
@RequiresRoles(value = {"admin","user"},logical = Logical.AND)
当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。
@RequiresPermissions(value = {"userlist","userlist2"},logical=Logical.OR)
当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。
认证通过后接受 Shiro 授权检查,授权验证时,需要先判断当前角色是否拥有该权限,只有授权通过,才可以访问受保护 URL 对应的资源,否则跳转到“未经授权页面”。
@RequiresAuthentication
用于表明当前用户需是经过认证的用户。使用该注解标注的类/实例/方法在访问或调用时,要求当前Subject 必须在当前的session 中被认证通过才能被访问或调用。即 Subjec.isAuthenticated()返回 true。
@RequiresUser
该注解需要当前的Subject 是一个应用程序用户才能被注解的类/实例/方法访问或调用。一个“应用程序用户”被定义为一个拥有已知身份,或在当前session 中由于通过验证被确认,或者在之前session 中的'RememberMe'服务被记住。
@RequiresGuest
使用该注解标注的类/实例/方法在访问或调用时,当前Subject可以是“gust”身份,不需要经过认证或者在原先的session中存在记录,即游客身份。
2、使用方法
Shiro 的授权注解处理是有内定的处理顺序的,如果有多个注解的话,前面的通过了会继续检查后面的,若不通过则直接返回,处理顺序依次为(与实际声明顺序无关):
RequiresRoles
RequiresPermissions
RequiresAuthentication
RequiresUser
RequiresGuest
例如:你如果同时声明了 RequiresRoles 和 RequiresPermissions 注解,那就要求拥有此角色的同时还得拥有相应的权限。
三、基于权限授权的简单注解应用
使用注解 实现 第一点 的功能
1、spring.xml 在配置 shiro 的过滤器上
取消配置权限控制的拦截规则,
添加一个SimpleMappingExceptionResolver异常处理。
<!-- 配置shiro的一些拦截规则,id必须和web.xml中的 shiro 拦截器名一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager" />
<!-- 身份认证失败,则跳转到登录页面的配置 -->
<property name="loginUrl" value="/login" />
<!-- 登录成功后的页面 -->
<property name="successUrl" value="/admin/index" />
<!-- 权限认证失败,则跳转到指定页面 -->
<property name="unauthorizedUrl" value="/unauthorized" /> <!-- 登录后访问没有权限的页面后跳转的页面 -->
</bean>
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">unauthorized</prop>
<prop key="org.apache.shiro.authz.UnauthenticatedException">login</prop>
</props>
</property>
</bean>
2、自定义 ShiroRealm类的继承 AuthorizingRealm 类:和第一点相同处理
3、action 类中进行注解授权:
@RequiresGuest
@GetMapping(value= {"/","/login"})
public String login(Model model, HttpSession session) {
//生成一组16位随机数
int hashCodeValue = UUID.randomUUID().hashCode();
if(hashCodeValue < 0) hashCodeValue = -hashCodeValue;
String uuidSalt = String.format("%016d",hashCodeValue);//左边补0,16位,进制(d,x)
//把uuid盐值,同时保存在前后端
model.addAttribute("uuidSalt", uuidSalt);
session.setAttribute("uuidSalt", uuidSalt);
return "login";
}
@RequiresGuest
@PostMapping("/login")
public String login(User user, HttpSession session) {
//使用 shiro 登录验证
//1 认证的核心组件:获取 Subject 对象
Subject subject = SecurityUtils.getSubject();
//2 将登陆表单封装成 token 对象
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPazzword());
try {
//3 让 shiro 框架进行登录验证:
subject.login(token);
} catch (Exception e) {
e.printStackTrace();
return "loginError";
}
return "redirect:/admin/index";
}
@RequiresAuthentication
@GetMapping("/admin/index")
public String admin(Model model) {
return "admin/index";
}
@RequiresPermissions(value = {"userlist","userlist2"},logical=Logical.OR)
@GetMapping("/admin/userlist")
public String userlist() {
return "admin/userlist";
}
@RequiresPermissions(value = {"addUser"})
@GetMapping("/admin/addUser")
public String addUser() {
return "admin/addUser";
}
@GetMapping("/unauthorized")
public String unauthorized() {
return "unauthorized";
}
/**
* shrio 使用注解之后,需要自己实现退出
*/
@GetMapping("/logout")
public String logout() {
SecurityUtils.getSubject().logout(); //实现退出
return "redirect:/login";
}
4、登录访问项目:
结果和上面一致
注意:使用注解后的2个问题
1、退出登录需要自己实现(SecurityUtils.getSubject().logout();)。
2、没有登录认证访问无访问权限的页面后的跳转要自己配置实现(添加一个SimpleMappingExceptionResolver异常处理)。
不处理这两个问题会出现这个:
5、测试一下同时声明了 RequiresRoles 和 RequiresPermissions 注解
@RequiresRoles(value = {"admin2","user"},logical=Logical.AND)
@RequiresPermissions(value = {"userlist","userlist2"},logical=Logical.OR)
@GetMapping("/admin/userlist")
public String userlist() {
System.out.println("admin");
return "admin/userlist";
}
用户 admin,拥有 admin 和 user 角色,userlist 权限资源,所以上面会跳转到 无访问权限 页面。
把 RequiresRoles 中 admin2 改为 admin 时, 认证通过,能够访问 /admin/userlist 权限资源。
end ~