Shiro系列(五)--- Springboot整合Shiro实现基本授权流程

继续我们shiro系列博客相关的学习笔记,各位看到此博客的小伙伴,如有不对的地方请及时通过私信我或者评论此博客的方式指出,以免误人子弟。多谢!

在Shiro系列的前几篇中提到,Shiro的授权方式有三种,回顾一下, 3 种方式如下:

  • 以编程方式 - 您可以使用ifelse块等结构在 Java 代码中执行授权检查。

  • 注解方式 - 您可以将授权注释附加到您的 Java 方法。

  • JSP/GSP 标签 - 您可以根据角色和权限控制 JSP 或 GSP 页面输出。

下面着重记录一下编程方式和注解方式两种授权实现方式。

目录

编程方式授权

基于角色的授权

角色检查

角色断言

基于权限的授权

基于注解的授权

@RequiresAuthentication

@RequiresPermissions

@RequiresRoles

代码实现

开启注解支持

完善测试代码

完善后的CustomRealm

测试


编程方式授权

执行授权的最简单和最常见的方法可能是以编程方式直接与当前Subject实例交。

基于角色的授权

如果您只想检查当前Subject是否具有角色,您可以在Subject实例上调用变体hasRole*方法。

如果您想根据更简单/传统的隐式角色名称控制访问,您可以执行角色检查:

角色检查

可以使用Subject的hasRole*相关方法进行角色判断,相关方法如下:

