天天看点

SpringBoot集成Shiro(二)验证用户角色

SpringBoot集成Shiro(一)验证用户登录验证

SpringBoot集成Shiro(三)验证用户权限

SpringBoot集成Shiro(四)验证用户角色升级版

Shiro 这个安全认证框架已经帮我们做了很多事情,在一般情况下,我们完全可以将它当做一个黑盒来使用。

在上一篇文章中,我们通过 Shiro 完成了用户登录验证功能,这篇文章将在它的基础上增加角色的控制。角色控制也很简单,大致分为以下几步:

  1. 增加想要角色控制的资源
  2. Shiro配置
  3. 角色设置

增加需要角色控制的资源

我们首先在 UserController 类下增加一个方法,它的访问URL就是 /user/getAllUser,我们将对这个方法进行角色控制:

@RequestMapping("getAllUser")
public String getAllUserInfo(){
    return "This is all user information.";
}
           

Shiro 配置

那么哪些角色可以访问到这个方法呢?需要我们在 ShiroConfig 配置类中 shiroFilter 方法里面增加配置(完整配置在下方):

// 1.创建拦截器 Map,按照顺序拦截,所以用 LinkedHashMap
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

// 2.配置不会被拦截的 URL,按照顺序判断,anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/user/register", "anon");
  
// 3.配置需要角色验证的 URL
filterChainDefinitionMap.put("/user/getAllUser", "roles[admin,developer]");

filterChainDefinitionMap.put("/**", "anon");

// 将拦截器绑定到 shiroFilterFactoryBean上
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
           

我们在配置拦截器的时候,会使用一个 Map,而且要按照顺序拦截,所以用 LinkedHashMap。

我们通过 Map 来指定这一个或者这一类 URL 使用哪个或哪些拦截器来进行拦截。Map 的 key 代表的是 要访问 URL,URL支持通配符!所以可以指定一类URL。

Map 的 value 代表的是使用到的拦截器。这个 value 不是随便写的哦。它是我们拦截器的简写名称,下面我列举出这些拦截器的简称和具体类的对应关系:

Filter Name Class
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter

这里我们将登陆注册接口和静态资源放行,这些资源不用认证就能访问。然后对要控制的资源 /user/getAllUser 使用 roles 过滤器,它对应的类就是 org.apache.shiro.web.filter.authz.RolesAuthorizationFilter。中括号里面两个字符串代表:这个资源需要有 admin 角色和 developer 角色才能进行访问。

