Shiro-使用笔记

发布时间 2023-07-28 21:15:09作者: 47677777

零、资料

一、使用

1.1、环境搭建

  • SpringBoot2
  • Mybatis3
  • MySQL8.0
  • Shiro

1.1.1、创建数据库

create database db_shiro_demo;
  
drop table if exists tb_user;
create table tb_user(
	id int primary key auto_increment,
	user_name varchar(50),
	pwd varchar(500)	
);
INSERT INTO `db_shiro_demo`.`tb_user` (`id`, `user_name`, `pwd`) VALUES (1, 'zs', '123');
INSERT INTO `db_shiro_demo`.`tb_user` (`id`, `user_name`, `pwd`) VALUES (2, 'ls', '123');



drop table if exists tb_role;
create table tb_role(
	id int primary key auto_increment,
	role_name varchar(50)
);
INSERT INTO `db_shiro_demo`.`tb_role` (`id`, `role_name`) VALUES (1, 'admin');
INSERT INTO `db_shiro_demo`.`tb_role` (`id`, `role_name`) VALUES (2, 'normal');




drop table if exists tb_perm;
create table tb_perm(
	id int primary key auto_increment,
	perm_name varchar(50)
);
INSERT INTO `db_shiro_demo`.`tb_perm` (`id`, `perm_name`) VALUES (1, 'add');
INSERT INTO `db_shiro_demo`.`tb_perm` (`id`, `perm_name`) VALUES (2, 'del');
INSERT INTO `db_shiro_demo`.`tb_perm` (`id`, `perm_name`) VALUES (3, 'update');
INSERT INTO `db_shiro_demo`.`tb_perm` (`id`, `perm_name`) VALUES (4, 'find');



drop table if exists tb_mid_role_perm;
create table tb_mid_role_perm(
	id int primary key auto_increment,
	role_id int,
	perm_id int
);
INSERT INTO `db_shiro_demo`.`tb_mid_role_perm` (`id`, `role_id`, `perm_id`) VALUES (1, 1, 1);
INSERT INTO `db_shiro_demo`.`tb_mid_role_perm` (`id`, `role_id`, `perm_id`) VALUES (2, 1, 2);
INSERT INTO `db_shiro_demo`.`tb_mid_role_perm` (`id`, `role_id`, `perm_id`) VALUES (3, 1, 3);
INSERT INTO `db_shiro_demo`.`tb_mid_role_perm` (`id`, `role_id`, `perm_id`) VALUES (4, 1, 4);
INSERT INTO `db_shiro_demo`.`tb_mid_role_perm` (`id`, `role_id`, `perm_id`) VALUES (5, 2, 4);



drop table if exists tb_mid_user_role;
create table tb_mid_user_role(
	id int primary key auto_increment,
	user_id int,
	role_id int
);
INSERT INTO `db_shiro_demo`.`tb_mid_user_role` (`id`, `user_id`, `role_id`) VALUES (1, 1, 1);
INSERT INTO `db_shiro_demo`.`tb_mid_user_role` (`id`, `user_id`, `role_id`) VALUES (2, 2, 2);


-- 【约定】:用户zs: 增删改查;用户ls:查


1.1.2、导入依赖

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.7.14</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <groupId>com.cyw</groupId>
    <artifactId>shiro-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>shiro-demo</name>
    <description>shiro-demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <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>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter-test</artifactId>
            <version>2.3.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.12.0</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

1.1.3、编写springboot配置文件

server:
  port: 8000