Subject方法说明
hasRole(roleName)如果Subject有指定角色返回true,否则false
hasRoles(List<String> roleNames)返回与方法参数中的索引对应的hasRole结果数组。
hasAllRoles(Collection<String> roleNames如果拥有所有指定的角色,则返回true,否则返回false。

角色断言

检查布尔值以查看Subject是否拥有某个角色,您可以简单地在执行逻辑之前断言它们具有预期的角色。如果Subject没有预期的角色,将抛出AuthorizationException,如果有预期的角色将继续执行后面的代码。

相关方法如下:

Subject方法说明
checkRole(String roleName)如果Subject被分配了指定的角色,则继续执行,否则抛出AuthorizationException。
checkRoles(String... roleNames)如果Subject被分配了所有指定的角色,则继续执行,否则抛出AuthorizationException。
checkRoles(Collection<String> roleNames)同checkRoles(String... roleNames)

使用示例如下:

Subject currentUser = SecurityUtils.getSubject();
currentUser.checkRole("admin");
doSomething();

基于权限的授权

如果你想检查一个Subject是否被允许做某事,你可以调用各种isPermitted*方法变体中的任何一个。检查权限的主要方法有两种 - 基于对象的权限实例或表示权限的字符串。

感觉这种授权方式不是很方便,不想记录它了,常用的是基于注解的权限和上面这种,感兴趣的小伙伴可以看下官网介绍

基于注解的授权

@RequiresAuthentication

要求当前主题在当前会话期间已经过身份验证,这个注解基本上保证了subject . isAuthenticated() === true subject . isAuthenticated() === true。

示例:

@RequiresAuthentication
public void updateAccount(Account userAccount) {
    //this method will only be invoked by a 
    //Subject that is guaranteed authenticated
    ...
}

等效于:

public void updateAccount(Account userAccount) {
    if (!SecurityUtils.getSubject().isAuthenticated()) {
        throw new AuthorizationException(...);
    }
    
    //Subject is guaranteed authenticated here
    ...
}

@RequiresPermissions

需要当前执行者的 Subject 拥有特定的权限才能执行带注释的方法。

示例:

@RequiresPermissions("account:create")
public void createAccount(Account account) {
    //this method will only be invoked by a Subject
    //that is permitted to create an account
    ...
}

等效于:

public void createAccount(Account account) {
    Subject currentUser = SecurityUtils.getSubject();
    if (!subject.isPermitted("account:create")) {
        throw new AuthorizationException(...);
    }
    
    //Subject is guaranteed to be permitted here
    ...
}

@RequiresRoles

要求当前执行的Subject具有所有指定的角色。 如果他们没有角色,则不会执行该方法并抛出AuthorizationException 。

示例:

@RequiresRoles("administrator")
public void deleteUser(User user) {
    //this method will only be invoked by an administrator
    ...
}

 等效于:

public void deleteUser(User user) {
    Subject currentUser = SecurityUtils.getSubject();
    if (!subject.hasRole("administrator")) {
        throw new AuthorizationException(...);
    }
    
    //Subject is guaranteed to be an 'administrator' here
    ...
}

注意:如果您的应用程序具有动态安全模型,可以在运行时添加和删除角色,请小心使用此注释。 如果您的应用程序允许在运行时删除带注释的角色,则任何人都无法执行该方法(至少在再次创建具有相同名称的新角色之前)。

比如上面示例中,加入你在程序运行期间,通过后台权限管理删除了administrator角色,那么将不会有任何人拥有administrator角色,那么也必将产生任何人都无法执行deleteUser这个方法。

代码实现

我们以基于注解的方式实现权限控制,那么首先不要忘了在Shiro的配置文件中开启对注解的支持,开启方式如下:

开启注解支持

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
            @Qualifier("securityManager") SecurityManager securityManager)
    {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

完善测试代码

UserController

@Controller
@RequestMapping("/users")
public class UserController {

    @Autowired
    public UserManager userManager;

    @RequiresRoles("common")
    @RequestMapping("/list")
    @ResponseBody
    public List<User> list(){
        List<User> userList = userManager.findUsers();
        return userList;
    }

    @RequiresRoles("admin")
    @RequestMapping("/reset")
    @ResponseBody
    public String reset(){
        return " --- reset --- ";
    }

    @RequiresPermissions("system:user:delete")
    @RequestMapping("/delete")
    @ResponseBody
    public String delete(){
        return " --- delete --- ";
    }

    @RequiresPermissions("system:user:put")
    @RequestMapping("/put")
    @ResponseBody
    public String put(){
        return " --- put --- ";
    }

    @RequiresPermissions("system:user:view")
    @RequestMapping("/view")
    @ResponseBody
    public String view(){
        return " --- view --- ";
    }
}

如上:我们新增一个UserController类,前两个方法我们使用@RequiresRoles注解测试基于注解的角色验证,后面三个方法我们使用@RequiresPermissions注解测试基于注解的资源的权限验证,为了方便测试,我们需要完善一下index.html,同时我们在Realm中模拟下对用户的权限设置,先看下index.html:

<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login Page</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body >
   <span color="red">登录成功!</span>

   <a href="/users/list" > list </a> <br>
   <a href="/users/reset" > reset </a> <br>
   <a href="/users/delete" > delete </a> <br>
   <a href="/users/put" > put </a> <br>
   <a href="/users/view" > view </a> <br><br>
</body>
</html>

一般我们验证用户身份和权限的时候,需要查询数据库,这里为了方便,并没有建表存储用户对应的角色和权限,只是模拟设置了一下,如下我们在UserManager中新增两个方法模拟下给用户赋权:

    /**
     * 查询用户角色
     */
    public Set<String> findRoles(String userName){
        Set<String> stringSet = new HashSet<>();
        if("yjh".equals(userName)){
            stringSet.add("admin");
            stringSet.add("common");
        }else{
            stringSet.add("common");
        }
        return stringSet;
    }

    /**
     * 查询用户权限
     */
    public Set<String> findPermissions(String userName){
        Set<String> stringSet = new HashSet<>();
        if("yjh".equals(userName)){
            stringSet.add("*:*:*");
        }else{
            stringSet.add("system:user:view");
            stringSet.add("system:user:delete");
        }
        return stringSet;
    }

完善后的CustomRealm

public class CustomRealm extends AuthorizingRealm {
    @Autowired
    private UserManager userManager;
    @Autowired
    private LoginManager loginManager;

    /**
     * 认证: 登录时调用
     * AuthenticationToken 用于收集用户提交的身份(如用户名)及凭据(如密码)
     * Object getPrincipal() --- 身份
     * Object getCredentials() --- 凭据
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken utoken = (UsernamePasswordToken) token;
        // 登录验证逻辑
        String username = utoken.getUsername();
        String password = new String(utoken.getPassword());
        loginManager.login(username,password);
        // 如果身份认证验证成功,返回一个AuthenticationInfo实现,保存主体和凭据。
        return new SimpleAuthenticationInfo(utoken.getUsername(),new String(utoken.getPassword()),getName());
    }

    /**
     * 授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 得到用户
        String userName = (String)principals.getPrimaryPrincipal();
        // 查询用户角色和权限
        Set<String> roles = userManager.findRoles(userName);
        Set<String> permissions = userManager.findPermissions(userName);
        // 将角色和权限存储为内部属性
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(roles);
        authorizationInfo.setStringPermissions(permissions);
        return authorizationInfo;
    }
}

测试

我们数据库中有两个用户,对应的角色、权限如下

用户角色:yjh:admin、common;lisi:common。

用户权限:yjh:所有权限;lisi:system:user:view、system:user:delete。

我们期望使用lisi登录成功后,可以访问list()、view()、delete()方法,访问其他方法时因权限不足而失败,使用yjh登录的时候可以访问所有方法。

下面使用lisi登录访问下list()、view()、put()方法结果如下:

 

 

其他方法可以自行测试。

注意:如果不在shiro的配置文件中开启注解支持,会发现权限验证失效了,使用lisi登录后访问任何方法都可以访问。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值