注意:拦截器过滤链定义是从上向下顺序执行,一般将 “/**” 放在最为下边,这是一个坑呢,一不小心代码就不好使了。

提醒:如果要对一个URL进行多个拦截器的拦截,那么在 Map 的 value 里面使用逗号分隔。例如:

filterChainDefinitionMap.put("/user/getAllUser", “authc,roles[admin,developer]”);

接下来配置当我们未进行认证时去访问需要认证后才能访问的资源和角色授权失败后,Shiro做的处理:

// 跳转登录界面,如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/myLogin");

//未授权界面,在授权失败后会跳转到指定界面
shiroFilterFactoryBean.setUnauthorizedUrl("/Unauthorized");
           

也就是说,如果我们没有登陆就直接访问 /user/getAllUser 这个资源的话,Shiro 会帮我们自动拦截并跳转到登陆界面。如果角色授权失败了,就会跳转到我们上面配置的 Unauthorized 界面去。我们待会儿会进行验证看看结果就明白了。

至于 Shiro 是怎么知道我们有没有进行认证的呢?其实是 Shiro 就是通过 Session 进行会话管理的,当用户登录之后会生成一个 sessionId 返回给前端,前端保存在 cookie 里面,下次请求时携带上这个 sessionId,最后Shiro 在验证这个SessionId合不合法将就行了。

shiroFilter完整配置如下:

@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
    // 创建 ShiroFilterFactoryBean
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    // 将SecurityManager实例 并绑定给 ShiroFilterFactoryBean
    shiroFilterFactoryBean.setSecurityManager(securityManager);

    // 创建拦截器 Map,按照顺序拦截,所以用 LinkedHashMap
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    // 配置不会被拦截的链接 按照顺序判断
    filterChainDefinitionMap.put("/static/**", "anon");
    // 过滤链定义,从上向下顺序执行,一般将 "/**" 放在最为下边:这是一个坑呢,一不小心代码就不好使了;
    // anon:所有url都都可以匿名访问
    filterChainDefinitionMap.put("/user/login", "anon");
    filterChainDefinitionMap.put("/user/register", "anon");
    filterChainDefinitionMap.put("/user/getAllUser", "roles[admin,developer]");
    filterChainDefinitionMap.put("/**", "anon");

    // 登录界面,如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
    shiroFilterFactoryBean.setLoginUrl("/myLogin");

    //未授权界面,在授权失败后会跳转到指定界面
    shiroFilterFactoryBean.setUnauthorizedUrl("/Unauthorized");

    // 将拦截器绑定到 shiroFilterFactoryBean上
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

    return shiroFilterFactoryBean;
}
           

好了,现在我们已经明确的知道了哪些资源需要哪些角色才能访问,那么问题来了,我们如何把用户拥有的角色和Shiro配置里面的东西进行对应起来呢?

角色授权

接下来我们就来完成这块的逻辑,在此之前,先来回忆一下,在上一篇文章中,我们的 UserRealm 类里有一个授权方法没有实现,而现在,我们就要来实现它。

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    //  可以通过下面这个方法拿到前端传过来的用户名,然后根据这个用户名去数据库查询用户信息
    //  String username = (String) principalCollection.getPrimaryPrincipal();
    
    // 创建授权器
    SimpleAuthorizationInfo sao = new SimpleAuthorizationInfo();

    /* 模拟数据库查询出来的角色信息 */
    Set<String> set = new HashSet<>();
    set.add("admin");
    set.add("developer");

    // 将角色信息设置进授权器。
    sao.setRoles(set);

    return sao;
}
           

首先将当前用户拥有的角色信息查询出来,然后再将角色数据设置进授权器即可,这样 Shiro 就能自动帮我们进行角色认证了。

测试

不登录直接访问受限资源

接下来我们就进入测试,我们启动服务后,先不登录,而直接访问一下 /user/getAllUser 这个资源,看看有什么效果:

SpringBoot集成Shiro(二)验证用户角色

可以看出没有经过登录认证的话,直接访问受限资源会被 Shiro 拦截,出现 404 ,为什么会出现 404 呢?因为我们在上面配置里面配置了,如果认证失败就会跳转到我们指定的 “myLogin” 登录界面。可是我们没有这个界面,所以就出现了404.

在此我也建议:我们一般不会让后端去控制页面的跳转,我们会返回特定的状态码和消息给前端,前端根据不同的情况去跳转页面。这样处理比较好。

先登录再访问受限资源

现在我们测试认证过后访问资源的效果,先进行登录:

SpringBoot集成Shiro(二)验证用户角色

登录成功,下面访问资源:

SpringBoot集成Shiro(二)验证用户角色

可以看到访问成功了,成功的拿到了返回值。

权限不够访问受限资源

因为我们使用的是 Shiro 自带的角色验证规则,它的规则是——hasAllRoles,也就是说当前进行角色授权的用户,他必须拥有配置中的所有角色,可以多不可以少。举个例子:

配置文件中要求的角色是 admin 和 developer。如果用户只拥有两者其一的话,是会授权失败的。用户必须同时拥有 admin 和 developer 才可以。如果用户拥有 admin 和 developer 和 manager 三种角色也是可以的。

我们验证一下,我们将代码中用户的角色注释一个,然后重启服务,再登录,最后来访问看看:

/* 模拟数据库查询出来的角色信息 */
Set<String> set = new HashSet<>();
set.add("admin");
//set.add("developer");
           
SpringBoot集成Shiro(二)验证用户角色

Shiro 自带的角色验证规则难免有点要求过严了,那么有没有办法做到,用户只拥有两者之一就可以呢?答案是当然可以,我们在后续的角色授权升级版中会讲到,如何自定义角色验证规则,和上面提到的角色认证失败时不进行跳转,而只返回指定消息给前端。

完整代码可到此处下载:springboot-shiro-role-demo

技 术 无 他, 唯 有 熟 尔。

知 其 然, 也 知 其 所 以 然。

踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。

继续阅读