mybatis:
  mapper-locations: classpath:/mapper/*Mapper.xml
  configuration:
    map-underscore-to-camel-case: true
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db_shiro_demo?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
    username: root
    password: root

logging:
  level:
    com.cyw.shirodemo.mapper: info

1.1.4、创建 统一结果集类 + pojo + DTO

统一结果集类 Rst

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Rst<T> {
    private Integer code;
    private Boolean flag;
    private String msg;
    private T data;

    public static <T> Rst<T> ok() {
        return new Rst<>(2000, true, "请求成功", null);
    }

    public static <T> Rst<T> ok(String msg) {
        return new Rst<>(2000, true, msg, null);
    }

    public static <T> Rst<T> ok(String msg, T data) {
        return new Rst<>(2000, true, msg, data);
    }

    public static <T> Rst<T> ok(Integer code, String msg, T data) {
        return new Rst<>(code, true, msg, data);
    }

    public static <T> Rst<T> err() {
        return new Rst<>(4000, false, "请求失败", null);
    }

    public static <T> Rst<T> err(String msg) {
        return new Rst<>(4000, false, msg, null);
    }

    public static <T> Rst<T> err(Integer code, String msg) {
        return new Rst<>(code, false, msg, null);
    }
    public static <T> Rst<T> err(Integer code, String msg,T data) {
        return new Rst<>(code, false, msg, data);
    }

}

用户 User

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String userName;
    private String pwd;
    private List<Role> roleList;
}

角色 Role

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
    private Integer id;
    private String roleName;
    private List<Perm> permList;
} 

权限 Perm

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Perm {
    private Integer id;
    private String permName;
}

UserDto

@AllArgsConstructor
@NoArgsConstructor
@Data
public class UserDto{
    private Integer id;
    private String userName;
    private String pwd;
    private Set<String> roleStrSet;
    private Set<String> permStrSet;
}

1.1.5、mapper接口及xml

UserMapper.java

@Mapper
public interface UserMapper {
    User findUserByName(String userName);
}

UserMapper.xml

<?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="com.cyw.shirodemo.mapper.UserMapper">

    <resultMap id="BaseResultMap" type="com.cyw.shirodemo.pojo.User">
            <id property="id" column="id" jdbcType="INTEGER"/>
            <result property="userName" column="user_name" jdbcType="VARCHAR"/>
            <result property="pwd" column="pwd" jdbcType="VARCHAR"/>
            <collection property="roleList" ofType="com.cyw.shirodemo.pojo.Role">
                <id property="id" column="role_id"/>
                <result property="roleName" column="role_name"/>
            </collection>
    </resultMap>

    <sql id="Base_Column_List">
        tb_user.id,
        tb_user.user_name,
        tb_user.pwd
    </sql>

    <select id="findUserByName" resultMap="BaseResultMap">
        select <include refid="Base_Column_List"></include>,tb_mid_user_role.role_id,tb_role.role_name
            from tb_user,tb_role,tb_mid_user_role
            where
                tb_user.id = tb_mid_user_role.user_id
                and tb_mid_user_role.role_id = tb_role.id
                and tb_user.user_name=#{userName}
    </select>

</mapper>

PermMapper.java

@Mapper
public interface PermMapper {
    List<Perm> findPermListByRoleName(String roleName);
}

PermMapper.xml

<?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="com.cyw.shirodemo.mapper.PermMapper">

    <resultMap id="BaseResultMap" type="com.cyw.shirodemo.pojo.Perm">
            <id property="id" column="id" jdbcType="INTEGER"/>
            <result property="permName" column="perm_name" jdbcType="VARCHAR"/>
    </resultMap>

    <sql id="Base_Column_List">
        tb_perm.id,tb_perm.perm_name
    </sql>

    <select id="findPermListByRoleName" resultType="com.cyw.shirodemo.pojo.Perm">
        select tb_perm.*
        from tb_role,tb_mid_role_perm,tb_perm
        where
            tb_role.id = tb_mid_role_perm.role_id
            and tb_mid_role_perm.perm_id = tb_perm.id
            and tb_role.role_name = #{roleName}
    </select> 

</mapper>

1.1.6、service接口及实现类

UserService:

public interface UserService {
    User findUserByName(String userName);
    UserDto findUserWithRoleAndPerm(String userName);
}

UserServiceImpl:


@AllArgsConstructor
@Transactional
@Service("userService")
public class UserServiceImpl implements UserService {
    private UserMapper userMapper;
    private PermMapper permMapper;

    /**
     * 按用户名查询用户信息
     * @param userName
     * @return
     */
    @Override
    public User findUserByName(String userName) {
        return userMapper.findUserByName(userName);
    }
    
    /**
     * 按用户名查询带有权限的用户信息
     * @param principal
     * @return
     */
    @Override
    public UserDto findUserWithRoleAndPerm(String principal) {

        User user = userMapper.findUserByName(principal);
        UserDto userDto = new UserDto();
        BeanUtils.copyProperties(user,userDto);

        Set<String> roleSet = new HashSet<>();
        Set<String> permSet = new HashSet<>();
        user.getRoleList()
                .forEach(role -> {
                    roleSet.add(role.getRoleName());

                    List<Perm> permListByRoleName = permMapper.findPermListByRoleName(role.getRoleName());
                    Set<String> permSetTmp = permListByRoleName.stream()
                            .map(Perm::getPermName).collect(Collectors.toSet());
                    permSet.addAll(permSetTmp);
                });
        userDto.setRoleStrSet(roleSet);
        userDto.setPermStrSet(permSet);
        return userDto;
    }

}

