目录
零、资料
一、使用
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、前端
登录页:
主页: