6

2流高手速成记(之五):Springboot整合Shiro实现安全管理 - 14号程序员

 2 years ago
source link: https://www.cnblogs.com/itfantasy/p/16837917.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

2流高手速成记(之五):Springboot整合Shiro实现安全管理

废话不多说,咱们直接接上回

上一篇我们讲了如何使用Springboot框架整合Nosql,并于文章最后部分引入了服务端Session的概念

而早在上上一篇中,我们则已经讲到了如何使用Springboot框架整合Mybatis/MybatisPlus实现业务数据的持久化(写入数据库)

本篇我们把关注点放在一个于这两部分有共同交集的内容——安全管理,并且引入我们今天的主角——Shiro框架

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

—— 来自百度百科

Shiro框架包含三个核心组件:

Subject —— 泛指当前与Shiro交互中的实体,可以是用户或者某后台进程

SecurityManager —— Shiro的核心组件,对内管理各种组件实例,对外提供各种安全服务

Realm —— Shiro与安全数据之间的桥接器

Shiro框架还包含有其他诸多概念,为降低大家的心智负担,这些我们暂且不谈,文末会给大家推荐延展阅读的相关文章

还是老规矩直接上干货,以完整的实例让大家对【如何基于Shiro实现权限的细粒度控制】有一个整体上的认知

1047129-20221028233045114-333096685.png

 

1047129-20221028233103992-1293655864.png

不知道大家会不会觉得项目结构突然变复杂?别担心,接下来我会给大家逐一拆解

1. 创建数据表

首先是角色表——role

1047129-20221028233732825-1631558384.png

 

1047129-20221028233742028-664171427.png

 然后是用户表——user

1047129-20221028233810142-316146140.png

 

1047129-20221028233821396-1396672442.png

 最后是权限表——permission

1047129-20221028233920483-377526455.png
1047129-20221028233939223-469149822.png

 2. 创建三个对应的Mapper

package com.example.hellospringboot.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.hellospringboot.model.Role;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface RoleMapper extends BaseMapper<Role> {
}
package com.example.hellospringboot.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.hellospringboot.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
}
package com.example.hellospringboot.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.hellospringboot.model.Permission;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface PermissionMapper extends BaseMapper<Permission> {
}

这里我们用到了上上一节讲到的内容

这里的Mapper会辅助于后续的安全数据读取

3. 接下来是Service及其实现类

package com.example.hellospringboot.service;

import com.example.hellospringboot.model.Role;

public interface RoleService {
    Role findRoleById(int id);
}
package com.example.hellospringboot.service.impl;

import com.example.hellospringboot.mapper.RoleMapper;
import com.example.hellospringboot.model.Role;
import com.example.hellospringboot.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RoleServiceImpl implements RoleService {

    @Autowired
    RoleMapper mapper;

    public Role findRoleById(int id){
        Role role = mapper.selectById(id);
        return role;
    }
}
package com.example.hellospringboot.service;

import com.example.hellospringboot.model.User;

public interface UserService {
    boolean checkUserByUsernameAndPassword(String userName, String passWord);
    User findUserByUserName(String userName);
}
package com.example.hellospringboot.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.hellospringboot.mapper.UserMapper;
import com.example.hellospringboot.model.User;
import com.example.hellospringboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper mapper;

    public boolean checkUserByUsernameAndPassword(String userName, String passWord){
        QueryWrapper<User> wrapper = new QueryWrapper<User>();
        wrapper = wrapper.eq("user_name", userName).eq("pass_word",passWord);
        List<User> userList = mapper.selectList(wrapper);
        return userList.size() > 0;
    }

    public User findUserByUserName(String userName){
        QueryWrapper<User> wrapper = new QueryWrapper<User>();
        wrapper = wrapper.eq("user_name", userName);
        User user = mapper.selectOne(wrapper);
        return user;
    }

}
package com.example.hellospringboot.service;

import com.example.hellospringboot.model.Permission;

import java.util.List;

public interface PermissionService {
    List<Permission> findPermissionsByRoleId(int roleId);
}
package com.example.hellospringboot.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.hellospringboot.mapper.PermissionMapper;
import com.example.hellospringboot.model.Permission;
import com.example.hellospringboot.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class PermissionServiceImpl implements PermissionService {

    @Autowired
    PermissionMapper mapper;

    public List<Permission> findPermissionsByRoleId(int roleId){
        QueryWrapper<Permission> wrapper = new QueryWrapper<>();
        wrapper = wrapper.eq("role_id", roleId);
        List<Permission> list = mapper.selectList(wrapper);
        return list;
    }

}

ok,我们已经准备好了所有的安全数据,及对应的读取方法

到这里,我们就算是做好了所有的准备工作

接下来看我们如何通过Shiro框架来运用这些已经装配好的枪炮子弹

4. 引入Shiro框架相关依赖(pom.xml)

        <!-- 引入shiro框架依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.10.0</version>
        </dependency>

这次pom.xml终于不是第一步了,哈哈哈。。。

5. 创建Realm嫁接Shiro框架及安全数据(realm/MyAuthorizingRealm)

package com.example.hellospringboot.realm;

import com.example.hellospringboot.model.Permission;
import com.example.hellospringboot.model.Role;
import com.example.hellospringboot.model.User;
import com.example.hellospringboot.service.PermissionService;
import com.example.hellospringboot.service.RoleService;
import com.example.hellospringboot.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class MyAuthorizingRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    @Autowired
    RoleService roleService;

    @Autowired
    PermissionService permissionService;

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String userName = token.getUsername();
        String passWord = String.valueOf(token.getPassword());
        if (!userService.checkUserByUsernameAndPassword(userName, passWord)) {//判断用户账号是否正确
            throw new UnknownAccountException("用户名或密码错误!");
        }
        return new SimpleAuthenticationInfo(userName, passWord, getName());
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        String userName = principalCollection.getPrimaryPrincipal().toString();
        User user = userService.findUserByUserName(userName);
        if (user == null) {
            throw new UnknownAccountException("用户名或密码错误!");
        }
        List<Integer> rolesList = user.rolesList();
        Set<String> roles = new HashSet<>();
        Set<String> permissions = new HashSet<>();
        for (Integer roleId : rolesList) {
            Role role = roleService.findRoleById(roleId);
            roles.add(role.getName());
            List<Permission> permissionList = permissionService.findPermissionsByRoleId(roleId);
            for (Permission permission : permissionList) {
                permissions.add(permission.getName());
            }
        }
        info.setRoles(roles);
        info.setStringPermissions(permissions);
        return info;
    }
}

Realm的创建对于整个Shiro安全验证体系搭建而言是至关重要的一步!

其中两个抽象方法

doGetAuthenticationInfo —— 用于校验用户名及密码的合法性

doGetAuthorizationInfo —— 用于赋予实体对应的角色及交互权限

6. 测试用Controller创建

package com.example.hellospringboot.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserController {

    @PostMapping("/login")
    public String login(String user, String pass) {
        UsernamePasswordToken token = new UsernamePasswordToken(user, pass);
        Subject subject = SecurityUtils.getSubject();
        if(!subject.isAuthenticated()) {
            try {
                subject.login(token);
            } catch (AuthenticationException e) {
                return e.getMessage();
            }
        }
        return "ok";
    }

    @PostMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        if(subject.isAuthenticated()) {
            try {
                subject.logout();
            } catch (AuthenticationException e) {
                return e.getMessage();
            }
        }
        return "ok";
    }

    @GetMapping("/admin")
    public String admin() {
        return "admin";
    }

    @GetMapping("/user")
    public String user() {
        return "user";
    }

}

内容很简单:

login——登录方法

logout——登出方法

admin、user——两个测试方法,用于测试不同角色对于不同方法可访问的细粒度控制

7. ShiroConfig配置类创建,实现用户访问权限的细粒度控制

package com.example.hellospringboot.configure;

import com.example.hellospringboot.realm.MyAuthorizingRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    @Bean
    public SecurityManager securityManager(Realm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    @Bean
    public MyAuthorizingRealm getRealm() {
        MyAuthorizingRealm realm = new MyAuthorizingRealm();
        return realm;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        Map<String, String> filterChainMap = new LinkedHashMap<String, String>();
        filterChainMap.put("/user/login", "anon");
        filterChainMap.put("/user/logout", "anon");
        filterChainMap.put("/user/admin", "authc,roles[admin],perms[admin:read]");
        filterChainMap.put("/user/user", "authc,roles[user],perms[user:read]");
        shiroFilter.setFilterChainDefinitionMap(filterChainMap);
        return shiroFilter;
    }
}

securityManager 和 getRealm 显示指定了Shiro两大组件的实例声明

shiroFilterFactoryBean 则是实现角色访问权限控制的重要方法

        filterChainMap.put("/user/login", "anon"); // 代表login方法可以匿名访问

        filterChainMap.put("/user/logout", "anon"); // 代表logout方法可以匿名访问

        filterChainMap.put("/user/admin", "authc,roles[admin],perms[admin:read]"); // 代表admin方法需要用户满足admin角色,同时具备admin:read权限

        filterChainMap.put("/user/user", "authc,roles[user],perms[user:read]"); // 代表user方法需要用户满足user角色,同时具备user:read权限

至此,整个接入流程便结束了

我们再次结合最开始我们配置的数据来对业务逻辑进行分析

用户 admin,同时具备admin、user两种角色

用户 juste,仅具备user一种角色

角色 admin,同时具备admin:write、admin:read两种权限

角色 user,同时具备user:write、user:read两种权限

用户 admin,同时具备admin:write、admin:read、user:write、user:read 四种操作权限

用户 juste,同时具备user:write、user:read两种操作权限

大家理清楚这其中的关系了吗?^ ^

8. 执行Postman验证结果

1047129-20221029001852346-2046877496.png

 我们在执行login之前,admin方法无权访问

1047129-20221029001929068-982320074.png

 

1047129-20221029001938733-543872710.png

 

1047129-20221029001947941-1198314407.png

 登录admin之后,同时具备admin和user方法的访问权限

1047129-20221029002020636-1369940051.png
1047129-20221029002031647-399341758.png
1047129-20221029002135499-899271175.png

logout登出,然后login登录普通用户juste

会发现依然具备user方法的访问权限,但是失去了admin方法的访问权限

到此,验证我们基于Shiro框架的细粒度权限控制已经实现

除了Shiro框架,我们还有另一个选择,那就是同样可以通过集成Spring Security框架来达成相同的目的

关于更多Shiro框架的内容,及其和Spring Security之间的异同,大家感兴趣可以参考这篇文章:

Shiro最全基础教程_思月行云的博客-CSDN博客

对于Spring Security框架,我们暂且留个悬念,以后会专门再给大家讲解这部分内容

下一节,我们将把关注点投向微服务领域,SpringCloudAlibaba将会是接下来几个章节的重头戏,敬请期待~

MyAuthorizingRealm

Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK