美文网首页
SpringBoot整合Shiro权限管理

SpringBoot整合Shiro权限管理

作者: yunqing_71 | 来源:发表于2019-11-24 15:17 被阅读0次

1.Shiro简简简简介

Apache Shiro™是一个功能强大且易于使用的Java安全框架,它执行身份验证,授权,加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的Web和企业应用程序。

image.png

可以看到Shiro的主要体系结构:
1、 Authentication 认证 ---- 用户登录
2、 Authorization 授权 --- 用户具有哪些权限
3、 Cryptography 安全数据加密
4、 Session Management 会话管理
5、 Web Integration web系统集成
6、 Interations 集成其它应用,spring、缓存框架

下面我主要讲前两个,认证和授权的用法。】

2.建立SpringBoot应用

声明:项目使用了SpringBoot、mybatis plus、RBAC的5张表(注意:我这里的系统默认一个用户只有一个角色)关于这几部门不明白的,请自行百度。

2.1项目引入的maven依赖有:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yunqing</groupId>
    <artifactId>questionnaire</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>questionnaire</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--springboot项目web相关依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis plus相关依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.29</version>
        </dependency>
        <!--前端h5使用springboot默认的thymeleaf模板引擎-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--shiro核心依赖-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>
        <!-- thymeleaf对shiro的扩展依赖,为了前端使用shiro标签 -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!--mysql驱动默认8.0-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--项目热部署依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!--lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.2创建LoginController首先令/user/index访问到主页面

package com.yunqing.questionnaire.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * Created by yunqing on 2019/11/20 23:33
 */
@Controller
@RequestMapping("/user")
public class LoginController {

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

}

springboot默认h5页面放在templates中


image.png

这样访问/user/index就能访问到上图的index.html页面了。

3.SpringBoot整合Shiro实现用户认证

3.1 分析Shiro的核心Api

Subject: 用户主体(把操作交给SecurityManager)
SecurityManager:安全管理器(关联Realm)
Realm:Shiro连接数据的桥梁

3.2 SpringBoot整合Shiro

3.2.1自定义realm类

/**
 * 自定义Realm
 * Created by yunqing on 2019/11/21 21:43
 */
@Slf4j
public class UserRealm extends AuthorizingRealm {

    /**
     * 执行授权逻辑
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("执行授权逻辑");
    }

    /**
     * 执行认证逻辑
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("执行认证逻辑");

    }
}

3.2.2编写Shiro配置类ShiroConfig

/**
 * Created by yunqing on 2019/11/21 21:39
 */
@Configuration
public class ShiroConfig {


    /**
     * 创建ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        
        return shiroFilterFactoryBean;
    }


    /**
     * 创建DefaultWebSecurityManager
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     * 创建Realm
     */
    @Bean(name = "userRealm")
    public UserRealm getRealm() {
        return new UserRealm();
    }


}

3.3使用Shiro内置过滤器实现页面拦截

/**
 * Created by yunqing on 2019/11/21 21:39
 */
@Configuration
public class ShiroConfig {


    /**
     * 创建ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        /**
         * 添加shiro内置过滤器
         * 常用的过滤器
         * anon:无需认证登录就可以访问
         * authc:必须认证才能访问
         * user:如果使用remeberMe的功能可以直接访问
         * perms:该资源必须得到资源权限才能访问
         * role:该资源必须得到角色权限才能访问
         */
        Map<String, String> filterMap = new LinkedHashMap<>();

        filterMap.put("/user/index", "anon");//设置/user/index不需要认证
        filterMap.put("/user/add", "authc");//设置/user/add需要认证
        //filterMap.put("/user/*", "authc");


        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        //Shiro默认需要认证跳转login.jsp,这里更改通过controller跳转到login.html
        shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
        //shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuth");
        return shiroFilterFactoryBean;
    }


    /**
     * 创建DefaultWebSecurityManager
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     * 创建Realm
     */
    @Bean(name = "userRealm")
    public UserRealm getRealm() {
        return new UserRealm();
    }


}

上面的代码运行后,首先localhost:8080/user/index进入index.html我们设置了不拦截,index.html页面如下

<body>
    <a href="/user/add">跳转添加页面</a>
    <a href="/user/update">跳转修改页面</a>
</body>

这时候的LoginController页面也加入了这几个跳转的方法

@GetMapping("/index")
    public String index() {
        return "index";
    }
    @GetMapping("/add")
    public String add() {
        return "add";
    }
    @GetMapping("/update")
    public String update() {
        return "update";
    }
    @GetMapping("/toLogin")
    public String toLogin() {
        return "login";
    }

如上面设置/user/add需要认证,/user/update没设置认证

image.png
image.png
image.png

3.4实现用户认证(登录)操作

3.4.1 登录页面login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登录页面login
<h3 th:text="${msg}" style="color: red"></h3>
<form action="/user/login">
    用户名:<input type="text" name="username"/><br/>

    密码:<input type="password" name="password"/><br/>

    <input type="submit" value="登录">
</form>
</body>
</html>

3.4.2 编写LoginController中的登录逻辑

@GetMapping("/login")
    public String login(String username, String password, Model model) {
        System.out.println(username);
        /**
         * 1.获取Subject
         */
        Subject subject = SecurityUtils.getSubject();

        /**
         * 把用户名,密码封装进UsernamePasswordToken
         */
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try{
            /**
             * subject携带token调用login,不出异常则登陆成功
             * 具体登录去ShiroConfig的认证里去决定
             */
            subject.login(token);
            //登陆成功
            return "redirect:index";//重定向到index页面

        } catch (IncorrectCredentialsException e) {
            //e.printStackTrace();
            //登录失败:密码错误
            model.addAttribute("msg", "密码错误");
            return "login";
        } catch (UnknownAccountException e) {
            //e.printStackTrace();
            //登录失败:用户名不存在
            model.addAttribute("msg", "用户名不存在");
            return "login";
        }

    }

3.4.3编写自定义realm的认证逻辑,去数据库查询用户名密码

/**
     * 执行认证逻辑
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("执行认证逻辑");
        
        //通过authenticationToken获取当前登录用户信息
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        //mybatis plus 去数据库根据登录名查询User
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("login_name", token.getUsername());
        User user = userService.getOne(wrapper);
        //判断是否查询到
        if (StringUtils.isEmpty(user)) {
            return null; //shiro内部会抛出UnknownAccountException异常
        }
        //判断密码,如果认证成功,则第一个参数是传给授权逻辑的,相当于授权逻辑中的Principal()
        return new SimpleAuthenticationInfo(user, user.getPassWord, "");

    }

登录成功后重定向到index.html再次点击跳转添加页面,成功

image.png

测试一下登录过程中用户名不存在,和密码错得情况,看看利用Model返回的msg,login.html页面的thymeleaf标签<h3 th:text="${msg}"></h3>是否接收到msg的值。

image.png
image.png

4.实现用户授权

4.1 使用shiro内置过滤器拦截资源

/**
     * 创建ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        /**
         * 添加shiro内置过滤器
         * 常用的过滤器
         * anon:无需认证登录就可以访问
         * authc:必须认证才能访问
         * user:如果使用remeberMe的功能可以直接访问
         * perms:该资源必须得到资源权限才能访问
         * role:该资源必须得到角色权限才能访问
         */
        Map<String, String> filterMap = new LinkedHashMap<>();

        filterMap.put("/user/index", "anon");//路径/user/index不需要认证
        filterMap.put("/user/login", "anon");//路径/user/login不需要认证
        filterMap.put("/user/add", "perms[user:add]");//路径/user/add不仅需要登录,还需要登录的角色拥有访问授权字符串为user:add资源的权利
        //filterMap.put("/user/update", "perms[user:update]");
        filterMap.put("/user/*", "authc");//拦截/user/*的路径需要进行认证,不需要认证的写在上面了


        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        shiroFilterFactoryBean.setLoginUrl("/user/toLogin");
        shiroFilterFactoryBean.setUnauthorizedUrl("/user/noAuth");//登录后如果没有访问权限,设置跳转noAuth.html页面
        return shiroFilterFactoryBean;
    }

4.2 完成资源的授权,修改UserRealm类

@Autowired
    private UserService userService;
    @Autowired
    private UserRoleService userRoleService;
    @Autowired
    private RoleResourceService roleResourceService;
    @Autowired
    private ResourceService resourceService;
    /**
     * 执行授权逻辑
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("执行授权逻辑");
        /**
         * 给user:add进行授权
         */
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //SecurityUtils.getSubject() 获取当前user
        //info.addStringPermission("user:add");
        //Subject subject = SecurityUtils.getSubject();
        User user = (User) principalCollection.getPrimaryPrincipal();//获取当前user
        QueryWrapper<UserRole> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", user.getId());
        UserRole userRole = userRoleService.getOne(wrapper);//获取角色id 当前系统默认一个用户对应一个角色。
        if (StringUtils.isEmpty(userRole)) return null;

        String roleId = userRole.getRoleId();
        QueryWrapper<RoleResource> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("role_id", roleId);
        List<RoleResource> list = roleResourceService.list(queryWrapper);//查出角色对应的资源id
        List<String> strs = new ArrayList<>();
        list.forEach(e->strs.add(e.getResourceId()));
        Set<String> flags = new HashSet<>();
        strs.forEach(e->{
            String flag = resourceService.getById(e).getFlag();
            if (flag!=null && !"".equals(flag))
            flags.add(flag);
        });

        info.addStringPermissions(flags);//授权当前角色可以访问的资源
        return info;
    }
image.png

5.设置页面只显示当前角色能访问的资源thymeleaf整合shiro

5.1 配置ShiroDialect,在ShiroConfig中配置

    /**
     * 使前端shiro-thymeleaf生效
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

5.2 在页面上使用shiro标签,index.html页面

<!DOCTYPE html>
<html lang="en" xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div shiro:hasPermission="user:add">
    <a href="/user/add">跳转添加页面</a>
</div>
<div shiro:hasPermission="user:update">
    <a href="/user/update">跳转修改页面</a>
</div>

</body>
</html>

注意,因为加入了shiro授权标签,所以最初访问/user/index就不会显示内容,因为没有被授权,这时候,我们可以直接通过/user/toLogin访问login页面进行登录,如下所示:数据库中admin配置了添加user:add资源授权,admin111配置了修改user:update资源授权。

image.png image.png
image.png
image.png

相关文章

网友评论

      本文标题:SpringBoot整合Shiro权限管理

      本文链接:https://www.haomeiwen.com/subject/qwjlwctx.html