SpringBoot整合Shiro权限管理(小白必备)
- 简介
最近在做项目中遇到了权限分配这一块,所以自学了下shiro,分享下自己在使用过程中遇到的问题,解决方法,还有一个demo,废话不多说,我们直接进入正题。
1.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>
<groupId>com.hui</groupId>
<artifactId>springboot-shiro-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-shiro-test</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.0.RELEASE</spring-boot.version>
<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version>
</properties>
<dependencies>
<!-- thymel对shiro的扩展坐标 thymeleaf需要3.0以上 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!--导入配置文件处理器,配置文件进行绑定有提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--shiro和spring整合-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<!-- 日志记录-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--连接池Druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.8</version>
</dependency>
<!-- 模板引擎thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--springboot启动项-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatis整合springboot-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<!-- mysql连接驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 测试-->
<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>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.0.RELEASE</version>
<configuration>
<mainClass>com.hui.SpringbootShiroTestApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2.配置数据库连接:(application.yml)
server:
port: 8080
servlet:
context-path: /shiro
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/myschool?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
# 数据源其他配置 springboot没有这些自动配置,需要引入
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,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
thymeleaf:
prefix: classpath:/templates/
check-template-location: true
suffix: .html
encoding: UTF-8
content-type: text/html
mode: HTML5
cache: false
mybatis:
type-aliases-package: com.hui.pojo
mapper-locations: classpath:mybatis/mapper/*.xml
spring boot中druid连接池没有默认的这些自动装配属性,需要自己手动注入。
编写DruidConfig类:
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druid() {
return new DruidDataSource();
}
}
3.自定义ShiroConfig类:
package com.hui.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
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;
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
/*
anon:无需认证可以访问
authc:必须认证才能访问
user:必须拥有 记住我 功能才能用
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问
*/
//登录拦截
Map<String, String> filterMap = new LinkedHashMap<>();
//用户进入登录界面,无需认证
filterMap.put("/", "anon");
filterMap.put("/login", "anon");
filterMap.put("/tologin", "anon");
//用户进入系统,必须进行登录验证
filterMap.put("/sys/*", "authc");
//角色拦截
filterMap.put("/sys/admin", "authc,roles[admin]");
filterMap.put("/sys/user", "authc,roles[user]");
filterMap.put("/sys/company", "authc,roles[company]");
//权限拦截 设置admin下的所有方法都需要增删改查权限
filterMap.put("/sys/admin/*", "perms[admin:add,admin:update,admin:delete,admin:find]");
//设置user下的add,update,delete需要特定权限
filterMap.put("/sys/user/find", "perms[user:find]");
filterMap.put("/sys/user/delete", "perms[user:delete]");
filterMap.put("/sys/user/add", "perms[user:add]");
filterMap.put("/sys/user/update", "perms[user:update]");
//设置登出
filterMap.put("/logout", "logout");
//设置admin下的某个方法需要某个权限
// filterMap.put("/sys/admin/find", "perms[admin:find]");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//设置登录请求
shiroFilterFactoryBean.setLoginUrl("/login");
//设置401(无权限界面)
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
return shiroFilterFactoryBean;
}
//DefaultWebSecurityManager:2
@Bean("defaultWebSecurityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//关联userRealm
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
//创建 realm对象,需要自定义:1
@Bean("userRealm")
public UserRealm getUserRealm() {
return new UserRealm();
}
/**
* 配置shiro和thymeleaf整合
*
* @return
*/
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
4.自定义UserRealm:
package com.hui.config;
import com.hui.pojo.Account;
import com.hui.pojo.Role;
import com.hui.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.session.mgt.SimpleSession;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.Set;
/**
* 继承AuthorizingRealm
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
/**
* 授权
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了==》》授权doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获取到当前用户
Account account = (Account) principalCollection.getPrimaryPrincipal();
//赋予用户角色
Role role = userService.findRoleByAccountName(account.getUsername());
Set<String> roleSet = new HashSet<String>();
roleSet.add(role.getRoleType());
info.setRoles(roleSet);
//赋予用户权限
Set<String> permSet = new HashSet<String>();
permSet = userService.findAccountPerById(account.getId());
System.out.println("账户权限:" + permSet);
info.setStringPermissions(permSet);
return info;
}
/**
* 认证
*
* @param
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了==》》doGetAuthenticationInfo");
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
//用户名,密码,数据库中取
Account account = userService.findAccountByName(userToken.getUsername());
if (account == null) {
return null;//用户名不存在
}
//获取session
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginUser",account);
//可以加密:md5,md5盐值加密
//密码认证 shiro做 加密密码 这里传入数据库的密码,会自动判断
return new SimpleAuthenticationInfo(account, account.getPassword(), this.getName());
}
}
5.编写Mapper持久层:
package com.hui.mapper;
import com.hui.pojo.Account;
import com.hui.pojo.Role;
import java.util.Set;
public interface UserMapper {
/**
* 登录验证 根据名称查找账号
*
* @param username
* @return
*/
Account findAccountByName(String username);
/**
* 根据名称查找账号角色
*
* @param username
* @return
*/
Role findRoleByAccountName(String username);
/**
* 根据账号编号查找账号权限
*
* @param accountId
* @return set集合
*/
Set<String> findAccountPerById(Integer accountId);
}
6.编写登录的Controller:
package com.hui.controller;
import com.hui.pojo.Account;
import com.hui.pojo.Role;
import com.hui.service.UserService;
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.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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
@Controller
public class LoginController {
@Resource
private UserService userService;
@GetMapping({"/login", "/"})
public String showIndex() {
return "login";
}
@PostMapping(value = {"/tologin"})
public String tologin(Account account, Model model) {
System.out.println("当前登录账号:" + account);
//1.获取当前用户
Subject subject = SecurityUtils.getSubject();
//2.封装用户登录数据
UsernamePasswordToken token = new UsernamePasswordToken(account.getUsername(), account.getPassword());
try {
subject.login(token);//执行登录,没有异常,说明ok
return "redirect:/sys/showview";
} catch (UnknownAccountException ex) {//用户名不存在
model.addAttribute("msg", "用户名错误");
return "login";
} catch (IncorrectCredentialsException ex) {
model.addAttribute("msg", "密码错误");
return "login";
}
}
@RequestMapping("/sys/showview")
public String showView() {
//1.获取当前用户
Subject subject = SecurityUtils.getSubject();
Account account = (Account) subject.getPrincipal();
//根据角色进入不同的界面
Role role = userService.findRoleByAccountName(account.getUsername());
if ("admin".equals(role.getRoleType())) {
return "view/admin";
} else if ("user".equals(role.getRoleType())) {
return "view/user";
} else if ("company".equals(role.getRoleType())) {
return "view/company";
} else {
return "";
}
}
/**
* 无权限错误界面
*
* @return
*/
@RequestMapping("/noauth")
public String noauth() {
return "/error/401";
}
@RequestMapping("/sys/logout")
public String logout() {
//1.获取当前用户
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login";
}
}
7.编写其他用户角色进入的界面:
StudentController:账号角色为admin的权限才可以访问
package com.hui.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/sys/admin")
public class StudentController {
@RequestMapping("/add")
public String addStudent() {
return "/student/add";
}
@RequestMapping("/update")
public String updateStudent() {
return "/student/update";
}
@RequestMapping("/delete")
public String deleteStudent() {
return "/student/delete";
}
@RequestMapping("/find")
public String findStudent() {
return "/student/find";
}
}
UserController:账号权限为user的用户才可以访问
package com.hui.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/sys/user")
public class UserController {
@RequestMapping("/add")
public String addStudent() {
return "/user/add";
}
@RequestMapping("/update")
public String updateStudent() {
return "/user/update";
}
@RequestMapping("/delete")
public String deleteStudent() {
return "/user/delete";
}
@RequestMapping("/find")
public String findStudent() {
return "/user/find";
}
}
8.template下的html界面:
login.html界面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h3 th:text="${msg}"></h3>
登录界面
<form action="tologin" method="post">
用户名:<input type="text" name="username"/></br>
密码:<input type="text" name="password"/></br>
<input type="checkbox" name="rememberMe">记住我?</br>
<input type="submit" value="login"/></br>
</form>
</body>
</html>
9.数据库设计:
account表:账号表
accountPerm表:账号权限表
role表:角色表
总结:
1.shiro和thymeleaf整合时,thymeleaf版本必须为3.0及以上
2.在html中写th或者shiro标签有提示:
xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"