Shiro
目录
1 Shiro简介
1.1 什么是shiro
- Apache Shiro是一个Java安全的框架
- Shiro能够非常容易开发出很好的应用,其不仅可以在JavaSE环境,也可以用在JavaEE环境
- Shiro可以完成认证、授权、加密、会话管理、web集成、缓存等
- 下载地址:Apache Shiro | Simple. Java. Security.
Authentication:身份认证/登录,验证用户是不是拥有相应的身份
Authorization:授权,即权限验证,验证某个已经认证的用户是否拥有某个权限
Session Manager:会话管理,即用户登录以后就是一次会话,在没有推出之前,它的所有信息都会存储在会话中;会话可以是普通的JavaSE环境,也可以是如web环境
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储
Web Support:Web支持。可以非常容易集成到web环境
Caching:缓存,用户登录后,其用户信息、拥有的角色\权限不必每次去查、这样可以提高效率
Concurrency:shiro 支持多线程的并发验证,即在一个线程中开启另外骗一个线程,能将权限自动传播过去
Testing:提供测试支持
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
注意:shiro不会去维护用户、维护权限;这些需要我们自己去设计提供;然后通过相应的接口注入给shiro即可。
1.2 Shiro架构
Subject:主体,代表了当前用户,这个用户不一定是一个具体的人,与当前用户交互的任何东西都要Subject,如网络爬虫、机器人等;相当于一个抽象概念;所有的subject都要绑定到SecurityManager,与Subject所有交互都会委托给SecurityManager;可以将Subject认为是一个门面,而SecurityManager才是实际的执行者。
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且他管理者所有的Subject;可以看出他是Shiro的核心,它负责与后边介绍的其他组件交互,就相当于SpringMVC中DispatcherServlet 前端控制器。
Relam:域,shiro从Relam获取安全数据(用户、角色、权限),就是SecurityManager 需要验证身份,那么久需要从Relam获取相应的用户进行比较以确定用户身份是否合法;也需要从Relam得到相应的角色\权限进行验证用户是否能进行操作;就相当于DataSource这样的一个数据源。
注意:
1)应用代码通过Subject进行认证和授权,而subject又委托给SecurityManager;
2)我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager得到合法的用户及其权限进行判断。
2 SpringBoot整合Shiro
项目依赖:
<?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.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.liubujun</groupId>
<artifactId>springboot-shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-shiro</name>
<description>springboot-shiro</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<!--shiro整合spring的包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.9.0</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.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.9</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
实现逻辑:
定义几个html,让其登录能够进行访问,从而验证我们的授权和验证的逻辑。

如:首页有add和update两个按钮,点击其中一个按钮就进入到其对应的页面。
2.1 用户认证
步骤一:shiro的配置类,其分为3步
配置UserRealm、DefaultWebSecurityManager、ShiroFilterFactoryBean
@Configuration
public class ShiroConfig {
/**
* 步骤三:ShiroFilterFactoryBean
* @param defaultWebSecurityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/**
* anon: 无需认证就可以访问
* authc: 必须认证了才能访问
* user: 必须记住我才能访问
* perms: 拥有对某个资源的权限才能访问
* role: 拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
// filterMap.put("/user/add","authc");
// filterMap.put("/user/update","authc");
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求
bean.setLoginUrl("/toLogin");
return bean;
}
/**
* 步骤二:DefaultWebSecurityManager
* @param userRealm
* @return
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRelam
securityManager.setRealm(userRealm());
return securityManager;
}
/**
* 步骤一
* 创建realm对象,需要自定义类
* @return
*/
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
步骤二:定义UserRealm类
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
/**
* @author liubujun
* @date 2022/5/16 13:40
*/
public class UserRealm extends AuthorizingRealm {
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了》》》授权doGetAuthorizationInfo");
return null;
}
/**
* 认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了》》》认证doGetAuthenticationInfo");
String name = "root";
String password = "123456";
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
if (!userToken.getUsername().equals(name)){
return null;
}
//shiro对密码进行了加密处理 不能直接验证
return new SimpleAuthenticationInfo("",password,"");
}
}
注意:只要是执行了登录操作,就会进入到这个认证逻辑,其中shiro对密码进行了加密处理,不能直接进行验证。
步骤三:controller层访问
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
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.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author liubujun
* @date 2022/5/12 16:54
*/
@Controller
public class MyController {
@RequestMapping({"/","/index"})
public String toIndex(Model model){
model.addAttribute("msg","欢迎学习Shiro");
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 {
//执行登录方法,如果没有异常就说明OK了
subject.login(token);
return "index";
} catch (UnknownAccountException e) { //用户名不存在异常
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e) { //密码不存在
model.addAttribute("msg","密码错误");
return "login";
}
}
}
步骤四:测试
1)访问

2)点击add或者update跳转登录

3)输入账号和密码(正确的是root/123456)我输的是root/root

可见,密码错误时会自动拦截,并且控制台执行了认证逻辑

4)输入正确的账号和密码(root/123456)

可见进入了页面,并且控制台也执行了认证逻辑

2.2 用户授权
用户授权需要用到
* perms: 拥有对某个资源的权限才能访问
步骤一:在shiroConfig中的ShiroFilterFactoryBean里面对perms进行配置;例如:
filterMap.put("/user/update","perms[user:update]");
那么登录用户必须拥有update权限才能够进行访问 ;
演示:登录进来之后

1)点击add ,进入页面
2)点击update,无法访问

注意:因为此时登录进来的页面是什么权限也没有的(如果不加上面那条授权),就add和update两者都可以进来,但加了之后就只有update权限的用户才能进来了(但登录用户此时什么权限都没,所以能够进入add,因为add不需要授权,不能够进入update,因为被要求授权了才可以进)
步骤二:对所有接口进行授权
//授权
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
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 liubujun
* @date 2022/5/16 13:35
*/
@Configuration
public class ShiroConfig {
/**
* 步骤三:ShiroFilterFactoryBean
* @param defaultWebSecurityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/**
* anon: 无需认证就可以访问
* authc: 必须认证了才能访问
* user: 必须记住我才能访问
* perms: 拥有对某个资源的权限才能访问
* role: 拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
//授权
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求
bean.setLoginUrl("/toLogin");
//设置未授权页面
bean.setUnauthorizedUrl("/noauth");
return bean;
}
/**
* 步骤二:DefaultWebSecurityManager
* @param userRealm
* @return
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRelam
securityManager.setRealm(userRealm());
return securityManager;
}
/**
* 步骤一
* 创建realm对象,需要自定义类
* @return
*/
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
步骤三:设置UserRealm,对用户进行授权
package com.liubujun.config;
import com.liubujun.entity.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.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
/**
* @author liubujun
* @date 2022/5/16 13:40
*/
public class UserRealm extends AuthorizingRealm {
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了》》》授权doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("user:add");
//拿到当前登录的这个对象
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
info.addStringPermission(user.getPerms());
return info;
}
/**
* 认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了》》》认证doGetAuthenticationInfo");
String name = "root";
String password = "123456";
//为了在授权那边能够拿到用户信息,定义了个用户对象并且传到了下面的SimpleAuthenticationInfo中的principal参数,这样在
//授权那边可以直接获取到
User user = new User();
user.setUsername(name);
user.setPassword(password);
String perms = "user:add";
user.setPerms(perms);
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
if (!userToken.getUsername().equals(name)){
return null;
}
//shiro对密码进行了加密处理 不能直接验证
return new SimpleAuthenticationInfo(user,password,"");
}
}
步骤三:controller加个未授权的接口
@RequestMapping("/noauth")
@ResponseBody
public String Unauthorized(){
return "未经授权无法发访问本页面";
}
步骤四:访问
因为add和update都被要求了需要授权,而在UserRelam中用户只被赋予了update权限。所以update可以访问,而add不能。
首页:

访问:add
访问update


浙公网安备 33010602011771号