java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > springboot整合shiro

springboot整合shiro的过程详解

作者:桐花思雨

Shiro 是一个强大的简单易用的 Java 安全框架,主要用来更便捷的 认证,授权,加密,会话管理,这篇文章给大家详细介绍Shiro 工作原理及架构图,通过实例图文相结合给大家介绍的非常详细,需要的朋友参考下吧

什么是 Shiro

Shiro 是一个强大的简单易用的 Java 安全框架,主要用来更便捷的 认证,授权,加密,会话管理Shiro 首要的和最重要的目标就是容易使用并且容易理解,通过 Shiro 易于理解的API,您可以快速、轻松地获得任何应用程序——从最小的移动应用程序最大的网络和企业应用程序

Shiro 架构

Shiro 架构图

在这里插入图片描述

Shiro 工作原理

Shiro 的架构有三个主要概念:SubjectSecurityManagerRealms

在这里插入图片描述

Shiro 详细架构图

在这里插入图片描述

springboot 整合 shiro

springboot 整合 shiro 思路

在这里插入图片描述

项目搭建

主要依赖

<!--thymeleaf 模板引擎-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--shiro-->
<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-spring-boot-starter</artifactId>
	<version>1.4.0</version>
</dependency>
<!-- thymeleaf 集成 shiro -->
<dependency>
	<groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
	<version>2.0.0</version>
</dependency>

数据库表设计

CREATE TABLE `shiro_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  `nickname` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;

INSERT INTO `shiro_user` VALUES (1, 'lisi', '110110', '李四');
INSERT INTO `shiro_user` VALUES (2, 'zs', '123456', '逆风飞翔');
INSERT INTO `shiro_user` VALUES (3, 'jack', '111111', '砥砺奋进');
INSERT INTO `shiro_user` VALUES (4, 'Tom', '123123', '静夜思');
INSERT INTO `shiro_user` VALUES (5, 'nike', '222222', '杀伤力巨大');

CREATE TABLE `shiro_user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `role_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;

INSERT INTO `shiro_user_role` VALUES (1, 1, 1);
INSERT INTO `shiro_user_role` VALUES (2, 2, 3);
INSERT INTO `shiro_user_role` VALUES (3, 3, 3);
INSERT INTO `shiro_user_role` VALUES (4, 4, 2);

CREATE TABLE `shiro_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_code` varchar(255) NOT NULL,
  `role_name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;

INSERT INTO `shiro_role` VALUES (1, '1', '管理员');
INSERT INTO `shiro_role` VALUES (2, '2', '普通一级用户');
INSERT INTO `shiro_role` VALUES (3, '3', '普通二级用户');
INSERT INTO `shiro_role` VALUES (4, '4', '普通三级用户');

CREATE TABLE `shiro_auth_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `auth_id` int(11) NOT NULL,
  `role_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4;

INSERT INTO `shiro_auth_role` VALUES (1, 1, 1);
INSERT INTO `shiro_auth_role` VALUES (2, 2, 1);
INSERT INTO `shiro_auth_role` VALUES (3, 3, 1);
INSERT INTO `shiro_auth_role` VALUES (4, 4, 1);
INSERT INTO `shiro_auth_role` VALUES (5, 3, 2);
INSERT INTO `shiro_auth_role` VALUES (6, 4, 2);
INSERT INTO `shiro_auth_role` VALUES (7, 4, 3);
INSERT INTO `shiro_auth_role` VALUES (8, 4, 4);
INSERT INTO `shiro_auth_role` VALUES (9, 1, 3);

CREATE TABLE `shiro_auth` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `auth_code` varchar(255) NOT NULL,
  `auth_name` varchar(255) NOT NULL,
  `parent_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;

INSERT INTO `shiro_auth` VALUES (1, 'user:add', '添加', 1);
INSERT INTO `shiro_auth` VALUES (2, 'user:delete', '删除', 2);
INSERT INTO `shiro_auth` VALUES (3, 'user:update', '更新', 3);
INSERT INTO `shiro_auth` VALUES (4, 'user:list', '查看', 4);

在这里插入图片描述

实体类

public class User implements Serializable {
    private Integer id;

    @NotBlank(message = "账号不能为空")
    private String username;

    @NotEmpty(message = "密码不能为空")
    private String password;

    private String nickname;

	// set/get方法省略
}

public class Role {
    private Integer id;

    private String roleCode;

    private String roleName;
    
	// set/get方法省略
}

public class Auth {
    private Integer id;

    private String authCode;

    private String authName;

    private Integer parentId;

	// set/get方法省略
}

自定义 Realm

realmshiro 进行登录认证,权限,角色校验的关键,我们需要重写里面的方法

@Component
@Slf4j
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    // 授权,权限操作
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(@NotNull PrincipalCollection principals) {
        log.info("------进入授权操作了------");
        User user = (User) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 通过账号来查询相应的角色,权限数据
        List<AuthAndRoleVO> authAndRoleVOS = userService.selectAuthAndRole(user.getUsername());
        authAndRoleVOS.forEach(item -> {
            log.info("查询到的权限,角色:" + item.toString());
            String roleName = item.getRoleName();
            String authCode = item.getAuthCode();
            info.addStringPermission(authCode);
            info.addRole(roleName);
        });
        return info;
    }

    // 认证操作
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        log.info("------进入认证操作了------");
        // 拿到UsernamePasswordToken,它里面有用户名,密码数据
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        // 查询数据库
        User user = userService.selectOne(usernamePasswordToken.getUsername(), String.valueOf(usernamePasswordToken.getPassword()));
        if (user == null) {
            return null;
        }
        return new SimpleAuthenticationInfo(user, token.getCredentials(), getName());
    }
}

shiro 的配置类

@Configuration
public class ShiroConfig {

    /**
     * 安全管理器
     */
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(userRealm);     
        return defaultWebSecurityManager;
    }

    /**
     * thymeleaf模板引擎中使用shiro标签时,要用到
     */
    @Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        // 设置登录页面url
        shiroFilterFactoryBean.setLoginUrl("/user/login");
        shiroFilterFactoryBean.setSuccessUrl("/user/index");
        shiroFilterFactoryBean.setUnauthorizedUrl("/user/unauthorized");

        // 注意此处使用的是LinkedHashMap是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就不再继续验证
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
		// 静态资源放行
        filterChainDefinitionMap.put("/layer/**", "anon");
        filterChainDefinitionMap.put("/img/**", "anon");
        filterChainDefinitionMap.put("/jquery/**", "anon");
        // add.html页面放行
        filterChainDefinitionMap.put("/user/add", "anon");
        // update.html必须认证
        filterChainDefinitionMap.put("/user/update", "authc");
        // index.html必须认证
        filterChainDefinitionMap.put("/user/index", "authc");
        // 设置授权,只有user:add权限的才能请求/user/add这个url
        filterChainDefinitionMap.put("/user/add", "perms[user:add]");
        filterChainDefinitionMap.put("/user/update", "perms[user:update]");     

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
}

ShiroFilterFactoryBean 过滤器链配置中的 url 匹配规则

ShiroFilterFactoryBean 过滤器

ShiroFilterFactoryBean 过滤器分类

前端页面

登录页面 login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <link rel="shortcut icon" type="image/x-icon" th:href="@{/img/favicon.ico}" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow" />
</head>
<body>
<form action="" method="post">
    <p>
        账号:
        <label><input type="text" class="username" name="username"></label>
    </p>
    <p>
        密码:
        <label><input type="text" class="password" name="password"></label>
    </p>
    <p>
        <label><input id="checkbox1" type="checkbox" name="rememberMe"></label>记住我
    </p>
    <p><button type="button" class="loginBtn">登录</button></p>
</form>
</body>
<script type="text/javascript" th:src="@{/jquery/jquery-3.3.1.min.js}"></script>
<script type="text/javascript" th:src="@{/layer/layer.js}"></script><!--layui的弹出层-->
<script type="text/javascript">
    $(document).ready(function () {
        $('.loginBtn').on('click', function () { // 登录按钮
            const username = $('.username').val();
            const password = $('.password').val();
            $.ajax({// 用户登录
                type: 'post',
                url: '/user/doLogin',
                dataType: 'json',
                data: ({
                    'username': username,
                    'password': password
                }),
                success: function (resp) {
                    console.log(resp);
                    if (resp.code !== 200) {
                        layer.msg(resp.message, function () {// layui的弹窗
                        });
                    } else if (resp.code === 200) {
                        window.location.href = 'http://127.0.0.1:8080'+ resp.action;
                    }
                },
                error: function () {// 此处添加错误处理
                    layer.open({
                        title: '提示信息',
                        content: '后台访问错误,请联系管理员',
                        skin: 'layui-layer-molv',
                        icon: 0
                    });
                }
            });
        });
    });
</script>
</html>

首页页面 index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
    <link rel="shortcut icon" type="image/x-icon" th:href="@{/img/favicon.ico}" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow" />
</head>
<body>
    <h1>首页</h1>

    <a th:href="@{/user/add}" rel="external nofollow" >add</a> | <a th:href="@{/user/update}" rel="external nofollow" >update</a><br>

    <a th:href="@{/user/logout}" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >退出登录</a>
</body>
</html>

添加页面 add.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>add</title>
    <link rel="shortcut icon" type="image/x-icon" th:href="@{/img/favicon.ico}" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow" />
</head>
<body>
    <h1>add</h1><br>

    <a th:href="@{/user/logout}" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >退出登录</a>
</body>
</html>

更新页面 update.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>update</title>
    <link rel="shortcut icon" type="image/x-icon" th:href="@{/img/favicon.ico}" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow" />
</head>
<body>
    <h1>update</h1><br>

    <a th:href="@{/user/logout}" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" >退出登录</a>
</body>
</html>

未授权页面 unauthorized.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>未授权</title>
    <link rel="shortcut icon" type="image/x-icon" th:href="@{/img/favicon.ico}" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  rel="external nofollow" />
</head>
<body>
    <p1>未授权,无法访问此页面</p1></br>
    <a th:href="@{/user/index}" rel="external nofollow" >回到上一页</a>
</body>
</html>

controller 控制器

鉴于文章篇幅,这里只展示主要的逻辑代码

@Controller
@RequestMapping(path = "/user")
@Slf4j
public class UserController {

	@GetMapping(path = "/login")
    public String login() {
        return "login";
    }

    @GetMapping(path = "/index")
    public String index() {
        return "index";
    }

    @GetMapping(path = "/add")
    public String add() {
        return "add";
    }

    @GetMapping(path = "/update")
    public String update() {
        return "update";
    }
	
	// 未授权页面
    @GetMapping(path = "/unauthorized")
    public String unauthorized() {
        return "unauthorized";
    }

    // 用户登录
    @PostMapping(path = "/doLogin")
    @ResponseBody
    public ResultMap doLogin(@NotNull @Valid User user, @NotNull BindingResult bindingResult) {
        // ------参数校验------
        if (bindingResult.hasErrors()) {
            String message = Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage();
            log.info("校验的message信息为:" + message);
            return new ResultMap().fail().message(message);
        }
        // 将用户名,密码交给shiro
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
        String msg;
        try {
            // shiro帮我们匹配密码什么的,我们只需要把东西传给它,它会根据我们在UserRealm里认证方法设置的来验证
            Subject subject = SecurityUtils.getSubject();
            subject.login(token);
            return new ResultMap().success().action("/user/index");
        } catch (AuthenticationException e) {
            if (e instanceof IncorrectCredentialsException) {
                msg = "密码错误";
            } else if (e instanceof LockedAccountException) {
                msg = "用户被禁用";
            } else if (e instanceof UnknownAccountException) {
                msg = "用户不存在";
            } else {
                msg = "用户认证失败";
            }
        }
        return new ResultMap().error().message(msg);
    }

    // 用户退出登录
    @GetMapping(path = "/logout")
    public String logout() {
        SecurityUtils.getSubject().logout();
        return "login";
    }
}

shiro 注解

在 contrller 的这些方法中,也可以使用 shiro 提供的一些注解来校验用户,认证用户。不过个人认为使用这些注解有点麻烦(因为有些注解会抛出异常,然后再 controller 层还要捕获异常),所以我在 ShiroConfig 配置类中进行了配置

测试

启动项目,首先进入登录页面 login.html,如下

在这里插入图片描述

我们分别以数据库中的 {jack,111111} 和 {Tom,123123} 账号与密码进行测试

在这里插入图片描述

测试一

首先使用 {jack,111111} 来进行登录,如下

在这里插入图片描述

进入首页页面,如下

在这里插入图片描述

我们在接着查看控制台日志,如下

在这里插入图片描述

我们看到首页页面有两个超链接页面,以用户 jack 的身份分别进入两个页面。首先进入 add.html 页面,如下

在这里插入图片描述

说明用户 jack 拥有访问 add.html 的权限,此时在查看控制台日志,如下

在这里插入图片描述

注意查看用户 jack 的数据,他的权限只有 user/add 和 user/list,是没有 user/update 权限的,也就是没有权限访问 update.html 页面的。可以验证,我们再以用户 jack 的身份进入 update.html 页面,如下

在这里插入图片描述

关于测试,到此为止。当然,依然可以使用其他的数据在进行测试

小结

shiro 最为关键的就是 realm 了,继承 AuthorizingRealm,然后重写两个方法

在 controller 中的核心登录操作,就是将前端页面用户的登录数据(如账号,密码)交给 UsernamePasswordToken,然后使用当前的 Subject 对象调用 login(token) 方法即可,如下

// 将用户名,密码交给shiro
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());

// shiro帮我们匹配密码什么的,我们只需要把东西传给它,它会根据我们在UserRealm里认证方法设置的来验证
Subject subject = SecurityUtils.getSubject();
subject.login(token);

源码:springboot-shiro

到此这篇关于springboot整合shiro的文章就介绍到这了,更多相关springboot整合shiro内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文