1.1.7、shiro操作数据库的类

MyRealm.java

@Component
@AllArgsConstructor
public class MyRealm extends AuthorizingRealm {

    private UserService userService;


    /**
     * 认证(登录)的处理
     *
     * @param token the authentication token containing the user's principal and credentials.
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        
        // 来自浏览器的用户名和密码
        String principal = (String) token.getPrincipal();
        
        // 按用户名从数据中查询用户信息
        User user = userService.findUserByName(principal);

        if (user == null) {
            throw new UnknownAccountException("用户不存在");
        }

        // 返回数据库中能正确登录的用户信息给shiro
        return new SimpleAuthenticationInfo(principal, user.getPwd(), "myRealm");
    }


    /**
     * 授权的处理
     *
     * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        
        // 浏览器传过来的用户名
        String principal = (String) principals.getPrimaryPrincipal();

        // 从数据库中根据浏览器传过来的用户名查询带有角色和权限的用户信息
        UserDto userDto = userService.findUserWithRoleAndPerm(principal);
        
        // 将带有权限的用户信息封装为shiro能够识别的用户信息对象AuthorizationInfo
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(userDto.getRoleStrSet());
        simpleAuthorizationInfo.setStringPermissions(userDto.getPermStrSet());

        return simpleAuthorizationInfo;
    }

}

1.1.8、shiro的配置类

ShiroConfig.java

@Configuration
public class ShiroConfig {

    @Bean
    public DefaultSecurityManager defaultSecurityManager(MyRealm myRealm){
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(myRealm);
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        return defaultSecurityManager;
    }

}

1.1.9、统一异常处理

GlobalExceptionHandler.java:

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

    @ExceptionHandler(IncorrectCredentialsException.class)
    public Rst<Void> err() {
        return Rst.err("用户名或密码错误");
    }

    @ExceptionHandler(Exception.class)
    public Rst<Void> err(Exception exception) {
        return Rst.err(exception.getMessage());
    }
}

1.1.10、controller

UserController.java:

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;  

@AllArgsConstructor
@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("/login")
    public Rst<String> login(User user) throws IncorrectCredentialsException{
        System.out.println("=== 正在登陆。。。");
        
        // 将用户信息转化为shiro能识别的用户信息UsernamePasswordToken 
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPwd());  
         
        // 从shiro提供的容器工具类SecurityUtils获取 Subject 对象,通过 Subject 对象可以执行shiro封装过的登录方法
        Subject subject = SecurityUtils.getSubject();
        subject.login(token);

        return Rst.ok("登录成功",token.getUsername());
    }

    @GetMapping("/logout")
    public Rst<Void> logout() {
        System.out.println("=== 正在注销。。。");

        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return Rst.ok("注销成功");
    }

}

1.1.11、前端

登录页:

主页: