本案例实战框架用了以下技术
- SpringBoot框架
- Shiro的框架
- MyBatis框架
- Spring框架
- thymeleaf技术
- SSM三层框架
- 数据库
实操部分
Subject——org.apache.shiro.subject.Subject
特定于当前与软件交互的实体的安全视图
SecurityManager——org.apache.shiro.mgt.SecurityManager
Shiro架构的核心,管理并协调各个组件共同完成安全工作,它还管理每个应用程序用户的Shiro视图,因此它知道如何为每个用户执行安全操作。
Authenticator——org.apache.shiro.authc.Authenticator
Authenticator是负责认证的组件,当用户尝试登录时,登录是由Authenticator来执行的,Authenticator将会从Realms取出用户信息,来和用户提供的登录信息进行比对
Authenticator Strategy——org.apache.shiro.authc.pam.AuthenticationStrategy
如果超过一个Realm被配置了,这个AuthenticationStrategy将会协调这些Realms来决定认证操作是否成功,比如,认证成功是需要所有的Realms都认证成功,还是仅仅一个Realm成功
Authorizer——org.apache.shiro.authz.Authorizer
Authorizer是负责访问控制的组件,换句话说,它负责检测用户是否有执行某个操作的权限。和Authenticator一样,Authorizer也从多个Realms数据源中获取用户的角色信息和权限信息,通过这些信息来判断用户是否可以执行某个操作
SessionManager——org.apache.shiro.session.mgt.SessionManager
注意不是SecurityManager,SessionManager负责创建和管理用户的Session周期。在安全框架中,Shiro提供了一个独有的特性,Shiro可以在任何环境中管理用户会话,即使实在非Web/Servlet和非EJB容器环境中,默认情况下,Shiro将会使用已有的会话机制,如Servlet容器,如果没有,Shiro将会使用内建的企业会话管理机制来管理会话。通过SessionDao,我们可以使用任何数据源来持久化Session
SessionDAO——org.apache.shiro.session.mgt.eis.SessionDAO
SessionDAO代表SessionManager执行会话持久性(CRUD)操作。它允许将任何数据存储插入到Session Management基础结构中
CacheManager——org.apache.shiro.cache.CacheManager
CacheManager负责创建Shiro中的其他组件的Cache实例并管理其生命周期,因为Shiro需要访问各种数据源来进行认证,授权和会话管理,缓存一直是框架中的一流架构特性,可以在使用这些数据源时提高性能,任何现代的开源或者企业缓存产品都可以接入Shiro中
Cryptography——org.apache.shiro.crypto.*
Shiro的crypto包包含了易于使用和理解的常见加密算法实现。使用过Java自带的加密库的人都应该知道它很难使用,Shiro提供的加密API简化了复杂的Java机制,让加密更便于使用
Realms——org.apache.shiro.realm.Realm
正如前面提到的,Realms是Shiro和应用程序数据的桥梁,当需要执行认证和授权时,Shiro会从至少一个Realm中寻找用户信息,应用程序中可以配置许多个Realm,Shiro会在必要时协调这些Realm
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.7.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.lop</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> <!-- mybatis整合SpringBoot--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <!--整合包--> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> <!-- druids数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.12</version> </dependency> <!-- log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> </dependency> <!-- MySql的依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>1.2.17</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.9.1</version> </dependency> <!-- Thymeleaf模板--> <!--Thymeleaf 说明基于3.0.1 模板引擎--> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-java8time</artifactId> <version>3.0.4.RELEASE</version> </dependency> <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> <dependency> <groupId>org.xmlunit</groupId> <artifactId>xmlunit-core</artifactId> <version>2.5.1</version> <scope>compile</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Yml文件信息
spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource initial-size: 5 min-idle: 5 max-active: 20 # 配置获取连接等待超时的时间 max-wait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 time-between-eviction-runs-millis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 min-evictable-idle-time-millis: 300000 validation-query: SELECT 1 FROM DUAL test-while-idle: true test-on-borrow: false test-on-return: false # 打开PSCache,并且指定每个连接上PSCache的大小 pool-prepared-statements: true # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 最好的功能 max-pool-prepared-statement-per-connection-size: 20 filters: stat,wall use-global-data-source-stat: true # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
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>Document</title> </head> <body> <div shiro:hasPermisssion="user:add"> 假如hasPermisssion有这个限制 <a th:href="@{/user/add}">add</a> </div> <div align="center"> <h1>index</h1> <!--/*@thymesVar id="msg" type="java"*/--> <p th:text="${msg}"></p> <a th:href="@{/user/add}">add</a> <br> <a th:href="@{/user/update}">update</a> </div> </body> </html>
login.html页面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--从Session判断值--> <div align="center" th:if="session.loginUser==null"> <h1>登录</h1> <hr> <!--/*@thymesVar id="msg" type="java"*/--> <h2 th:text="${msg}" style="color: red;"></h2> <form th:action="@{/login}" method="post"> <p>用户名:<input type="text" name="username" placeholder="请输入用户名"></p> <p>用户密码:<input type="password" name="password" placeholder="请输入用户密码"></p> <p><input type="submit"></p> </form> </div> </body> </html>
add.html
update.html
数据层:
<?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"> <!--比要时候要加上--> <!--suppress ALL --> <mapper namespace="com.lop.mapper.UserMapper"> <select id="queryUserByName" parameterType="String" resultType="User"> select * from mybatis.user where name =#{name} </select> </mapper>
package com.lop.mapper; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; @Repository @Mapper public interface UserMapper { public UserMapper queryUserByName(String username); }
Service层:
package com.lop.Service; import com.lop.pojo.User; public interface UserService { public User queryUserByName(String username); }
package com.lop.Service; import com.lop.mapper.UserMapper; import com.lop.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceLmp implements UserService { @Autowired UserMapper userMapper; @Override public User queryUserByName(String username) { userMapper.queryUserByName(username); return null; } }
控制层:
package com.lop.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.session.Session; import org.apache.shiro.subject.Subject; 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", "hllow"); 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) { //获取当前的用户 Subject subject = SecurityUtils.getSubject(); //封装用户数据 UsernamePasswordToken token = new UsernamePasswordToken(username, password); //执行登录方法 try { subject.login(token); return "index"; } catch (UnknownAccountException e) { model.addAttribute("msg", "用户的名字步存在"); return "login"; } catch (IncorrectCredentialsException e) { model.addAttribute("msg", "用户的密码有误"); return "login"; } } @RequestMapping("/noauth") public String unauthorized(){ return "没有授权无法访问信息"; } }
认证授权
package com.lop.config; import com.lop.Service.UserService; import com.lop.pojo.User; 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; /** * 自定义的UserRealm */ public class UserRealm extends AuthorizingRealm { //把用户数据库拿过来 连接真实数据库 @Autowired UserService userService; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("----------------------授权"); // SimpleAuthorizationInfo SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //打开没有授权的功能 info.addStringPermission("uder:add"); //拿到当前对象 Subject subject = SecurityUtils.getSubject(); User currentUser = (User) subject.getPrincipal();//拿到user对象 //设置当前用户的权限 info.addStringPermission(currentUser.getPerms()); return info; }
//认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("==========================认证"); // String name = "root"; // String password = "123456"; UsernamePasswordToken usertoken = (UsernamePasswordToken) token; //连接真实数据库 User user=userService.queryUserByName(usertoken.getUsername()); if (user == null) { return null; } Subject cursubject = SecurityUtils.getSubject(); Session session = cursubject.getSession(); session.setAttribute("loginUser",user); // if(!usertoken.getUsername().equals(name)){ // return null; // } //密码认证 return new SimpleAuthenticationInfo(user,user.getPassword(),""); }
ShiroConfig 代码解读:
package com.lop.config; 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.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class ShiroConfig { //ShiroFilterFactoryBean @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
设置管理器
bean.setSecurityManager(defaultWebSecurityManager);
//授权的信息内容 有user:add 没有授权 filterMap.put("/user/add", "perms[user:add]"); filterMap.put("/user/update", "perms[user:update]");
//用户没有授权请前往这个页面 bean.setLoginUrl("/noauth");
//设置登录请求 bean.setLoginUrl("/tologin"); bean.setFilterChainDefinitionMap(filterMap); return bean; }
// 自定义的UserRealm // @Bean(name = "userRealm") @Bean public UserRealm userRealm() { return new UserRealm(); }
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http:www.thymeleaf.org/thymeleaf-extras-shiro">
<div shiro:hasPermisssion="user:add"> 假如hasPermisssion有这个限制 <a th:href="@{/user/add}">add</a> </div>
<div align="center"> <h1>index</h1> <!--/*@thymesVar id="msg" type="java"*/--> <p th:text="${msg}"></p> <a th:href="@{/user/add}">add</a> <br> <a th:href="@{/user/update}">update</a> </div>
login的登录
SpringBoot
理论介绍:
//设置管理器 bean.setSecurityManager(defaultWebSecurityManager); //增加shiro的内置过滤器 /** * anon * authc * perms * role */ Map<String, String> filterMap = new LinkedHashMap<>(); // filterMap.put("/user/add","authc"); // filterMap.put("/user/update","authc"); filterMap.put("/user/*", "authc"); //授权的信息内容 有user:add 没有授权 filterMap.put("/user/add", "perms[user:add]"); filterMap.put("/user/update", "perms[user:update]"); //用户没有授权请前往这个页面 bean.setLoginUrl("/noauth"); //设置登录请求 bean.setLoginUrl("/tologin"); bean.setFilterChainDefinitionMap(filterMap); return bean; }
//DefaultWebSecurityManager //DefaultWebSecurityManager 管理 @Bean("securityManager") public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); return securityManager; }
// 自定义的UserRealm // @Bean(name = "userRealm") @Bean public UserRealm userRealm() { return new UserRealm(); }
/*整合包 thymeleaf-extras-shiro */ @Bean public SiroDialect getSiroDialect() { return new SiroDialect(); }