目录
1.创建项目
1.1、导入相关依赖
thymeleaf模板
<!-- thymeleaf模板-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
<dependencies>
<!--
shiro三大对象:
Subject :用户
SecurityManager :管理所有用户
Realm :连接数据
-->
<!-- 整合shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<!-- web-->
<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>
</dependencies>
1.2、创建结构目录(如下图)
1.3、静态页面设置
注意添加thymeleaf的域名空间:
xmlns:xmls="http://www.w3.org/1999/xhtml" xmls:th="http://www.thymeleaf.org"
index.html
<!DOCTYPE html>
<html lang="en" xmls:th="http://www.thymeleaf.org" xmlns:xmls="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<hr>
<a th:href="@{/user/add}">添加</a>
<hr>
<a th:href="@{/user/update}">修改</a>
<hr>
<a th:href="@{/user/delete}">删除</a>
</body>
</html>
页面展示:(并不是很beautiful)
add.html
<!DOCTYPE html>
<html lang="en" xmlns:xmls="http://www.w3.org/1999/xhtml" xmls:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>添加</h1>
</body>
</html>
页面展示:
update.html
<!DOCTYPE html>
<html lang="en" xmlns:xmls="http://www.w3.org/1999/xhtml" xmls:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>修改</h1>
</body>
</html>
页面展示:
delete.html
<!DOCTYPE html>
<html lang="en" xmls:th="http://www.thymeleaf.org" xmlns:xmls="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>删除</h1>
</body>
</html>
页面展示:
1.4、Controller层
通过Controller来进行各个页面之间的跳转
package com.jun.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MyController {
@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("user/delete")
public String delete(){
return "user/delete";
}
}
1.5、Config
Shiro三大对象:
原文链接:https://blog.csdn.net/qq_41430393/article/details/87730198
1.Subject(主体)
应用代码的直接交互对象就是Subject,也就是说Shiro对外的核心API就是Subject,Subject代表了当前“用户”,这个用户不是指具体的某一个人,可以说与当前应用交互的任何东西都是Subject,与Subject的所有交互都会委托给SecurityManager来执行,可以理解为Subject只是一个充当门面的,真正的幕后老大是SecurityManager,SecurityManager才是实际的执行者。
2.SecurityManager(安全管理器)
所有与安全有关的操作都会与SecurityManager进行交互,并且SecurityManager管理着所有的Subject,可以看出它才是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMvc中的dispatcherServlet(前端控制器)的角色。
3.Realm
Shiro从Realm获取安全数据(用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法,也需要从Realm获取用户的角色\权限来判断用户是否能进行一系列操作。可以把Realm看作DataSource数据源.
首先文明需要自定义一个Raelm
该类需要继承 extends AuthorizingRealm 表明该类是一个自定义的Realm
重写两个方法:
doGetAuthorizationInfo //授权
doGetAuthenticationInfo //认证
具体代码如下:
package com.jun.config;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
//自定义的Realm extends AuthorizingRealm
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权==>doGetAuthorizationInfo");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证==>doGetAuthenticationInfo");
return null;
}
}
创建一个 ShiroConfig类,在此类中分别按顺序实现一下三种方法,并必须放到Bean中
1、创建 realm对象,需要自定义 ,调用之前创建的Realm
//创建 realm对象,需要自定义 1
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
2、DefaultWebSecurityManager 2
注意:在这个方法中
securityManager.setRealm(userRealm);
userRealm并不能够直接调用userRealm方法中的对象,我们要通过
(@Qualifier("userRealm") UserRealm userRealm)
@Qualifier这个注解来将对象作为实参,实现对象的调用,
//DefaultWebSecurityManager 2
@Bean(name="securityManager")
public DefaultWebSecurityManager dafaultWebSecurityMansger(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联Realm
securityManager.setRealm(userRealm);
return securityManager;
}
3、ShiroFilterFactoryBean 3
//ShiroFilterFactoryBean 3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityMansger){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityMansger);
return bean;
}
附:
2.Shiro实现相关功能
2.1、Shiro实现登录拦截
Shiro的内置过滤器:
anon: 无须认证就可以访问 authc: 必须认证才能访问 user: 必须拥有 记住我 才能访问 perms: 必须拥有某个资源的权限才能访问 role: 拥有某个角色权限才能访问
- 在ShiroConfig的 ShiroFilterFactoryBean 方法中写入Shiro的内置过滤器
Map<String, String> filterMap = new LinkedHashMap<>();
// filterMap.put("/user/add","authc");
// filterMap.put("/user/update","authc");
// filterMap.put("/user/delete","authc");
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
- 创建一个登录界面login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录</h1>
<hr>
<form action="">
<p> 用户名: <input type="text" name="username"></p>
<p> 密码: <input type="text" name="password"></p>
<p><input type="submit"></p>
</form>
</body>
</html>
- 在MyController这个类中添加登陆页面的Mapping
@RequestMapping("/toLogin") public String toLogin(){ return "login"; }
- 将登录页面添加到Bean中
bean.setLoginUrl("/toLogin");
2.2、Shiro实现用户认证
- 在MyController中添加登录方法
@RequestMapping("/login")
public String login(String username ,String password,Model model) {
//通过Model给前端传递一个值
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//验证用户名和密码
try{
subject.login(token);//执行登录的方法,如果没有异常就说明OK了
return "index";
}catch (UnknownAccountException e){//用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
}catch(IncorrectCredentialsException e){//密码错误
model.addAttribute("msg","密码错误");
return "login";
}
- 前端接收后端传递的Model值
<p th:text="${msg}" style="color: red"></p>
- 在UserRealm类中模拟数据,并实现认证
//用户名,密码 数据库中取
String username = "root";
String password = "123456";
//将authenticationToken 强转成我们所需要的 UsernamePasswordToken 类型
UsernamePasswordToken usertoken = (UsernamePasswordToken) authenticationToken;
//用户名认证
if(!usertoken.getUsername().equals(username)){
return null;//抛出一个异常 UnknownAccountException
}
//密码认证,Shiro做~
return new SimpleAuthenticationInfo("",password,"");
2.3、Shiro整合Mybatis
- 2.3.1、导入需要的依赖
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<!-- 引入Mybatis,这是-,Mybatis官方提供的适配Springboot的 而不是Spring boot 自己的 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<!-- log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
- 创建并编写yaml配置文件
spring:
datasource:
username: stu
password: 123456
#假如时区报错了,就在url中添加时区 serverTimezone=UTC
url: jdbc:mysql://(端口号)/stu?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.jdbc.Driver
# 设置数据源为Druid
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
- 连接数据库
编写pojo实体类
package com.jun.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private int id;
private String name;
private String pwd;
}
- 编写Mapper
package com.jun.mapper;
import com.jun.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Repository
@Mapper
public interface UserMapper {
public User queryUserByName(String name);
}
配置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="com.jun.mapper.UserMapper">
<select id="queryUserByName" parameterType="String" resultType="User">
select * from stu.student where name = #{name }
</select>
</mapper>
- 编写service层
接口:
package com.jun.service;
import com.jun.pojo.User;
public interface UserService {
public User queryUserByName(String name);
}
实现类:
package com.jun.service;
import com.jun.mapper.UserMapper;
import com.jun.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService{
@Autowired
UserMapper userMapper;
@Override
public User queryUserByName(String name) {
return userMapper.queryUserByName(name);
}
}
- 将之前模拟的数据,改成真实的数据库数据(在自定义的Realm中)
// //用户名,密码 数据库中取
// String username = "root";
// String password = "123456";
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//连接真实的数据库
User user = userService.queryUserByName(userToken.getUsername());
if(user == null){//如果没有这个人
return null;//UnknownAccountException
}
//密码认证,Shiro做~
return new SimpleAuthenticationInfo("",user.getPwd(),"");
2.4、Shiro请求授权实现
- 在数据库中定义用户的权限(perms)
- 设置未授权页面
直接通过Controller实现
@GetMapping("/unauth")
@ResponseBody
public String unauthorized(){
return "未经授权无法访问此页面";
}
- 定义需要授权的页面
//授权,正常情况是没有授权会跳到未授权页面
filterMap.put("/user/add" , "perms[user:add]");
filterMap.put("/user/update" , "perms[user:update]");
filterMap.put("/user/delete" , "perms[user:delete]");
为授权就跳转到未授权页面
//未授权页面
bean.setUnauthorizedUrl("/unauth");
- Realm授权
//SimpleAuthorizationInfo
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// info.addStringPermission("user:add");
// info.addStringPermission("user:update");
// info.addStringPermission("user:delete");
//拿到当前登陆的对象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();//拿到User对象
//设置当前用户的权限
info.addStringPermission(currentUser.getPerms());
return info;
2.5、Shiro整合Thymeleaf
- 导入相关依赖
<!-- shiro-Thymeleaf整合-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
- 整合ShiroDialect
在ShiroController中
//整合ShiroDialect:用来整个 Shiro 和Thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
- 设置session
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginUser",user);
前端首页:
<!DOCTYPE html>
<html lang="en" xmls:th="http://www.thymeleaf.org" xmlns:xmls="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录</a>
</div>
<hr>
<dev shiro:hasPermission="user:add">
<a th:href="@{/user/add}">添加</a>
</dev>
<hr>
<dev shiro:hasPermission="user:update">
<a th:href="@{/user/update}">修改</a>
</dev>
<hr>
<dev shiro:hasPermission="user:delete">
<a th:href="@{/user/delete}">删除</a>
</dev>
</body>
</html>