整个项目结构:
总体步骤
1、引入依赖
开发环境:IDEA、mysql8.0、maven、mybatis、shiro、thymeleaf
首先搭建一个springboot项目,接着引入依赖,本项目中所有依赖均在此pom.xml文件中
<?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.6.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.shiro.test</groupId>
<artifactId>shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiro</name>
<description>Shiro project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- thymeleaf模板依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--引入thymeleaf-shiro依赖-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!-- shiro-springboot依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.8.0</version>
</dependency>
<!-- mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 打印日志依赖包-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- 数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<!-- 整合mybatis和springboot-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<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>
</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
</project>
2、新增shiro配置类
与shiro相关的配置类,共有两个,分别是ShiroConfig.java和UserRealm.java
- ShiroConfig.java
package com.shiro.test.shiro.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Description
* @ClassName ShiroConfig
* @Author yuhuofei
* @Date 2021/11/21 12:32
* @Version 1.0
*/
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean(第三步)
@Bean(name = "shiroFilterFactoryBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//关联到DefaultWebSecurityManager,设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro内置过滤器,拦截时有顺序要求!
/**
* anon:无需认证就可以访问
* authc:必须认证才能访问
* user:必须拥有记住我功能才能用
* perms:拥有对某个资源的权限才能访问
* role:拥有某个角色权限才能访问
*/
Map<String,String> filterMap = new LinkedHashMap<>();
//权限拦截,拥有授权字符串user:add,才能访问/user/add
filterMap.put("/user/add","perms[user:add]");
//权限拦截,拥有授权字符串user:update,才能访问/user/update
filterMap.put("/user/update","perms[user:update]");
//拦截/user/目录下所有,必须认证才能访问
//filterMap.put("/user/add","authc");
//filterMap.put("/user/update","authc");
filterMap.put("/user/*","authc");
//设置过滤器的过滤链
bean.setFilterChainDefinitionMap(filterMap);
//拦截后,设置跳转的请求(加/表示绝对路径,不加/表示相对路径)
bean.setLoginUrl("/toLogin");
//未经授权的跳转
bean.setUnauthorizedUrl("/unAuth");
return bean;
}
//DefaultWebSecurityManager(第二步)
@Bean(name = "manager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("realm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRealm,进行管理
securityManager.setRealm(userRealm);
return securityManager;
}
//创建realm对象,需要自定义一个类实现,本文是自定义了UserRealm类(第一步)
@Bean(name = "realm")
public UserRealm getUserRealm() {
return new UserRealm();
}
//整合shiro thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
- UserRealm.java
package com.shiro.test.shiro.config;
import com.shiro.test.shiro.entity.User;
import com.shiro.test.shiro.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @Description 自定义的realm,继承AuthorizingRealm,重写两个方法
* @ClassName UserRealm
* @Author yuhuofei
* @Date 2021/11/21 12:36
* @Version 1.0
*/
@Slf4j
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
//授权方法,用于授权用户访问指定资源的权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("授权方法被执行成功!");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//获取当前登录的这个用户对象
Subject subject = SecurityUtils.getSubject();
User curUser = (User) subject.getPrincipal();
//获取数据库中配置的权限,为当前用户进行授权
simpleAuthorizationInfo.addStringPermission(curUser.getPerms());
log.info("当前用户拥有的权限"+curUser.getPerms());
return simpleAuthorizationInfo;
}
//认证方法,用于登录时,认证该登录用户是否可以登录
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("认证方法被执行成功!");
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
//从数据库中查记录
User user = userService.queryByUserName(userToken.getUsername());
if (user == null) {//无此用户
return null;
}
//此处可以对密码进行MD5盐值加密
//密码认证
return new SimpleAuthenticationInfo(user,user.getPassWord(),"");
}
}
3、 新增html页面
共4个页面,分别是login.html、index.html、add.html、update.html
- login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h1>登录</h1>
<hr>
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">
<p>用户名: <input type="text" name="userName"></p>
<p>密码: <input type="text" name="passWord"></p>
<p><input type="submit"></p>
</form>
</body>
</html>
- index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<hr>
<!--有user:add权限才展示新增菜单-->
<shiro:hasPermission name="user:add">
<a th:href="@{/user/add}">新增</a>
</shiro:hasPermission>
<!--有user:update权限才展示修改菜单-->
<shiro:hasPermission name="user:update">
<a th:href="@{/user/update}">修改</a>
</shiro:hasPermission>
<a th:href="@{/logout}">退出</a>
</body>
</html>
- add.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加用户</title>
</head>
<body>
<h1>add</h1>
</body>
</html>
- update.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>修改用户</title>
</head>
<body>
<h1>update</h1>
</body>
</html>
4、新建数据库表,整合mysql、mybatis、springboot
4.1 新建数据库表
# 新建一个数据库
CREATE DATABASE `shiro_test`;
# 使用数据库
USE `shiro_test`;
# 创建用户表
CREATE TABLE `user`(
`user_id` INT(20) PRIMARY KEY,
`user_name` VARCHAR(30) DEFAULT NULL,
`password` VARCHAR(30) DEFAULT NULL,
`perms` VARCHAR(100) DEFAULT NULL
)ENGINE INNODB CHARACTER SET utf8 COLLATE utf8_bin;
4.2 整合mysql、mybatis、springboot
- 引入依赖
在前面的pom.xml中,已经引入,可以参考前面的pom.xml文件 - 在配置文件application.properties中新增以下内容
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro_test?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=pan
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
mybatis.type-aliases-package=com.shiro.test.shiro.entity
mybatis.mapper-locations=classpath:mapper/*.xml
#开启驼峰字段映射
mybatis.configuration.map-underscore-to-camel-case=true
- 新建实体类,User.java
package com.shiro.test.shiro.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Description 实体类
* @ClassName User
* @Author yuhuofei
* @Date 2021/11/21 18:53
* @Version 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer userId;
private String userName;
private String passWord;
private String perms;
}
- 新建mapper层接口,UserMapper
package com.shiro.test.shiro.mapper;
import com.shiro.test.shiro.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
* @Description
* @InterfaceName UserMapper
* @Author yuhuofei
* @Date 2021/11/21 18:57
* @Version 1.0
*/
@Mapper
public interface UserMapper {
User queryByUserName(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.shiro.test.shiro.mapper.UserMapper">
<resultMap id="userResultMap" type="com.shiro.test.shiro.entity.User">
<result column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<result column="password" property="passWord"/>
<result column="perms" property="perms"/>
</resultMap>
<sql id="Base_Column_List">
user_id,user_name,password,perms
</sql>
<select id="queryByUserName" parameterType="String" resultType="com.shiro.test.shiro.entity.User">
select user_id,user_name,password,perms
from shiro_test.user
where user_name = #{userName}
</select>
</mapper>
- 新建service层接口
package com.shiro.test.shiro.service;
import com.shiro.test.shiro.entity.User;
/**
* @Description
* @InterfaceName UserService
* @Author yuhuofei
* @Date 2021/11/21 19:16
* @Version 1.0
*/
public interface UserService {
User queryByUserName(String userName);
}
- 新建service层接口实现类
package com.shiro.test.shiro.service.impl;
import com.shiro.test.shiro.entity.User;
import com.shiro.test.shiro.mapper.UserMapper;
import com.shiro.test.shiro.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @Description
* @ClassName UserServiceImpl
* @Author yuhuofei
* @Date 2021/11/21 19:17
* @Version 1.0
*/
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public User queryByUserName(String userName) {
User user = userMapper.queryByUserName(userName);
return user;
}
}
- 新建controller类,对外暴露接口
package com.shiro.test.shiro.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @Description
* @ClassName UserController
* @Author yuhuofei
* @Date 2021/11/21 12:02
* @Version 1.0
*/
@Controller
public class UserController {
@RequestMapping("/index")
public String toIndex(Model model) {
model.addAttribute("msg", "hello,Shiro");
return "index";
}
@RequestMapping("/user/add")
public String add() {
return "/user/add";
}
@RequestMapping("/user/update")
public String update() {
return "/user/update";
}
@RequestMapping("/toLogin")
public String toLogin() {
return "login";
}
@RequestMapping({"/","/login"})
public String login(String userName, String passWord, Model model) {
model.addAttribute("msg", "hello,Shiro");
//获取当前登录用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(userName, passWord);
//执行登录
if (userName != null) {
try {
subject.login(token);
return "index";
} catch (UnknownAccountException e) {//用户名不存在
model.addAttribute("msg", "用户名错误");
return "login";
} catch (IncorrectCredentialsException e) {//密码错误
model.addAttribute("msg", "密码错误");
return "login";
}
}
return "login";
}
@RequestMapping("/unAuth")
@ResponseBody
public String unAuth() {
return "未经授权,无法访问!";
}
@RequestMapping("/logout")
public String logout() {
//获取当前登录用户
Subject subject = SecurityUtils.getSubject();
//退出登录
subject.logout();
return "/login";
}
}
5、测试
在数据库的user表中,新增两个用户root、jack,分别赋予root用户新增的权限,jack修改的权限。
5.1 启动项目
5.2 使用root用户登录
登录后,进到首页,由于root用户只有新增的权限,没有修改的权限,所以不能看到修改的菜单
点击新增,进到新增页面
点击首页的退出,回到登录页面
5.3 使用jack用户登录
成功登录到首页,jack用户只有修改权限,所以看不到新增的菜单
点击修改,进到update页面
点击首页的退出,回到登录页面