这里写自定义目录标题
1、权限的5张表
一个管理系统,不同身份的人登录后,操作的权限(访问的功能)是不同的
Shiro SpringSecurity 都是解决授权(判断权限)、认证(登录)的框架
权限:能访问的功能。
通常设计一张表,用于存储系统中的所有功能
权限表:Permission
【注意】如果直接为每个用户去分配具体的权限,但是比较繁琐,需要做重复性的分配
角色:是具有相同权限的一个称呼
角色表:role
【问题】如何给每个角色分配具体的权限?
角色权限对应表:每个角色都对应哪些权限。
role_permission
用户表:用户注册的基本信息
用户角色对应表:把每个用户对应的角色进行关联
查询每个用户对应的角色以及权限信息
2、搭建shiro环境
1、新建maven项目
2、添加jar依赖
<packaging>war</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.6.0</version>
</dependency>
</dependencies>
3、创建启动类
package com.qf.shiro2205;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.qf.shiro2205.dao") //让mybatis扫描此包,扫描到所有接口,mybatis就生成接口的代理类,并创建代理对象,放于bean容器中
public class ShiroApplication {
public static void main(String[] args) {
SpringApplication.run(ShiroApplication.class,args);
}
}
4、创建application.yml配置文件
配置连接池、mybatis的相关信息
server:
port: 8081
spring:
application:
name: Shiro2205
datasource: # 配置连接池信息
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/java2205
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
druid:
initial-size: 10 #连接池中连接的核心连接数量
mybatis:
type-aliases-package: com.qf.shiro2205.pojo
mapper-locations: mybatis/*.xml
configuration:
use-generated-keys: true
map-underscore-to-camel-case: true
5、实体类的生成
6、编写登录的dao
public interface UserDao {
User login(String userId);
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.shiro2205.dao.UserDao">
<select id="login" parameterType="string" resultType="User">
select id,userId,pwd,realName,phone
from `user`
where userId=#{userId}
</select>
</mapper>
7、编写根据用户Id查询角色的dao
public interface RoleDao {
List<Role> findRolesByUserId(String userId);
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.shiro2205.dao.RoleDao">
<select id="findRolesByUserId" parameterType="string" resultType="Role">
SELECT r.roleId,r.roleName
from `user` u
INNER JOIN user_role ur on u.userId=ur.userId
INNER JOIN role r on ur.roleId=r.roleId
where u.userId=#{userId}
</select>
</mapper>
8、编写根据角色id查询对应的权限
public interface PermissionDao {
List<Permission> findPermissionsByRoleId(int roleId);
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.shiro2205.dao.PermissionDao">
<select id="findPermissionsByRoleId" parameterType="int" resultType="Permission">
SELECT p.permissionId,p.permissionName,p.permissionUrl
from role_permission rp,permission p
where rp.permissionId=p.permissionId
AND
rp.roleId=#{roleId}
</select>
</mapper>
3、shiro的体系结构
由三部分组成
1、Realm: 与数据库的关联层,负责登录的数据库查询及角色、权限的查询调用
2、SecurityManager 安全管理器 核心
1、Realm: 与数据库的关联层,负责登录的数据库查询及角色、权限的查询调用
2、SecurityManager 安全管理器 核心
1、认证:判断密码是否正确
2、授权:存储当前登录用户的角色、权限信息
3、鉴权:当当前用户进行网络请求时,拦截该请求,判断是否有该请求的权限
如果有:放行
如果没有:则返回,抛出无权限的异常
4、session的管理功能
5、记住我,下次自动登录
6、密码加密的功能
7、登出功能
8、以另一种身份登录的功能
3、Subject 登录功能
4、自定义Realm
通过Realm做两件事情:
1、认证:通过调用登录方法,把用户的账号及密码信息返回给SecurityManager
2、授权:通过调用 查询角色 、查询权限方法,把用户的角色、权限存储给SecurityManager
package com.qf.SpringBootApplication.realms;
import com.qf.SpringBootApplication.dao.PermissionDao;
import com.qf.SpringBootApplication.dao.RoleDao;
import com.qf.SpringBootApplication.dao.UserDao;
import com.qf.SpringBootApplication.pojo.Permission;
import com.qf.SpringBootApplication.pojo.Role;
import com.qf.SpringBootApplication.pojo.User;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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.springframework.beans.factory.annotation.Autowired;
import java.util.List;
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
@Autowired
private PermissionDao permissionDao;
@Override
// 授权:查询登录账号的角色,权限信息
// 再认证方法认证通过后(账号,密码都正确的时候)才调用此授权方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 1.通过形式参数principalCollection获取账号信息
String userId = principalCollection.getPrimaryPrincipal().toString();
// 2.创建一个SimpleAuthorizationInfo对象用来存储该用户的角色,权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 3.根据账号查询对应的角色信息
List<Role> roles = roleDao.findRolesByUserId(userId);
// 4.遍历角色集合,在便利过程中赋值权限,同时查询每一个角色的权限信息
for (Role role : roles) {
// 把当前角色赋值给simpleAuthorizationInfo
simpleAuthorizationInfo.addRole(role.getRoleName());
// 根据角色信息查询权限信息
List<Permission> permissions = permissionDao.findPermissionsByRoleId(role.getRoleId());
// 遍历权限集合,把每一个权限赋值给simpleAuthorizationInfo对象
for (Permission permission : permissions) {
// 把权限赋值给simpleAuthorizationInfo对象
simpleAuthorizationInfo.addStringPermission(permission.getPermissionUrl());
}
}
// 经过二层循环,已经把角色,权限赋值给了simpleAuthorizationInfo
// 把simpleAuthorizationInfo对象返回给
return simpleAuthorizationInfo;
}
@Override
// 认证:登录
// SecurityManager先调用此认证方法
// 参数authenticationToken:里面封装着登录用户的账号
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 1.先通过参数authenticationToken获取账号信息
Object userId = authenticationToken.getPrincipal();
// 2.判断账号信息是否为空
if (userId == null) {
// 把null值返回给SecurityManager,然后SecurityManager就以报异常的方式提示用户,账号或密码是空的
return null;
}
// 3.调用dao的login方法查询用户信息
User user = userDao.login(String.valueOf(userId));
// 声明对象:用于存储登录用户的密码信息等
SimpleAuthenticationInfo simpleAuthenticationInfo = null;
// 4.判断查询的账户是否存在
if (user == null) {
// 说明登录账号是错误的
return null;
} else {
//把查询到的密码封装后返回给securityManager即可
//在账号正确的情况下创建simpleAuthenticationInfo对象,用于封装查询到的密码信息
//参数1:登录的账号
//参数2:从数据库根据账号查询到的密码
//参数3:当前realm的名字
simpleAuthenticationInfo =
new SimpleAuthenticationInfo(userId, user.getPwd(), getName());
}
//return给SecurityManager对象,自动判断密码是否正确
//如果正确,继而调用上面的授权方法
//如果错误,以报异常的方式返回给登录的调用处
return simpleAuthenticationInfo;
}
}
创建config包,建一个配置类
package com.qf.SpringBootApplication.config;
import com.qf.SpringBootApplication.realms.MyRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration //当前类就是一个配置类 相当于spring的配置文件
public class ShiroConfig {
@Bean
@ConditionalOnMissingBean //如果此方法需要传参,到bean工厂中寻找参数类型的对象,进行传参
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
//创建一个代理对象,去控制shiro
DefaultAdvisorAutoProxyCreator
defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
//设置此代理对象理解生效
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
//创建自定义的realm对象
@Bean
public MyRealm createMyRealm(){
return new MyRealm();
}
//创建SecurityManager对象
@Bean
public SecurityManager createSecurityManager(){
DefaultWebSecurityManager securityManager=
new DefaultWebSecurityManager();
//关联自定义Realm
securityManager.setRealm(createMyRealm());
return securityManager;
}
//配置shiro的拦截规则
//拦截所有网络请求
@Bean
@ConditionalOnMissingBean
public ShiroFilterFactoryBean createShiroFilterFactoryBean(SecurityManager securityManager){
//把过滤器拦截到的请求,转移到参数securityManager中
//创建过滤器
ShiroFilterFactoryBean shiroFilterFactoryBean=
new ShiroFilterFactoryBean();
//设置此过滤器shiroFilterFactoryBean把拦截的请求转移到securityManager中
shiroFilterFactoryBean.setSecurityManager(securityManager);
//声明一个map集合,存放拦截、放行规则等
Map<String,String> map=new HashMap<String, String>();
//设置登出功能
//key是请求的路径 value是关键字
//配置登出
map.put("/logout","logout"); //logout securitymanager 就清空session,跳转到登录页面
//配置可以匿名访问的请求:无需登录就可以访问的
map.put("/user/register","anon"); //anon 代表可以匿名访问
map.put("/user/login","anon");
//企业的请求都需要认证(登录)后才能访问
map.put("/**","authc");//authc 需要认证、鉴权才能访问
//把集合里配置的内容赋值给过滤器
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
//配置登录页面的名字
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
//配置主页的名字
shiroFilterFactoryBean.setSuccessUrl("/index");
//设置当权限不足时,报异常,跳转的页面
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
return shiroFilterFactoryBean;
}
//接收Realm中授权方法返回的角色、权限,赋值给SecurityManager
@Bean
@ConditionalOnMissingBean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor
authorizationAttributeSourceAdvisor=
new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
添加视图解析器
server:
port: 8081 # 端口号
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource # Druid连接池
url: jdbc:mysql://localhost:3306/db2205 # 数据库连接地址
username: root # mysql账号
password: admin # mysql 密码
driver-class-name: com.mysql.jdbc.Driver #驱动
druid:
initial-size: 3 # 连接池中初始化连接的数量
mvc:
view:
prefix: /
suffix: .jsp
mybatis: #mybatis的配置
configuration:
map-underscore-to-camel-case: true
mapper-locations: classpath:mappers/*.xml
type-aliases-package: com.qf.SpringBootApplication.pojo
author.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
权限不足
</body>
</html>
error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
出错了
</body>
</html>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
项目主页
</body>
</html>
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<form method="post" action="user/login">
<p>
账号:<input type="text" name="userId">
</p>
<p>
密码:<input type="password" name="pwd">
</p>
<p>
<input type="submit" value="登录">
</p>
</form>
</body>
</html>
Shiro工具类
package com.qf.SpringBootApplication.util;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class ShiroAdvice {
@ExceptionHandler
public String doException(AuthorizationException ex){
//只捕获权限不足的异常
System.out.println("权限不足");
return "redirect:author.jsp";
}
}
控制层controller
DeptController
package com.qf.SpringBootApplication.controller;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/dept")
public class DeptController {
@RequiresPermissions("dept/add")
@GetMapping("/add")
public String add(){
System.out.println("调用了dept/add");
return "调用了dept/add请求";
}
@RequiresPermissions("dept/update")
@GetMapping("/update")
public String update(){
System.out.println("调用了dept/update");
return "调用了dept/update";
}
@RequiresPermissions("dept/delete")
@GetMapping("/delete")
public String delete(){
System.out.println("调用了dept/delete");
return "调用了dept/delete";
}
@RequiresPermissions("dept/query")
@GetMapping("/query")
public String query(){
System.out.println("调用了dept/query");
return "调用了dept/query请求";
}
}
EmpController
package com.qf.SpringBootApplication.controller;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/emp")
public class EmpController {
@RequiresPermissions("emp/add")
@GetMapping("/add")
public String add(){
return "调用了emp/add请求";
}
@RequiresPermissions("emp/edit")
@GetMapping("/edit")
public String edit(){
return "调用了emp/edit请求";
}
@RequiresPermissions("emp/query")
@GetMapping("/query")
public String query(){
return "调用了emp/query请求";
}
@RequiresPermissions("emp/exist")
@GetMapping("/exist")
public String exist(){
return "调用了emp/exist请求";
}
}
UserController
package com.qf.SpringBootApplication.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
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.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/user")
public class UserController {
@PostMapping("/login")
public String login(String userId,String pwd){
System.out.println("账号:"+userId);
System.out.println("密码:"+pwd);
//1、把账号、密码必须封装在token中
UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(userId,pwd);
//2、获取Subject对象
Subject subject= SecurityUtils.getSubject();
try {
//3、调用登录方法
subject.login(usernamePasswordToken);
//如果登录认证的过程失败,抛出异常
}catch (UnknownAccountException ex){
System.out.println("账号不存在");
return "error";
}catch (AuthenticationException ex){
System.out.println("密码错误");
return "error";
}catch (Exception ex){
System.out.println("发生异常,信息如下:"+ex.getMessage());
return "error";
}
return "index";
}
}
网页访问:http://localhost:8081/login.jsp
测试登录功能
登录成功
测试访问权限,测试成功(因为zym用户,具备了5678的员工执行功能)
如果我访问dept部门中的方法会超出权限