1、Shiro简介
1.1、什么是shiro
- Apache Shiro 是 Java 的一个安全(权限)框架。
-
Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。
-
Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等。
-
下载地址
1.2、shiro有哪些功能?
如图所示,shiro的大部分功能在此图上,接下来我帮助大家解读这些功能
1、Authentication:身份认证,用于验证判断用户是否拥有权限 (对应身份)
2、Authorization:身份授权,对于指定身份拥有什么权限进行给予(如老师给了同学管理班级的能力)
3、Session Management:会话管理,用户在进行登录操作时便开启了一次会话;
4、Cryptography:数据加密,用于保护数据的安全性,将密码加密存储更安全,常用加密方式有md5、des等
5、Web Suppot:支持web,方便集成到Web环境中
6、Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率
7、Concurrency:支持多线程应用并发验证,如一个线程中又开启了一个线程,能将权限自动传递过去
8、Testing:提供测试支持
9、Run As:允许用户伪装成另一个用户的身份进行方法(需取得允许)
10、RememberMe:记住我,相当于选中后下次便不需要输入账户密码了
以上就是shiro的大体功能了!
现在我们来解析shiro的架构
1.3、Shiro架构(外部)
我们首先从外部来观查Shiro是如何执行的
启动应用程序后首先获取当前的对象然后根据安全管理器ShiroSecurityManager从Realm中获得的安全数据(如用户、权限)来进行验证
- Subject:程序直接交互的对象便是Subject,可以理解Subject就是shiro对外API核心。Subject代表当前用户(这个用户可以不是一个人,他是程序直接交互的对象,它可以是网络爬虫,也可以是机器人!),与Subject的所有交互都会由SecurityManager来处理,SecurityManager才是实际执行者
- SecurityManager:安全管理器,所有与安全有关的操作都会与其交互,它管理着所有的Subject,它是Shiro的核心,负责与Shiro的其他组件交互的职责
- Realm:Shiro从Realm中获取安全数据(如用户信息、权限等),如果SecurityManager要验证用户身份,那么他需要从Realm中获取相应的用户来进行比较来确认用户身份是否合法,也需要从Realm中得到用户相应的角色/权限进行验证用户是否能执行该操作
1.4、Shiro架构(内部)
- Subject:任何可以与应用交互的用户
- Security Manager:所有的交互都通过其进行控制,它控制所有Subject,且负责认证、授权、会话以及缓存的管理
- Cryptography:数据加密模块,Shiro提供了一些常见的加密组件用于加密操作
其余具体可参考1.2
2、与SpirngBoot集成
2.1、 Springboot环境搭建
1、新建一个项目或模块,勾选依赖
2、创建完项目修改pom文件添加依赖
<dependencies>
<!--导入springWeb-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--导入thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--spring单元测试-->
<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>
项目结构如下、
3、创建controller来测试环境是否正常
3.1、创建一个控制器
/**
* @author: 霖磬
* @remark:
* @createTime: 2021-03-04 20:21
* @version:
**/
@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";
}
}
3.2、创建一个index.html页面
<!DOCTYPE html>
<!--导入命名空间 thymeleaf 不导入也没关系 只是会爆红但可以正常使用-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<hr>
<a th:href="@{/user/add}">add</a> |
<a th:href="@{/user/update}">update</a>
</body>
</html>
3.3、新建一个user包 包下创建add.html和update.html用于权限控制
3.3.1、创建add.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>add</h1>
</body>
</html>
3.3.2、创建update.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Update</h1>
</body>
</html>
4、运行测试
现在可以通过这两个超链接进行页面跳转,那我们要怎样才能使其拥有限制呢?
2.2、Shiro实现登录拦截
1、导入shiro整合Spring的包
<!--shiro整合spring的包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
2、创建一个ShiroConfig类
/**
* @author: 霖磬
* @remark:
* @createTime: 2021-03-04 21:30
* @version:
**/
@Configuration//申明这是个配置类
public class ShiroConfig {
//创建ShiroFilterFactoryBean :3
@Bean//通过Qualifier注解去指定我们安全管理器
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager safetyManager){
//ShiroFilterFactoryBean对象 它是shiro和spring整合的关键
ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
//通过bean去设置安全管理器
bean.setSecurityManager(safetyManager);
return bean;
}
//DefaultWebSecurityManager 该类对应SecurityManager(安全管理器) :2
@Bean//通过Qualifier注解去指定我们realm对象
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRealm对象 因为我们需要从realm对象中获取安全数据
securityManager.setRealm(userRealm());
return securityManager;
}
//创建realm对象 :1
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
3、创建UserRealm类
/**
* @author: 霖磬
* @remark: 自定义的Realm
* @createTime: 2021-03-04 21:33
* @version:
**/
public class UserRealm extends AuthorizingRealm {
@Override//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
return null;
}
@Override//认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
return null;
}
}
4、实现登录拦截,在ShiroConfig类中getShiroFilterFactoryBean方法添加代码
//创建ShiroFilterFactoryBean :3
@Bean//通过Qualifier注解去指定我们安全管理器
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager safetyManager){
//ShiroFilterFactoryBean对象 它是shiro和spring整合的关键
ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
//通过bean去设置安全管理器
bean.setSecurityManager(safetyManager);
/* shiro中内置的过滤器
- anon: 无需认证就可以访问
- authc: 必须认证了才能访问
- user: 必须拥有记住我功能才能用
- perms: 拥有对某个资源的权限才能访问
- role: 拥有某个角色权限
*/
Map<String, String> filterMap = new LinkedHashMap<>();
//访问/user/add 是必须认证
filterMap.put("/user/add","authc");
filterMap.put("/user/update","authc");
//将过滤器添加到bean中.如果权限不足会自动访问login.jsp页面
bean.setFilterChainDefinitionMap(filterMap);
//我们需要设置权限不足时会打开的页面
bean.setLoginUrl("/toLogin");
return bean;
}
5、在控制层MyController中添加两个方法
@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);//执行登录的方法,如果没有异常就说明ok了
return "index";
} catch (UnknownAccountException e) {//用户名错误 返回登录界面
model.addAttribute("msg","用户名错误");
return "login";
} catch (IncorrectCredentialsException e) {//密码错误 返回登录界面
model.addAttribute("msg","密码错误");
return "login";
}
}
6、在UserRealm类中认证添加虚拟的账号密码来进行验证
@Override//认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证");
// 用户名、密码, 数据中取 此数据虚拟(可以从数据库中取)
String name = "root";
String password = "123456";
//强转令牌 获取到从前端封装过来的账号和密码
UsernamePasswordToken userToken= (UsernamePasswordToken) token;
//如果用户名不正确则抛出UnknownAccountException异常 告诉用户改用户名不存在
if (!userToken.getUsername().equals(name)) {
return null;//抛出异常 UnknownAccountException
}
//密码验证,因为密码事关程序的安全,我们需要交给shiro中SimpleAuthenticationInfo类来做
return new SimpleAuthenticationInfo("",password,"");
}
7、创建login.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>登录</h1>
<hr>
<p th:text="${msg}" style="color: red;"></p>
<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>
8、简单测试
当我们输入错误信息后则会显示用户米错误如果密码错误则会显示密码错误
如果两者正确则可以进入到我们的首页!
2.3、结合mybatis实现
2.3.1、在pom中导入依赖
<!--导入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--导入mysql的驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--导入druid数据源(德鲁伊)-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--引入mybatis,这是MyBatis官方提供的适配spring Boot的,而不是spring Boot自己的-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
2.3.2、在resources中创建配置文件application.yml
注意:使用yml配置文件每次:号后需要多出一个空格 必须严格要求!!1
spring:
datasource:
username: root
password: admin
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/lq?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
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
#配置mybatis
mybatis:
#配置别名设置
type-aliases-package: com.lin.pojo
#指定mapper的路径
mapper-locations: classpath:mapper/*.xml
2.3.4、创建用户User对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String psd;
private String perms;//权限
}
2.3.5、创建mapper并配置xml文件以及service与实现类
2.3.5.1、创建UserMapper接口
@Repository//将该类交给Spring接管
@Mapper//告诉程序这是个mapper类
public interface UserMapper {
/**
* 通过用户名查询用户
* @param name
* */
User queryUserByName(String name);
}
2.3.5.2、创建UserMapper.xml文件(在resources创建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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.lin.mapper.UserMapper">
<select id="queryUserList" resultType="User" parameterType="String">
select * from lq.user where name=#{name};
</select>
</mapper>
2.3.5.3、创建UserService接口
/**
* @author: 霖磬
* @remark:
* @createTime: 2021-03-04 23:43
* @version:
**/
public interface UserService {
/**
* 通过用户名查询用户
* @param name
* */
User queryUserByName(String name);
}
2.3.5.4、创建UserService实现类
/**
* @author: 霖磬
* @remark:
* @createTime: 2021-03-04 23:44
* @version:
**/
@Service//交给spring托管
public class UserServiceImpl implements UserService {
@Autowired//自动注入UserMapper
UserMapper userMapper;
@Override
public User queryUserByName(String name) {
return userMapper.queryUserByName(name);
}
}
2.4、测试环境
成功查出!
2.5、UserRealm`连接真实数据库
@Override//认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证");
//强转令牌 获取到从前端封装过来的账号和密码
UsernamePasswordToken userToken= (UsernamePasswordToken) token;
//通过传递过来的用户名从数据库查询出对应user
User user = userService.queryUserByName(userToken.getUsername());
if (user == null) {//没有这个人
return null;
}
//密码验证,因为密码事关程序的安全,我们需要交给shiro中SimpleAuthenticationInfo类来做 注意,写在这里我们可以通过subject去取到对应的数据
return new SimpleAuthenticationInfo(user,user.getName(),"");
}
2.6、实现授权功能
2.6.1、在controller添加一个方法,来告诉用户当前状况
@RequestMapping("/noauto")
@ResponseBody//返回一个字符串
public String unauthorized() {
return "未经授权,无法访问此页面";
}
2.6.2、在ShiroConfig类getShiroFilterFactoryBean方法中添加
//设置无权限进入页面时的自动跳转
bean.setUnauthorizedUrl("/noauth");
2.6.3、在UserRealm进行授权处理
@Override//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
//创建给予授权方法
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
//获取当前对象
Subject subject=SecurityUtils.getSubject();
//拿到User对象
User user = (User) subject.getPrincipal();
//设置当前用户权限
info.addStringPermission(user.getPerms());
//将权力返回
return info;
}
ShiroConfig
中的getShiroFilterFactoryBean
方法添加认证代码
//授权,正常情况下,没有授权会跳转到为授权页面
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
2.6.4、测试!
项目目录结构
感谢各位看官姥爷,初来乍到,还请多多包容!!