栏目分类:
子分类:
返回
终身学习网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
终身学习网 > IT > 软件开发 > 后端开发 > Java

springboot整合shiro的一次进阶与补充

Java 更新时间:发布时间: 百科书网 趣学号

springboot整合shiro的一次进阶与补充
  • 说明
  • 一、sql
  • 二、前后端不分离模式
    • (1)pom
    • (2)基础的业务搭建
      • entity
      • mapper
      • service
      • common
    • (3)shiro核心配置
      • realm
      • config
    • (4)使用
      • 登录认证
      • 授权认证
    • (5)CustomerRealm说明
      • doGetAuthorizationInfo
      • doGetAuthenticationInfo
    • (6)补充

说明

本文主要基于springboot,对shiro的一次进阶和补充,如具备有shiro和基础开发思想,观看本文效果更佳

本文仅为记录学习轨迹,如有侵权,联系删除

一、sql

本文基于springboot整合了shiro,实现了基于角色的权限控制系统的权限管理(RBAC),RBAC(role-based access control),基于角色的权限控制系统,是指对于不同角色的用户,拥有不同的权限 。用户绑定角色,角色绑定菜单权限和资源权限,形成用户-角色-权限的关系,

具体的sql如下:


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_perms
-- ----------------------------
DROp TABLE IF EXISTS `t_perms`;
CREATE TABLE `t_perms`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_perms
-- ----------------------------
INSERT INTO `t_perms` VALUES (1, 'user1:*:*', NULL);
INSERT INTO `t_perms` VALUES (3, 'user1:add', NULL);
INSERT INTO `t_perms` VALUES (4, 'user1:detail', NULL);
INSERT INTO `t_perms` VALUES (5, 'user1:edit', NULL);
INSERT INTO `t_perms` VALUES (6, 'user1:del', NULL);
INSERT INTO `t_perms` VALUES (7, 'user2:*.*', NULL);
INSERT INTO `t_perms` VALUES (8, 'user2:add', NULL);
INSERT INTO `t_perms` VALUES (9, 'user2:detail', NULL);
INSERT INTO `t_perms` VALUES (10, 'user2:edit', NULL);
INSERT INTO `t_perms` VALUES (11, 'user2:del', NULL);

-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_role
-- ----------------------------
INSERT INTO `t_role` VALUES (1, 'admin');
INSERT INTO `t_role` VALUES (2, 'user1');
INSERT INTO `t_role` VALUES (3, 'user2');

-- ----------------------------
-- Table structure for t_role_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_role_perms`;
CREATE TABLE `t_role_perms`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) NULL DEFAULT NULL,
  `perms_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_role_perms
-- ----------------------------
INSERT INTO `t_role_perms` VALUES (1, 1, 1);
INSERT INTO `t_role_perms` VALUES (2, 1, 7);
INSERT INTO `t_role_perms` VALUES (3, 2, 1);
INSERT INTO `t_role_perms` VALUES (4, 2, 3);
INSERT INTO `t_role_perms` VALUES (5, 2, 4);
INSERT INTO `t_role_perms` VALUES (6, 2, 5);
INSERT INTO `t_role_perms` VALUES (7, 2, 6);
INSERT INTO `t_role_perms` VALUES (8, 3, 7);
INSERT INTO `t_role_perms` VALUES (9, 3, 8);
INSERT INTO `t_role_perms` VALUES (10, 3, 9);
INSERT INTO `t_role_perms` VALUES (11, 3, 10);
INSERT INTO `t_role_perms` VALUES (12, 3, 11);

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `salt` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'admin', '1a5a87c78c15ccb7dce2c66da8ad02de', '0jgji');
INSERT INTO `t_user` VALUES (2, '用户1', '9280294433e60ffab79c9fa76bb13877', '0q1ry');
INSERT INTO `t_user` VALUES (3, '用户2', '8c032f06d637221c055798f04f88e906', 'wa4oj');

-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NULL DEFAULT NULL,
  `role_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_user_role
-- ----------------------------
INSERT INTO `t_user_role` VALUES (1, 1, 1);
INSERT INTO `t_user_role` VALUES (2, 2, 2);
INSERT INTO `t_user_role` VALUES (3, 3, 3);

SET FOREIGN_KEY_CHECKS = 1;

注:这里仅仅做了最基础的实现

二、前后端不分离模式 (1)pom

项目所需依赖如下

       
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
        
            mysql
            mysql-connector-java
            runtime
        

        
        
            org.projectlombok
            lombok
            true
        

        
        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        

        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.3.1
        

        
        
            com.alibaba
            druid
            1.1.21
        

        
        
            log4j
            log4j
            1.2.17
        

        
        
            cn.hutool
            hutool-all
            5.3.10
        

        
        
            org.apache.shiro
            shiro-spring
            1.8.0
        

        
        
            org.apache.shiro
            shiro-ehcache
            1.6.0
        


    
(2)基础的业务搭建

主要是对用户、角色、权限这三张表进行基础业务的编写,整合mybatis,实现基础的增删改查,用于后续shiro的配置

entity

主要有三个实体,User(用户)、Role(角色)、Perms(权限)

User实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User implements Serializable {

    private static final long serialVersionUID = 2052750694898007196L;

    @TableId(type = IdType.AUTO)
    private Long id;
    @TableField("name")
    private String name;
    @TableField("password")
    private String password;
    @TableField("salt")
    private String salt;

    @TableField(exist = false)//忽略该字段映射
    private List roles;
}

Role实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_role")
public class Role implements Serializable {

    private static final long serialVersionUID = 2148634916936785098L;

    @TableId(type = IdType.AUTO)
    private Long id;
    @TableField("name")
    private String name;

}

Perms实体

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_perms")
public class Perms implements Serializable {

    private static final long serialVersionUID = -2097887484456744985L;

    @TableId(type = IdType.AUTO)
    private Long id;
    @TableField("name")
    private String name;
    @TableField("url")
    private String url;

}
mapper

mapper层负责数据库的增删改查操作,具体结构如下

UserMapper

@Repository
@Mapper
public interface UserMapper extends BaseMapper {
}

UserMapper.xml






RoleMapper

@Mapper
@Repository
public interface RoleMapper extends BaseMapper {

    
    List selectRoleByUserName(String name);
}

RoleMapper.xml




    
        
        
    


    
        SELECT
            p.*
        FROM
            t_perms p
            LEFT JOIN t_role_perms rp ON p.id = rp.perms_id
            LEFT JOIN t_role r ON r.id = rp.role_id
        WHERe
            r.id = #{id}
    


service

service具体业务,结构如下:

UserServiceImpl

@Service
public class UserServerImpl extends ServiceImpl {


    
    public List findUserByName(String username) {
        LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getName,username);
        List userList = this.baseMapper.selectList(wrapper);
        return userList;
    }

    
    public Boolean login(User user) {
        //封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(user.getName(),user.getPassword());
        Subject subject = SecurityUtils.getSubject();

        try {
            

            // 执行登录方法,如果没有异常即成功了
            subject.login(token);
            return true;
        } catch (DisabledAccountException e){
            e.printStackTrace();
            log.error("禁用的帐号");
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            log.error("错误的帐号");
        } catch (ExcessiveAttemptsException e){
            e.printStackTrace();
            e.printStackTrace();
            log.error("登录失败次数过多");
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            log.error("错误的凭证(密码)");
        }catch (UnauthorizedException e){
            e.printStackTrace();
            log.error("权限不足");
        }catch (AuthenticationException e) {
            e.printStackTrace();
            log.error("未知的错误");
        }
        return false;
    }

    
    public Boolean register(User user) {
        //验证用户名是否存在
        List users = this.findUserByName(user.getName());

        if (users.isEmpty()) {
            //密码加密,随机盐
            String salt = RandomUtil.randomString(5);
            Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
            user.setPassword(md5Hash.toHex());
            user.setSalt(salt);
            return this.baseMapper.insert(user) == 1? true:false;
        } else {
            //用户名已经存在,注册失败
            return  false;
        }

    }
}

RoleServerImpl

@Service
public class RoleServerImpl extends ServiceImpl {


    
    public List findRoleByUserName(String name) {
        return this.baseMapper.selectRoleByUserName(name);
    }
}

PermsServerImpl

@Service
public class PermsServerImpl extends ServiceImpl {

    
    public List findPermsByRoleId(Long id) {
        return this.baseMapper.selectPermsByRoleId(id);
    }
}

common

这个用于封装通用返回实体类,以及一些通用的类

ResponseResult

@Slf4j
@Data
public class ResponseResult {

    
    private Integer code;

    
    private String msg;

    
    private T data;



    
    public static ResponseResult success(Integer code, String msg){
        ResponseResult result = new ResponseResult();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }


    
    public static ResponseResult success(Integer code, String msg,T data){
        ResponseResult result = new ResponseResult();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }

    
    public static ResponseResult failure(Integer code, String msg){
        ResponseResult result = new ResponseResult();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }



}

(3)shiro核心配置

只要有一点shiro基础的人都应该知道,其实shiro就两个核心的配置,一个是自定义的realm,只要继承AuthorizingRealm这个类即可,里面实现认证和授权的逻辑,还有一个是shiro的拦截和其他的核心配置,这个是真正意义上的核心配置,配置哪些路径需要拦截,哪些不拦截,未登录时应该跳往哪个路径,还有默认的注销方法配置等。

realm
@Slf4j
public class CustomerRealm extends AuthorizingRealm {

    @Autowired
    private UserServerImpl userServer;

    @Autowired
    private RoleServerImpl roleServer;

    @Autowired
    private PermsServerImpl permsServer;

    
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        


        // 获取用户名
        String username = (String) principalCollection.getPrimaryPrincipal();

        //添加用户权限
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //根据用户名称查询对应角色
        List roles = roleServer.findRoleByUserName(username);

        //依次添加权限
        for (Role role:roles){
            //角色信息添加
            simpleAuthorizationInfo.addRole(role.getName());
            List perms = permsServer.findPermsByRoleId(role.getId());

            //权限信息添加
            for(Perms perm:perms){
                simpleAuthorizationInfo.addStringPermission(perm.getName());
            }
        }

        return simpleAuthorizationInfo;
    }

    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        


        // 获取用户的信息:UsernamePasswordToken,里面封装了username和password
        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        // 根据用户名查询该用户
        List users = userServer.findUserByName(userToken.getUsername());

        // 如果用户不存在就抛出异常
        if(CollectionUtils.isEmpty(users)){
            throw new RuntimeException("用户不存在");
        }

        //密码认证,shiro自动处理
        return new SimpleAuthenticationInfo(
                users.get(0).getName(),
                users.get(0).getPassword(),
                ByteSource.Util.bytes(users.get(0).getSalt()),
                this.getName());
    }


}

config

ShiroConfig

@Configuration
public class ShiroConfig {

    
    @Bean(name = "customerRealm")
    public CustomerRealm customerRealm() {
        //修改凭证校验匹配器
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //md5算法
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        //散列次数
        hashedCredentialsMatcher.setHashIterations(1024);

        //应用凭证校验匹配器
        CustomerRealm customerRealm = new CustomerRealm();
        customerRealm.setCredentialsMatcher(hashedCredentialsMatcher);

        //开启缓存
        customerRealm.setCacheManager(new EhCacheManager());
        customerRealm.setCachingEnabled(true);
        //开启认证缓存
        customerRealm.setAuthenticationCachingEnabled(true);
        //给认证缓存起个名字
        customerRealm.setAuthenticationCacheName("authenticationCache");
        //开启授权缓存
        customerRealm.setAuthorizationCachingEnabled(true);
        //给授权缓存起个名字
        customerRealm.setAuthorizationCacheName("authorizationCache");

        return customerRealm;
    }

    
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("customerRealm") CustomerRealm customerRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //关联UserRealm
        securityManager.setRealm(customerRealm);
        return securityManager;
    }


    
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

        //关联安全管理器(DefaultWebSecurityManager)
        bean.setSecurityManager(securityManager);


        
        Map filterMap = new LinkedHashMap<>();
        // 登录
        filterMap.put("/login/page", "anon");
        filterMap.put("/login", "anon");

        // 注册
        filterMap.put("/register/page", "anon");
        filterMap.put("/register", "anon");

        // 注销
        filterMap.put("/logout", "logout");

        //拦截所有的这一行必须放在最后
        filterMap.put("
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

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


}


到这一步,基本完成了shiro的搭建,下面只需要写几个controller去测一下功能即可

(4)使用 登录认证

首先编写登录和注册的接口
LoginController

@RestController
@RequestMapping("/login")
public class LoginController {

    @Resource
    private UserServerImpl userServer;

    @PostMapping
    public ResponseResult login(User user){
        return userServer.login(user)?ResponseResult.success(200, "登录成功") : ResponseResult.failure(500, "登录失败");
    }

    @GetMapping("/page")
    public ResponseResult loginPage(){
        return ResponseResult.failure(500,"友情提示,请先登录");
    }


}

RegisterController

@RestController
@RequestMapping("/register")
public class RegisterController {

    @Resource
    private UserServerImpl userServer;

    @PostMapping
    public ResponseResult register(User user){
        return userServer.register(user)?ResponseResult.success(200, "注册成功") : ResponseResult.failure(500, "注册失败");
    }
}

启动项目进行测试,如果处于未登录状态的话,访问该系统的任意接口(哪怕这个接口不存在),都会先被拦截到配置好的登录接口,理论上这个接口应该跳转到一个页面(例如用thymeleaf跳转到html页面),在页面进行登录,这里为了演示就直接返回一个字符串提示

拦截生效了,再进行登录

授权认证

授权有种方式,同样是对于接口进行授权

注解说明案例
@RequiresRoles基于角色进行授权,可以绑定多个角色@RequiresRoles(value = {“user1”,“admin”},logical = Logical.OR)// 具备有user1或者admin角色即可访问
@RequiresPermissions基于某个详细的权限进行授权,可以绑定多个@RequiresPermissions(“user2:detail”)//只有具备user2:detail权限的用户才可以访问

下面进行接口的编写

User1Controller

@RestController
@RequestMapping("/user1")
@RequiresRoles(value = {"user1","admin"},logical = Logical.OR)// 具备有user1或者admin角色即可访问
public class User1Controller {


    @GetMapping("/detail")
    public ResponseResult detailAll(){
        return ResponseResult.success(200,"用户1查看");
    }


    @PostMapping("/edit")
    public ResponseResult edit() {
        return ResponseResult.failure(200,"用户1编辑");
    }

    @PostMapping("/add")
    public ResponseResult add(User user){
        return ResponseResult.failure(200,"用户1新增");
    }

    @PostMapping("/del")
    public ResponseResult del(){
        return ResponseResult.failure(200,"用户1删除");
    }
}

User2Controller

@RestController
@RequestMapping("/user2")
@RequiresRoles("admin")
public class User2Controller {

    @RequiresPermissions("user2:detail")
    @GetMapping("/detail")
    public ResponseResult detailAll(){
        return ResponseResult.success(200,"用户2查看");
    }

    @RequiresPermissions("user2:edit")
    @PostMapping("/edit")
    public ResponseResult edit() {
        return ResponseResult.failure(200,"用户2编辑");
    }

    @RequiresPermissions("user2:add")
    @PostMapping("/add")
    public ResponseResult add(User user){
        return ResponseResult.failure(200,"用户2新增");
    }

    @RequiresPermissions("user2:del")
    @PostMapping("/del")
    public ResponseResult del(){
        return ResponseResult.failure(200,"用户2删除");
    }
}

开始测试,先登录用户1(具备有User1Controller里面的所有接口权限)

登录成功后访问User1Controller里面的接口,一切正常

如果访问User2Controller里面的接口,就会发现无权限访问,直接报错

如果觉得直接返回500错误不优雅,可以在代码里面加个全局异常捕捉,进行返回友好提示

(5)CustomerRealm说明

对于CustomerRealm这个类,功能就是简单的授权和认证,那么,什么时候会触发里面的方法呢?

doGetAuthorizationInfo

首先是里面的doGetAuthorizationInfo方法,这个方法是授权的时候用的,上面在授权的时候,用到两个注解@RequiresRoles和@RequiresPermissions,只要用到这两个注解的时候就会触发doGetAuthorizationInfo方法,例如:用户1登录后,访问了用户1授权的接口就会触发这个方法

doGetAuthenticationInfo

这个方法是用于认证,触发的时机就只有用户在登录的时候触发,更准确来说,用户在执行下图的subject.login(token);方法时就会触发这个认证方法

(6)补充

像这种前后端不分离的模式,一般用的session进行认证,上面的代码可以整合thymeleaf,做几个页面完善一下就可以直接用了。

转载请注明:文章转载自 www.051e.com
本文地址:http://www.051e.com/it/888757.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 ©2023-2025 051e.com

ICP备案号:京ICP备12030808号