Shiro学习(一)很棒的入门代码

Springboot搭建Shiro

Shiro是一个Apach旗下的Java的安全框架。相比较Spring Security,它比较轻量级,因此在国内的使用率比较高。

代码的编写

为了帮助更好的理解shiro的功能,以及快速上手。但这里仅仅包含了登录拦截和权限验证,不包括角色相关功能。待我更深入的Shiro的学习之后再做更新。
代码的功能:不同用户拥有不同的权限。,角色需要登录后才能进入功能界面。
该代码是Springboot+Mybatis+Shiro的项目,前端使用thymeleaf。

1.目录结构

目录结构

2.pom.xml文件的关键代码的引入

<properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <shiro.version>1.4.0</shiro.version>
    </properties>

    <dependencies>
        <!--导入thymeleaf模板-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--导入web支持:SpringMVC开发支持,Servlet相关程序-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--test测试的支持-->
        <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>
        
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.9</version>
        </dependency>
    </dependencies>

3.application.yml文件的代码

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    #                 本地ip:端口号/数据库名称
    url: jdbc:mysql://127.0.0.7:3306/shirodemo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: root
    #druid数据连接池的参数配置
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      max-active: 30
      initial-size: 5
      min-idle: 5
      max-wait: 60000

  thymeleaf:
    cache: false

mybatis:
  configuration:
    #使用camel规则
    map-underscore-to-camel-case: true
    #打印mybatis日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    use-column-label: true
  #mapper.xml文件映射位置
  mapper-locations: classpath:mapper/*.xml
  #扫描的实体类的位置
  type-aliases-package: cn.study.shirodemo.bean
  

4.用户类的建立

对应User.Class类,与用户表字段对应

public class User implements Serializable {

    private static final long serialVersionUID = -6675401285085356381L;

    private Integer id;
    private String username;
    private String password;
    private String perms;

    public User() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPerms() {
        return perms;
    }

    public void setPerms(String perms) {
        this.perms = perms;
    }
}

5.Dao层

UserMapper.Java文件

public interface UserMapper {

    User getUserByName(String username);

    User getUserById(Integer id);
}

UserMapper.xml文件
这里放在resources的mapper目录下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cn.study.shirodemo.mapper.UserMapper">

    <select id="getUserByName" resultType="cn.study.shirodemo.bean.User">
        SELECT *
        FROM user where username = #{username}
    </select>

    <select id="getUserById" resultType="cn.study.shirodemo.bean.User">
        SELECT *
        FROM user where id = #{id}
    </select>
</mapper>

若提示错误数据库相关根据下面图示修改路径
在这里插入图片描述

6.Service层

UserService

public interface UserService {

    User getUserByName(String username);

    User getUserById(Integer id);
}

UserServiceImpl实现类

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User getUserByName(String username) {
        User user = userMapper.getUserByName(username);
        return user;
    }

    @Override
    public User getUserById(Integer id) {
        User user = userMapper.getUserById(id);
        return user;
    }
}

7.UserController

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/toLogin")
    public String toLogin() {
        return "/login";
    }

    @RequestMapping("/toMian")
    public String toMian() {
        return "/user/main";
    }

    @RequestMapping("/toAdd")
    public String toAdd() {
        return "/user/add";
    }

    @RequestMapping("/toUpdate")
    public String toUpdate() {
        return "/user/update";
    }

    @RequestMapping("/unAuth")
    public String unAuth() {
        return "/unAuth";
    }

//===============================上面是页面跳转功能的代码=================================
    @RequestMapping("/login")
    public String login(String username, String password, Model model) {
        //获取subject
        Subject subject = SecurityUtils.getSubject();
        //封装用户数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        //执行登录操作
        try {
            subject.login(token);
            //登录成功
            return "/user/main";
        } catch (UnknownAccountException e) {
            //账户未知错误
            model.addAttribute("msg", "用户不存在");
            return "/login";
        } catch (IncorrectCredentialsException e) {
            //认证信息不正确错误
            model.addAttribute("msg", "密码不正确");
            return "/login";
        }
    }
}

注意
Subject是import org.apache.shiro.subject.Subject;包下的

启动类
在这里插入图片描述

9.重头戏:配置UserRealm

public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService 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();
        //通常权限表和用户表是分开的,这里为了简洁,使用同一张表
        //添加匹配的字符串
        String perms = user.getPerms();
        String[] strs = perms.split(",");
        for (String s : strs) {
            info.addStringPermission(s);
        }
        return info;
    }

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

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        User user = userService.getUserByName(username);
        if (user == null) {
            return null;//shiro会抛出UnknownAccountException
        }
        //判断密码
        return new SimpleAuthenticationInfo(user, user.getPassword(), "");
    }
}

登录操作,执行的是doGetAuthenticationInfo方法(执行认证逻辑部分)

首先从参数token中获取用户名和密码后,通过mybatis从数据库找出对应的user对象。
若不存在user为null,null作为返回值会触发controller代码中的UnknownAccountException异常。
若不为null,返回new SimpleAuthenticationInfo(user, user.getPassword(), "");

userPrincipal对象,是权限验证时使用的参数。

user.getPassword()是密码,shiro会匹配token中的密码,错误时会抛出IncorrectCredentialsException异常。

参数三这篇文章不做研究。

授权认证,执行的是doGetAuthorizationInfo方法(执行授权逻辑部分)

doGetAuthorizationInfo是执行权限的部分,通过User user = (User) subject.getPrincipal();将认证逻辑中的user参数保存过来。
info.addStringPermission(s)是添加授权逻辑,下面会介绍如何使用。

10.编写ShiroConfig类

@Configuration
public class ShiroConfig {

    /**
     * 创建ShiroFilterFact
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("webSecurityManager") DefaultWebSecurityManager manager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(manager);

        //添加Shiro内置过滤器
        /**
         * 内置过滤器,可以实现权限相关的拦截器
         *  常用的过滤器:
         *      anon:无需认证(登录)可以访问
         *      authc:必须认证才能访问
         *      user:如果使用remeberMe的功能可以直接访问
         *      perms:该资源必须得到资源权限才能访问
         *      role:该资源必须得到角色权限才能访问
         */
        Map<String, String> filterMap = new LinkedHashMap<>();

        filterMap.put("/login", "anon");//无需认证就能访问
        filterMap.put("/toLogin", "anon");//无需认证就能访问
        //注意拦截请求默认是拦截的,需要用anon来放行

        //拦截后,会自动跳转到未授权的页面
        filterMap.put("/toAdd", "perms[user:add]");
        filterMap.put("/toUpdate", "perms[user:update]");

        filterMap.put("/*", "authc");//拦截
        
        //调整成登录页面
        bean.setLoginUrl("/toLogin");
        //设置未授权的页面
        bean.setUnauthorizedUrl("/unAuth");

        bean.setFilterChainDefinitionMap(filterMap);
        return bean;
    }

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

    @Bean(name = "userRealm")
    public UserRealm getRealm() {
        return new UserRealm();
    }
}

