SpringBoot整合Shiro完整案例代码

SpringBoot整合Shiro完整案例代码

1.Shiro核心API

Subject 用户主体
SecurityManager 安全管理器
Realm shiro连接数据的桥梁

2.项目结构和数据库结构

在这里插入图片描述
在这里插入图片描述

3.依赖导入

        <!--web启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--thymeleaf-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--thymeleaf对shiro扩展,用于html中使用shiro标签-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.39</version>
        </dependency>
        <!--druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
        <!--myBatis-pulus-generator-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.30</version>
        </dependency>

4.使用generator代码生成器

CodeGenerator

package com.example.generator;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.Scanner;

public class CodeGenerator {
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        /**
         * 这里需要设定一下保存的地址是本项目下的/src/main/java
         */
        gc.setOutputDir(projectPath + "/shiro/src/main/java");
        gc.setAuthor("XYD");
        gc.setOpen(false);
         gc.setSwagger2(true); //实体属性 Swagger2 注解
        mpg.setGlobalConfig(gc);

        // 数据源配置
        /**
         * 设置数据库名称和数据库账户密码
         */
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/temporary?useUnicode=true&useSSL=false&characterEncoding=utf8& serverTimezone=UTC");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("12345");
        mpg.setDataSource(dsc);

        // 包配置
        /**
         * 设置生成文件保存地址,模块名为命令窗口输入的模块名
         */
        PackageConfig pc = new PackageConfig();
        pc.setModuleName(scanner("模块名"));
        //自定义相对于java目录的地址
        pc.setParent("com.baomidou.ant");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();

        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);

        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);

        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix(pc.getModuleName() + "_");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }
}

修改里面的数据库连接配置和文件生成存放地址

5.生成的实体类、mapper对象、service

User

@Data
@EqualsAndHashCode(callSuper = false)

public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    private String userName;

    private String password;

    private String perms;


}

UserMapper

public interface UserMapper extends BaseMapper<User> {

}

IUserService

public interface IUserService extends IService<User> {

}

UserServiceImpl

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

}

5. application配置

spring.thymeleaf.cache=false

#数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/temporary?useUnicode=true&useSSL=false&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=12345
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

6.shiro配置类

ShiroConfig

@Configuration
public class ShiroConfig {
/**
 * 创建三个Bean放入容器中
 * 1. ShiroFilterFactoryBean
 *
 * 2.DefaultWebSecurityManager
 *
 * 3.创建MyRealm
 */

    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //添加shiro内置过滤器
        /**
         * 常用过滤器
         *  anon 无需认证
         *  authc 必须认证后才能访问
         *  user 如果使用了rememberMe功能可以直接访问
         *  perms 该资源必须得到资源权限才能访问
         *  role 该资源必须得到角色权限才能访问
         */

        Map<String, String> filterMap = new LinkedHashMap<>();
        //认证拦截
        filterMap.put("/user/add","authc");
        filterMap.put("/user/update","authc");
        //无需认证
        filterMap.put("/test","anon");
        //设置资源授权,需要查看用户是否有对应的权限,如果没有自动跳转到未授权的提示页面,授权的判断是在realm中进行判断
        filterMap.put("/user/add","perms[user:add]"); //设置资源授权的字符是user:add
        filterMap.put("/user/update","perms[user:update]"); //设置资源授权的字符是user:update

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);


        //设置拦截后跳转的登录页面,默认跳转到login.jsp
        shiroFilterFactoryBean.setLoginUrl("/login");
        //设置未授权提示页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");

        return shiroFilterFactoryBean;

    }


    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getSecurityManager(@Qualifier("myRealm") MyRealm myRealm){

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联realm
        securityManager.setRealm(myRealm);

        return securityManager;
    }



    @Bean(name = "myRealm")
    public MyRealm getRealm(){
        return new MyRealm();
    }

    /**
     * 配置ShiroDialect 用于thymeleaf 和shiro进行配合使用,可以在页面中使用shiro:标签
     * @return
     */
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }
}

7.自定义Realm类

继承AuthorizingRealm
MyRealm

public class MyRealm extends AuthorizingRealm {
    //进行数据库交互
    @Autowired
    private IUserService userService;

    /**
     * 执行授权逻辑
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("授权管理");
        //对资源进行授权
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //先获取用户登录传递过来的输入,主要是通过下面的重写方法的返回值进行获取
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();

        //通过数据库查看用户的权限字符
        User byId = userService.getById(user.getId());
        info.addStringPermission(byId.getPerms());

        return info;
    }

    /**
     * 执行认证逻辑
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证逻辑");
        /**
         * 判断的是shiro的login认证操作,可以自定义或者链接数据库进行查询
         */

        //获取用户输入的username和password
        //请求传递过来的token中包含了所需信息
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;


        QueryWrapper<User> wrapper = new QueryWrapper();
        wrapper.eq("user_name",token.getUsername());
        User one = userService.getOne(wrapper);


        System.out.println(one);
        //判断用户名
        if (one==null){
            //用户名不存在
            return null;
        }
        //判断密码,如果密码正确,将拦截结果交给上面的方法进行资源判断
        return new SimpleAuthenticationInfo(one,one.getPassword(),"");
    }
}

8. html页面开发

test.html

<!DOCTYPE html>
<html lang="en" xmlns:shiro="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>test</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>

add.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>

<h1>用户新增</h1>

</body>
</html>

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
<h1>登录界面</h1>
<h2 th:text="${msg}" style="color: red"></h2>
<form method="post" rel="stylesheet" action="/dologin">
    <input type="text" name="name">
    <br>
    <input type="password" name="password">
    <br>
    <input type="submit" value="登录">
</form>
</body>
</html>

unauth.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>

<h1>未授权</h1>

</body>
</html>

update.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>

<h1>用户修改</h1>

</body>
</html>

9.controller开发

TestController

@Controller
public class TestController {
    //初始界面
    @GetMapping("/test")
    public String test(){
        return "test";
    }
    //login界面
    @GetMapping("/login")
    public String login(){
        return "login";
    }
    //dologin
    @PostMapping("dologin")
    public String dologin(String name,String password,Model model) {
        /**
         * 使用shiro进行认证操作
         *
         */
        // 获取subject
        Subject subject = SecurityUtils.getSubject();

        //封装用户数据
        UsernamePasswordToken token = new UsernamePasswordToken(name, password);
        //执行登录方法

        try {
            //表示认证成功
            subject.login(token);
            return "/test";
        } catch (UnknownAccountException e) {
            //表示认证失败,用户不存在
            model.addAttribute("msg", "用户名不存在");
            return "/login";

        } catch (IncorrectCredentialsException e) {
            //表示认证失败,密码错误
            model.addAttribute("msg", "密码错误");
            return "/login";
        }
    }
    //未授权页面
    @GetMapping("/unauth")
    public String unauth(){
        return "unauth";
    }
}

UserController

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

    @GetMapping("/add")
    public String  userAdd(){
        return "add";
    }
    @GetMapping("/update")
    public String  userUpdate(){
        return "update";
    }
}

10.演示结果

思路是:对于test页面不设置拦截,但是利用shiro标签的权限判断,没有权限就查看不到对应的标签列
初始直接查看test页面,不显示任何内容
在这里插入图片描述

访问login
在这里插入图片描述
输入用户名和密码,动态查询数据库进行shiro判断
在这里插入图片描述
在这里插入图片描述

成功登陆后,根据perms权限对应显示特定的标签在网页上
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

11.总结

1.用户请求到达controller

  1. 用token封装用户信息到subject的login方法;
  2. 传递token到Realm中进行判断,用try/catch进行认证结果的判断,分别进行处理;

2.到达自定义的Realm

  1. 通过token获取用户的输入信息,进行认证,判断用户是否存在和密码是否正确;
  2. 如果用户密码都正确,进行授权管理,通过subject获取用户信息,,通过数据库查找用户的对应权限,进行权限返回

3.到达shiro配置类

  1. 配置导入自己的Realm,注入到容器中
  2. 配置DefaultWebSecurityManager,关联Realm
  3. 配置ShiroFilterFactoryBean ,关联DefaultWebSecurityManager
  4. 在ShiroFilterFactoryBean 中进行不同页面请求的拦截设置,设置拦截后未通过验证的登录请求地址,默认的是login.jsp,设置虽然通过验证,但是没有授权的提示页面请求
  5. 最后配置thymeleaf对shiro的支持,引入依赖,配置ShiroDialect类,最后在页面中使用shiro标签进行页面标签的显示控制

猜你喜欢

转载自blog.csdn.net/Guesshat/article/details/109585273
今日推荐