上一篇还没连数据库也没有进行授权,在实现登录验证的基础上再实现授权(某个用户拥有某个资源的访问权限的意思)
首先创建数据库shiro,表:
CREATE TABLE `tb_user` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`pwd` varchar(255) DEFAULT NULL,
`perms` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
总的项目:本项目代码源自狂神,用通用mapper而已
pom:
<?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.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itheima</groupId>
<artifactId>shiro-springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiro-springboot</name>
<description>Demo project for Spring Boot</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.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--thymeleaf模板-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
<!--Shiro 和 spring整合的依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<!--Spring Boot的属性注入需要的-->
<dependency>
<groupId> org.springframework.boot </groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<!--不传递依赖-->
<optional>true</optional>
</dependency>
<!-- 引入 myBatis,这是 MyBatis官方提供的适配 Spring Boot 的,而不是Spring Boot自己的-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!--mybatis通用mapper需要的-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<!--lombok需要的-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<!--整合thymeleaf需要,就是根据你的权限显示对应的可操作按钮-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
ShiroConfig:
package com.itheima.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;
/**
* @author QLBF
* @version 1.0
* @date 2021/5/5 17:02
*/
//声明为配置类
@Configuration
public class ShiroConfig {
//创建 ShiroFilterFactoryBean 3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
/*
添加Shiro内置过滤器,常用的有如下过滤器:
anon: 无需认证就可以访问
authc: 必须认证才可以访问
user: 如果使用了记住我功能就可以直接访问
perms: 拥有某个资源权限才可以访问
role: 拥有某个角色权限才可以访问
*/
//拦截过滤器链
Map<String,String> filterMap=new LinkedHashMap<>();
//授权过滤器,正常情况下没授权的灰跳到未授权页面
//指定/user/add资源资源用户含有user:add权限才能被访问;
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
//未授权的页面,noauth是控制器条到未授权的html的一个方法
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
//设置过滤器,还没登录前要想访问controller的add和update必须通过登录认证才允许访问
//这里user/*可以通配符的 代表访问/user开头的用户都拦截
filterMap.put("/user/*","authc");
//修改到要跳转的login页面(shrio没security会帮你自动生成一个登录的,得自己写);
shiroFilterFactoryBean.setLoginUrl("/toLogin"); //toLogin是controller的一个跳到登录页面的方法
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
//创建 DefaultWebSecurityManager 2
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
//关联Realm
securityManager.setRealm(userRealm);
return securityManager;
}
//创建 realm 对象 1
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
//配置ShiroDialect:方言,用于 thymeleaf 和 shiro 标签配合使用
//整合thymeleaf需要,就是根据你的权限显示对应的可操作按钮
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
UserRealm(得自己实现):
package com.itheima.config;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author QLBF
* @version 1.0
* @date 2021/5/5 17:04
*/
//自定义Realm 必须要的 给ShrioConfig第三步用的
public class UserRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
//执行授权逻辑
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权逻辑");
//给资源进行授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//根据下面认证最后一句第一个参数传过来获得当前对象
Subject subject = SecurityUtils.getSubject();
//转为User对象
User currentUser = (User) subject.getPrincipal();
//把这个用户数据库perms的值作为权限,刚好和ShiroConfig匹配的话就证明有该权限的意思
info.addStringPermission(currentUser.getPerms());
//info.addStringPermission("user:add");
System.out.println("当前用户权限:"+currentUser.getPerms());
return info;
}
//执行认证逻辑
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了=>认证逻辑AuthenticationToken");
/*
//假设数据库的用户名和密码
String name="root";
String password="root";
*/
//1.判断用户名
//把token转为我们认识的,也是从controller那个token来的
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//真实连接数据库
User user = userService.queryUserByName(userToken.getUsername());
Subject subject1 = SecurityUtils.getSubject();
subject1.getSession().setAttribute("loginUser",user);
if (user==null){
//用户名不存在
return null; //shiro底层就会抛出 UnknownAccountException
}
//2. 验证密码,我们可以使用一个AuthenticationInfo实现类 SimpleAuthenticationInfo
// shiro会自动帮我们验证!重点是第二个参数就是要验证的密码!
//第一个参数是传给上面的授权方法获得当前用户的,
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}
}
com.itheima.controller.MyController:
package com.itheima.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.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author QLBF
* @version 1.0
* @date 2021/5/5 16:58
*/
@Controller
public class MyController {
// 加/代表默认跳到这
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg1","hello,Shiro");
return "index";
}
@RequestMapping("/user/add")
public String toAdd(){
return "user/add";
}
@RequestMapping("/user/update")
public String toUpdate(){
return "user/update";
}
//跳到登录登录页面的方法
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
//未授权页面
@RequestMapping("/noauth")
@ResponseBody
public String noAuth(){
return "未经授权不能访问此页面";
}
//登录操作
@RequestMapping("/login")
public String login(String username,String password,Model model){
//使用shiro,编写认证操作
//1. 获取Subject
Subject subject = SecurityUtils.getSubject();
//2. 封装用户的数据,token是根据用户名和密码生成的
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//token.setRememberMe(true); //记住我
//3. 执行登录的方法,只要没有异常就代表登录成功!
try {
subject.login(token); //执行登录,shiro帮我们弄的,很麻烦的,会跳到UserRealm的认证方法认证和授权的
subject.hasRole("登录");//用于触发realm中的授权doGetAuthorizationInfo()方法
//登录成功!返回首页
return "index";
}catch (UnknownAccountException e){
//用户名不存在
model.addAttribute("msg","用户名不存在");
return "login";
}catch (IncorrectCredentialsException e) {
//密码错误
model.addAttribute("msg","密码错误");
return "login";
}
}
}
com.itheima.mapper.UserMapper:
package com.itheima.mapper;
import com.itheima.pojo.User;
import tk.mybatis.mapper.common.Mapper;
/**
* @author QLBF
* @version 1.0
* @date 2021/5/5 20:43
*/
public interface UserMapper extends Mapper<User>{
}
User:
package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* @author QLBF
* @version 1.0
* @date 2021/5/5 20:42
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "tb_user")
public class User {
@Id
private int id;
private String name;
private String pwd;
private String perms;
}
package com.itheima.service;
import com.itheima.pojo.User;
/**
* @author QLBF
* @version 1.0
* @date 2021/5/5 20:46
*/
public interface UserService {
public User queryUserByName(String name);
}
package com.itheima.service.impl;
import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;
/**
* @author QLBF
* @version 1.0
* @date 2021/5/5 20:47
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User queryUserByName(String name) {
Example example=new Example(User.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("name",name);
return userMapper.selectOneByExample(example);
}
}
启动类:
package com.itheima;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.itheima.mapper")
public class ShiroSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(ShiroSpringbootApplication.class, args);
}
}
templates/user/add.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>add</title>
</head>
<body>
<h1>add</h1>
</body>
</html>
templates/user/update.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>update</title>
</head>
<body>
<h1>update</h1>
</body>
</html>
templates/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</title>
</head>
<body>
<h1>首页</h1>
<div th:if="${session.loginUser==null}">
<a th:href="@{/toLogin}">登录</a>
</div>
<p th:text="${msg1}"></p>
<!--/user/add是controller的一个路径-->
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>
</body>
</html>
templates/login.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<p style="color: red" th:text="${msg}"></p>
<!--login是controller使用shiro做登录验证的方法-->
<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>
application.yml:
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/shiro
username: root
password: root
#tomcat端口
server:
port: 8080
#日志记录级别
logging:
level:
com.itheima: debug
org.springframework: info
# mybatis配置
mybatis:
type-aliases-package: com.itheima.pojo
configuration:
# 控制台输出执行sql log-impl:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
启动:
输入xiaohe,123456那么它只有add出现,因为我们在html设置拥有xx权限才给显示:
张三的话就只有updtae出现。
总结:
1.其实shiro比security更加复杂了,但它的重点也只有三个:
Subject:用户(负责拦截请求)
SecurityManger:管理所以用户
Realm:连接数据(负责对哪些用户授权)
2.他们是一层一层调用的,ShiroFilterFactoryBean调用DefaultWebSecurityManager,DefaultWebSecurityManager调用UserRealm(自定义),他们三个实在ShiroConfig进行过滤拦截哪些资源需要登录后才能访问和就算你登录后还需要哪些权限才可以访问 权限的话就要看你是什么角色了。
3.然后你需要自定义一个Realm继承AuthorizingRealm进行写认证和授权的逻辑的。
4.这篇shiro是结合token的,不是jwt
5.重点:前端访问你的资源,若你的资源在ShiroConfig设置登录才能访问的话,那么你得登录(它在登录的controller中有个subject.login(token); shiro帮我们弄的,执行登录,很麻烦的,会跳到UserRealm的认证方法认证去认证),如果改资源还要权限才可以被访问的话,那么它也在subject.login(token)执行的时候跳到UserRealm的授权中心中,把它数据库的值设置为权限,然后返回一个对象给ShiroConfig再进行判断符合权限就给访问。(上面是我口述表达,可能有错)
反正关键就是ShiroConfig和你自定义的Realm
6.在realm中的return new SimpleAuthenticationInfo("", user.getPwd(), “”);
思考?这个Shiro,是怎么帮我们实现密码自动比对的呢?
我们可以去 realm的父类核心:
的父类 翻译过来:获取证书匹配器
中找一个方法
我们去看这个接口 CredentialsMatcher 有很多的实现类,MD5盐值加密
本项目代码来源:狂神
其中授权遇到的问题参考:springboot shiro 不执行授权方法doGetAuthorizationInfo()