defaultWebSecurityManager方法是将我们自定义的UserRealm添加到Shiro过滤器配置中
这里我们深入研究getShiroFilterFactoryBean方法
当方法设置好安全管理器后,需要添加过滤内容
内置过滤器,可以实现权限相关的拦截器
常用的过滤器
anon:无需认证(登录)可以访问
authc:必须认证才能访问
user:如果使用remeberMe的功能可以直接访问
perms:该资源必须得到资源权限才能访问
role:该资源必须得到角色权限才能访问
注意
添加过程中:一定要在最后添加

filterMap.put("/*", "authc");//拦截

否则在该段代码之后添加的过滤内容会无效。
这里还有个小事项:就是添加无需过滤的内容时,一定要将登录页面的所有请求添加全,否则浏览器会提示错误,而客户端并没有任何错误提示。(当时困了我很久 = =!)

filterMap.put("/toAdd", "perms[user:add]");
filterMap.put("/toUpdate", "perms[user:update]");

这部分代码是权限管理时使用的代码。Shiro会去匹配[]内的内容是否一致。
匹配的对象就是UserRealm类中getShiroFilterFactoryBeaninfo.addStringPermission(s);

我这里使用的是下表的方式,再用Split方法循环addStringPermission方法放入。
在这里插入图片描述

11.简单搭建前端页面

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>登录页</title>
</head>
<body>
登录页面
<div>
    <form action="/login" method="post">
        <div>
            <label>用户名:</label>
            <input type="text" id="username" name="username">
        </div>
        <div>
            <label>密码:</label>
            <input type="password" id="password" name="password">
        </div>
        <input type="submit" value="登录">
    </form>
    <div th:text="${msg}" color="red"></div>
</div>
</body>
</html>

unAuth.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
您没有权限
<a href="/toLogin">点击返回到登录页面</a>
</body>
</html>

add.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
添加用户
</body>
</html>

update.html与之类似

main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
<div>
    <a href="/toAdd">添加功能</a>
</div>
<div>
    <a href="/toUpdate">修改功能</a>
</div>
</body>
</html>

大功告成!启动项目体验一下吧!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值