Shiro安全框架【SpringBoot版】


Shiro安全框架

一、 入门概述

1.1、Shiro是什么

Apache Shiro 是一款功能强大的且易于使用的Java的安全框架。Shiro可以完成:认证、加密、会话管理、与web集集成等。借助SHiro可以帮助我们快速轻松的保护任何应用程序。

shiro官网:Apache Shiro | Simple. Java. Security.

在这里插入图片描述

1.2、为什么使用Shiro

与Shiro的特性密不可分:

  • 易于使用
  • 全面
  • 灵活
  • 强力支持Web
  • 兼容性强
  • 社区支持

1.3、Shiro与Spring Security的区别

  1. SpringSecurity基于Spring开发,项目若使用Spring 可以与SpringSecurity作权限更加方便,而Shiro需要与Spring进行整合

  2. Spring Security功能更加丰富

  3. Spring Security社区资源更加丰富

看到这里,是不是有些人就认为Spring Security功能更发面都比Shiro好,为什么不学习SpringSecurity。有一句话:存在即合理。下面看看Shiro的特点

  1. Shiro的配置和使用比较简单,SpringSecurity使用比较复杂

  2. Shiro的依赖性低,不需要任何的容器与框架,可以独立运行

  3. Shiro不仅仅可以使用在Web端,可以使用在任何的场景。

1.4、基本功能

了解Shiro的功能,我们可以去官网下载一张Shiro的功能结构图来进行补充学习:

阿帕奇四郎特色

1.4.1、主要功能
  1. 认证登录(Authentication)
  2. 授权验证(Authorization)
  3. 会话管理(Session Management)
  4. 密码加密(Cryptography)
1.4.2 、次要功能
  1. Web支持(web support)
  2. 缓存(caching)
  3. 多线程并发验证(Concurrency)
  4. 测试(Testing)
  5. 另外身份登录(Run as)
  6. 记住我(Remember me)

1.5、架构原理

从外部来看Shiro,即从应用程序的角度来观察使用Shiro完成工作

Shiro Basic Architecture Diagram

应用程序—>(登录)---->subject(对象)进行身份校验---->安全管理器(SecurityManager)---->Reaim(用户登陆的用户信息)

从内部的架构来看Shiro

Shiro Architecture Diagram

二、基本使用

2.1、环境准备

1、Shiro不依赖容器,可以直接利用Maven使用

2、添加依赖

<!-- Shiro依赖 -->
  <dependencies>
  	<dependency>
  		<groupId>org.apache.shiro</groupId>
  		<artifactId>shiro-core</artifactId>
  		<version>1.9.0</version>
  	</dependency>
  	
  	<dependency>
  		<groupId>commons-logging</groupId>
  		<artifactId>commons-logging</artifactId>
  		<version>1.2</version>
  	</dependency>
  </dependencies>

3、创建Maven工程

结构如下:

在这里插入图片描述

2.2、配置ini文件

在创建好的工程的Resources目录下,创建一个shiro.ini文件

在这里插入图片描述

[users]
zhangsan=z3
lisi=l4

2.3、登录认证

2.3.1、登录认证概念

(1)身份认证:一般需要提供身份ID等一些表示用户登陆这信息身份的标识,如提供email、用户名\密码来认证

(2)在Shiro中、用户需要提供principals(身份)和credentials(证明)给shiro。从而应用能验证用户身份。

2.3.2、登录认证的流程
  1. 收集用户二点身份/凭证,及如用户名/密码
  2. 调用Subject.login进行登录,如果失败则将得到的相应的AuthenticationException异常,根据异常提示用户登录错误信息,否则登陆成功。
  3. 创建自定义的Realm类,继承org.apache.shiro.realm.AuthenticationRealm类,实现doGetAuthenticationInfo()方法

在这里插入图片描述

2.3.3、登录认证示例

创建测试类,获取认证对象,进行登录认证,如下:

public class ShiroRun {
	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
		// 1、获取Shiro初始化 通过ini文件获取用户信息
		@SuppressWarnings("deprecation")
		IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
		try {
			// 通过工厂创建SecurityManager
			SecurityManager securityManager = factory.getInstance();
			SecurityUtils.setSecurityManager(securityManager);
			// 2、获取Subject对象
			Subject subject = SecurityUtils.getSubject();
			// 3、获取外部参数 通过页面获取的用户名和密码 创建token对象
			AuthenticationToken token = new UsernamePasswordToken("zhangsan", "z31");
			// 4、完成登录
			subject.login(token);
			System.out.println("登陆成功...");
		} catch (UnknownAccountException e) {
			// TODO: handle exception
			System.out.println("用户名不存在...");
		} catch (IncorrectCredentialsException e) {
			// TODO: handle exception
			System.out.println("密码错误...");
		} catch (AuthenticationException e) {
			System.out.println("登陆失败...");
		}
	}
}

登陆成功:

在这里插入图片描述

密码错误:

在这里插入图片描述

账户错误:

在这里插入图片描述

2.4、角色、授权

2.4.1、授权

授权:也叫做访问控制,即在应用中控制谁访问哪些资源授权中需要了解的概念:主体(Subject)资源(Resources)权限(Permission)角色(Role)

  • **主体:**访问应用的用户,用户经授权才可访问指定资源
  • **资源:**在应用中用户可以访问的URL,比如JSP页面,查看/编辑某些权限
  • **权限:**表示在应用中用户能不能访问某个资源(有没有权利去访问某一个资源)

Shiro支持粗粒度的授权(用户模块的所有权限的授权)、也支持细粒度的授权(某个模块下的某个功能,比如查询)

2.4.1.1、授权方式
  1. 编程式授权:通过IF-ELSE授权
if(subject.hasRole("admin")){
    // 有admin的权限
}else if(subject.hasRole("commons")){
	// 普通用户的权限
}else{
    // 没有权限
}
  1. 注解式:通过执行的Java方法上加上注解完成,没有泉下今年的将抛出异常
@RequriesRole("admin")
public void queryALL(){
    // 具体的业务逻辑
}
  1. JSP/GSP标签,在JSP/GSP页面通过相应的标签完成
<shiro:hasRole name="admin">
    <input type="button" class="queryAll" name="queryAll" value="查询所有"/>
</shiro:hasRole>
2.4.1.2、授权流程
  1. 首先调用Subject.isPermitted*/hasRole* 接口,其余委托SecurityManager。而SecurityManager接着会委托给Authorizer。
  2. Authorizer是真正的授权者,如果调用isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转化成相应的Permission示例。
  3. 再授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限
    1. Authorizer会判断Realm的角色/权限是否与传过来的匹配,如果有多个Realm,则会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败。

在这里插入图片描述

2.4.2、角色

角色:权限的集合(比如说系统管理员、业务人员、普通用户人员等)

2.4.3、角色授权示例

【角色】

在ini文件里配置用户角色的权限信息:

[users]
zhangsan=z3,admin,commons
lisi=l4,commons

[roles]
admin=user:insert,user:select
commons=user:select

通过以下方式完成用户角色下权限的判断:

if (subject.isPermitted("user:insert")) {
	System.out.println("用户有插入权限");
}else {
	System.out.println("没有insert权限");
}

完整示例:

public class ShiroRun {
	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
		// 1、获取Shiro初始化 通过ini文件获取用户信息
		@SuppressWarnings("deprecation")
		IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
		try {
			// 通过工厂创建SecurityManager
			SecurityManager securityManager = factory.getInstance();
			SecurityUtils.setSecurityManager(securityManager);
			// 2、获取Subject对象
			Subject subject = SecurityUtils.getSubject();
			// 3、获取外部参数 通过页面获取的用户名和密码 创建token对象
			AuthenticationToken token = new UsernamePasswordToken("lisi", "l4");
			// 4、完成登录
			subject.login(token);
			System.out.println("登陆成功...");
			// 5、判断用户角色
			if(subject.hasRole("commons")) {
				System.out.println("拥有commons角色");
			}else{
				System.out.println("没有拥有commons角色");
			}
			if (subject.isPermitted("user:insert")) {
				System.out.println("用户有插入权限");
			}else {
				System.out.println("没有insert权限");
			}
		} catch (UnknownAccountException e) {
			// TODO: handle exception
			System.out.println("用户名不存在...");
		} catch (IncorrectCredentialsException e) {
			// TODO: handle exception
			System.out.println("密码错误...");
		} catch (AuthenticationException e) {
			System.out.println("登陆失败...");
		}
	}
}

注意:使用subject.checkPermission(“user:insert”);没有权限则会抛异常

【授权】

首先现在ini文件里为用户添加相应的角色(zhangsan添加admin、commons角色;lisi添加commons角色)

[users]
zhangsan=z3,admin,commons
lisi=l4,commons

使用以下方式判断角色:

if(subject.hasRole("commons")) {
		System.out.println("拥有commons角色");
	}else{
		System.out.println("没有拥有commons角色");
}

完整示例:

public class ShiroRun {
	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
		// 1、获取Shiro初始化 通过ini文件获取用户信息
		@SuppressWarnings("deprecation")
		IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
		try {
			// 通过工厂创建SecurityManager
			SecurityManager securityManager = factory.getInstance();
			SecurityUtils.setSecurityManager(securityManager);
			// 2、获取Subject对象
			Subject subject = SecurityUtils.getSubject();
			// 3、获取外部参数 通过页面获取的用户名和密码 创建token对象
			AuthenticationToken token = new UsernamePasswordToken("zhangsan", "z3");
			// 4、完成登录
			subject.login(token);
			System.out.println("登陆成功...");
			// 5、判断用户角色
			if(subject.hasRole("commons")) {
				System.out.println("拥有commons角色");
			}else{
				System.out.println("没有拥有commons角色");
			}
		} catch (UnknownAccountException e) {
			// TODO: handle exception
			System.out.println("用户名不存在...");
		} catch (IncorrectCredentialsException e) {
			// TODO: handle exception
			System.out.println("密码错误...");
		} catch (AuthenticationException e) {
			System.out.println("登陆失败...");
		}
	}
}

2.5、密码加密

在实际的开发中,一些敏感的信息需要加密,比如说用户的密码,shiro内嵌了很多的加密算法

2.5.1、使用Shiro进行加密
public class ShiroMD5 {

	public static void main(String[] args) {
		String salt = "salt";
		// 1、密码明文
		String password = "z3";
		// 2、使用MD5加密
		Md5Hash MD5 = new Md5Hash(password);
		System.out.println("使用MD5加密后的密码:" + MD5.toHex());
		// 3、给MD5加盐值 在加密玩的再次拼接一段字符串
		Md5Hash MD5_2 = new Md5Hash(password, salt);
		System.out.println("使用MD5(带盐值)加密后的密码:" + MD5_2.toHex());
		// 3、给MD5加盐值 多次加密
		Md5Hash MD5_3 = new Md5Hash(password, salt,3);
		System.out.println("使用MD5(带盐值三次加密)加密后的密码:" + MD5_3.toHex());
	}
}

在这里插入图片描述

三、Shiro整合SpringBoot

3.1、整合依赖

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>com.wei</groupId>
		<artifactId>dhcc_ShiroProject</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>dhcc_ShiroSpringBoot</artifactId>
	
	 <properties>
        <java.version>1.8</java.version>
        <spring.shiro.version>1.9.0</spring.shiro.version>
    </properties>
    
	<dependencies>
		<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>
        <!-- shiro -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>${spring.shiro.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--页面模板依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--热部署依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- MyBatis-plus -->
        <dependency>
        	<groupId>com.baomidou</groupId>
        	<artifactId>mybatis-plus-boot-starter</artifactId>
        	<version>3.0.5</version>
        </dependency>
        <!-- MySQL -->
        <dependency>
        	<groupId>mysql</groupId>
        	<artifactId>mysql-connector-java</artifactId>
        	<version>8.0.28</version>
        </dependency>
	</dependencies>
	
	<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>    
        </plugins>
    </build>
</project>

3.2、yml配置文件

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*Mapper.xml
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shirodb?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
shiro: 
  loginUrl: /shiroController/login

3.3、创建目录结构

在这里插入图片描述

3.4、创建数据库

打开SqlYog工具创建数据库shirodb

USE `shirodb`;

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(50) DEFAULT NULL COMMENT '用户名',
  `password` varchar(50) DEFAULT NULL COMMENT '密码',
  `role_id` bigint DEFAULT NULL COMMENT '角色编号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

3.5、创建对应的类

【实体类User】

@Data
public class User {
	private Long id;
	private String name;
	private String password;
	private Long roleId;
}

【respority数据持久层】

@Repository
public interface UserMapper extends BaseMapper<User>{}

采用MyBatis-PLUS的通用Mapper

【Service数据服务层(业务层)】

// 接口
public interface UserService {
	User getUserInfoByName(String name);
}

// 实现类
@Service
public class UserServiceImpl implements UserService {
	
	@Resource
	private UserMapper userMapper;

	public User getUserInfoByName(String name) {
		QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
		queryWrapper.eq("name", name);
		User user = userMapper.selectOne(queryWrapper);
		return user;
	}

}

【controller控制层】

@Controller
@RequestMapping("/shiro")
public class UserController {

	@RequestMapping(value = "/login", method = RequestMethod.GET)
	@ResponseBody
	public String login(@RequestParam("name") String name, @RequestParam("password") String password) {
		// 1、获取Subject对象
		Subject subject = SecurityUtils.getSubject();
		// 2、 封装请求对象到Token对象
		UsernamePasswordToken token = new UsernamePasswordToken(name,password);
		try {
			// 3、调用Subject的login方法完成登录
			subject.login(token);
			return "登陆成功!";
		} catch (UnknownAccountException e) {
			e.printStackTrace();
			System.out.println("用户名不存在...");
			return "用户名错误,登陆失败";
		} catch (IncorrectCredentialsException e) {
			e.printStackTrace();
			System.out.println("密码错误...");
			return "密码错误,登陆失败!";
		} catch (AuthenticationException e) {
			e.printStackTrace();
			System.out.println("登陆失败...");
			return "登陆失败";
		}
	}
}

【Shiro的自定义授权配置类】

@Component
public class MyShiroRealm extends AuthorizingRealm {

	@Resource
	private UserService UserService;

	/**
	 * 用户的登录信息 自定义授权方法
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * 自定义登录认证方法 token
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		// 获取登录用户的信息
		String name = token.getPrincipal().toString(); // 获取用户名
//		String password = new String((char[]) token.getCredentials()); // 获取密码
		// 调用业务层的接口获取数据库的用户信息
		User user = UserService.getUserInfoByName(name);
		if (user != null) {
			// 封装数据
			AuthenticationInfo info = new SimpleAuthenticationInfo(
									token.getPrincipal().toString(),
									user.getPassword(),
									ByteSource.Util.bytes("salt"),
									token.getPrincipal().toString()
					);
			return info;
		}
		return null;
	}
}

这里需要继承一下AuthorizingRealm,重写参数为token的方法,实现用户的授权登录功能

3.6、创建Shiro配置类

3.6.1、自定义Shiro配置类

要想实现自定义的Shiro配置类,需要创建一个DefaultSecurityManager的方法,在里面去重新自定义授权功能。

@Configuration
public class ShiroConfig {

	@Resource
	private MyShiroRealm shiroRealm;
	
	/**
	* 描述:TODO(这里用一句话描述这个方法的作用) 
	* @Title: 创建默认的安全管理器
	* @return
	* @author weiyongpeng
	* @date  2022年10月3日 上午8:15:52
	 */
	@Bean
	public DefaultWebSecurityManager defaultWebSecurityManager() {
		// 1、创建DefaultWebSecurityManager
		DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
		// 2、创建加密对象 设置加密属性
		HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
		// 3、将加密对象存储到MyShiroRealm 采用MD5 迭代次数3
		matcher.setHashAlgorithmName("MD5");
		matcher.setHashIterations(3);
		shiroRealm.setCredentialsMatcher(matcher);
		// 4将MyShiroRealm存储到DefaultSecurityManager
		manager.setRealm(shiroRealm);
		// 5、返回DefaultSecurityManager
		return manager;
	}
}
3.6.2、自定义Shiro拦截范围
@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition() {
	DefaultShiroFilterChainDefinition filter = new DefaultShiroFilterChainDefinition();
	// 设置不忍证可以访问的资源
	filter.addPathDefinition("/shiro/login", "anon");
	filter.addPathDefinition("/login","anon");
	// 设置需要进行登录才可以访问的拦截范围
	filter.addPathDefinition("/**", "authc");
	return filter;
}

3.7、测试

使用APIFOX测试登录

【登陆成功】

在这里插入图片描述

【登陆失败】

在这里插入图片描述

3.8、登录认证前端

使用Thymeleaf实现前端的登陆页面

【引入依赖】

<!--页面模板依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

【添加配置】

  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    suffix: .html
    encoding: UTF-8
    mode: HTML5

【编写页面】

登陆页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
	<h1>Shiro登录认证</h1>
	<form th:action="@{/shiro/userLogin}" method="post">
		<div>
			<label>用户名:</label> <input type="text" name="name"
				placeholder="请输入用户名:">
		</div>
		<div>
			<label>用户名:</label> <input type="password" name="password"
				placeholder="123456">
		</div>
		<div class="buttonDiv">
			<input type="reset" value="重置"> 
			<input type="submit" value="登录">
		</div>
	</form>
</body>
</html>

登陆成功首页

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录首页</title>
</head>
<body>
登陆的用户:<span th:text="${session.user}"></span>
</body>
</html>

登陆失败错误页

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>登陆失败错误页面</h1>
<h3>
	对不起,你在登陆的时候遇到了<span th:text="${errorMsg}"></span>的错误
	<a th:href="@{/shiro/login}">重新登陆</a>
</h3>
</body>
</html>

修改controller的代码

@RequestMapping(value = "/login",method = RequestMethod.GET)
	public String login() {
		return "login";
	}
	
	@RequestMapping(value = "/userLogin", method = RequestMethod.POST)
	public String userLogin(@RequestParam("name") String name, 
							@RequestParam("password") String password,
							HttpSession session,
							Model model) {
		// 1、获取Subject对象
		Subject subject = SecurityUtils.getSubject();
		// 2、 封装请求对象到Token对象
		UsernamePasswordToken token = new UsernamePasswordToken(name,password);
		try {
			// 3、调用Subject的login方法完成登录
			subject.login(token);
			// 放入session
			session.setAttribute("user", token.getPrincipal().toString());
			return "main";
		} catch (UnknownAccountException e) {
			e.printStackTrace();
			System.out.println("用户名不存在...");
			model.addAttribute("errorMsg","用户名错误,登陆失败");
			return "error";
		} catch (IncorrectCredentialsException e) {
			e.printStackTrace();
			System.out.println("密码错误...");
			model.addAttribute("errorMsg","密码错误,登陆失败!");
			return "error";
		} catch (AuthenticationException e) {
			e.printStackTrace();
			System.out.println("登陆失败...");
			model.addAttribute("errorMsg","登陆异常,登陆失败!");
			return "error";
		}
	}

四、多个Realm登录校验

  1. 多个Realm实现原理

当应用程序配置多个Realm时,例如,用户名密码校验,手机号校验,邮箱校验等等。Shiro的ModularRealmAuthentication会使用内部的AuthenticationStarategy组件判断认证是否成功或者谁败。

AuthenticationStrategy是一个无状态的组件,它本身验证尝试中被询问4次(这4次交互所需的任何必须的状态将被作为方法参数)

(1)在所有的Realm被调用之前

(2)在调用Realm的getAuthenticationInfo()方法之前

(3)在调用Realm的getAuthenticationInfo()方法之后

(4)在所有的Realm被调用之后

五、rememberMe功能

Shiro提供了记住我的(Remember Me)功能,用户可以在登陆成功后,下次访问页面无需再次登录仍然可以访问。

5.1、基本流程

  1. 首先在登陆的页面选中Remember Me然后再登陆成功后,如果是浏览器登录,一般会把Remember Me的Cookie写道客户端并保存。
  2. 关闭浏览器再次重新打开,会发现浏览器还是记住你。
  3. 访问一般的网页服务器,仍然知道你是谁,且能正常访问。
  4. 但是,如果我们访问电商平台,如果要查看我的订单或者进行支付,此事还需再次进行身份的认证。

5.2、代码实现

5.2.1、设置记住我

在配置类里的安全管理器方法里添加记住我功能

在这里插入图片描述

5.2.2、配置记住我管理器以及Cookie属性
// Cookie的属性设置
public SimpleCookie rememberCookie() {
	SimpleCookie cookie = new SimpleCookie("rememberMe");
	// 设置跨域
//	cookie.setDomain(domain);
	cookie.setPath("/");
	cookie.setHttpOnly(true);
	cookie.setMaxAge(30*24*60*60); // 30天
	return cookie;
}
	
//创建CookieMaanger
public CookieRememberMeManager rememberMeManager() {
	CookieRememberMeManager manager  =new CookieRememberMeManager();
	manager.setCookie(rememberCookie());
	manager.setCipherKey("1234567890987654".getBytes());
	return manager;
}
5.2.3、添加用户过滤器

保证在登陆成功后,Shiro将登陆成功的用户信息放入到cookie中存储

在这里插入图片描述

5.2.4、改造Controller登录接口
// 2、 封装请求对象到Token对象 开启Remember
		UsernamePasswordToken token = new UsernamePasswordToken(name,password,rememberMe);
5.2.5、改造登陆页面
<div>记住我:<input type="checkbox" name="rememberMe" value="true"> </div>

在这里插入图片描述

六、用户登出

用户登陆之后,配套的操作有登出操作,直接通过Shiro过滤器即可以实现

6.1、代码实现

【过滤器】

@Bean
	public DefaultShiroFilterChainDefinition shiroFilterChainDefinition() {
		DefaultShiroFilterChainDefinition filter = new DefaultShiroFilterChainDefinition();
		// 设置不认证可以访问的资源
		filter.addPathDefinition("/shiro/userLogin", "anon");
		filter.addPathDefinition("/shiro/login","anon");
		// 配置登出操作
		filter.addPathDefinition("/logout", "logout");
		// 设置需要进行登录认证才可以访问的拦截范围
		filter.addPathDefinition("/**", "authc");
		// 添加remember的用户
		filter.addPathDefinition("/**", "user");
		return filter;
	}

【登陆后的页面改造】

在这里插入图片描述

七、授权、角色认证

7.1、角色认证

用户登录后,需要验证是否具有指定角色权限,Shiro也提供了方便的工具进行判断,

这个工具就是Realm的doGetAuthenticationinfo方法进行判断,出发权限判断的有两种方式

  1. 在页面中通过shiro:xxxx属性判断
  2. 在接口中通过注解@Requiresxxxxx判断
7.1.1、后端接口服务注解🔥🔥🔥

同过给接口方法添加注解可以实现权限校验,可以加载控制器上,也可以加载业务方法上,一般加载控制器方法上,常用的注解如下:

  1. @RequiresAuthentication

验证用户是否登录,等同于方法subject.isAuthenticated();

  1. @RequiresUser

验证用户是否记忆:

登录认证成功subject.isAuthenticated()为true

登录后被记忆subjec.isRemembered()为true

  1. @RequiresGuest

验证是否是一个Guest请求,是否是游客的请求

此时subject.getPrincipal()为null

  1. @RequiresRoles

验证subject是否有相应的角色,有角色访问方法,否则会抛出异常

AuthentizationException

例如:@RequiresRoles(“aRoleName”)

void someMethod();

只有subject有aRoleName角色才能访问方法someMethod()

  1. @RequiresPremissions🔥🔥

验证subject是否具有相应的权限,有权限访问方法,没有则抛出异常

AuthorizationException。

例如:@RequiresPermissions(“USER_SERVICE:QUERY”,“USER_SERVICE:MODIFY”);

void someMethod();

subject只有同时具有USER_SERVICE:QUERY,USER_SERVICE:MODIFY权限才可以访问someMethod()方法

7.1.2、授权验证-没有角色无法访问

【首先在Controller层写一个方法】

@RequiresRoles("admin")
@RequestMapping(value = "/userLoginRoles",method = RequestMethod.GET)
@ResponseBody
public String userLoginRoles() {
	System.out.println("登陆验证表示");
	return "验证角色成功";
}

注意加上注解@RequiresRoles

当我们去访问这个接口的时候,如果这时候还有没做任何的操作,肯定会被抛出异常,如下图所示:

在这里插入图片描述

7.1.3、授权验证-有角色

在自定义的Realm类里重写的doGetAuthorizationInfo定义角色授权信息

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
	// 进行授权信息
	System.out.println("进入自定义授权方法");
	// 有权限放行
	// 1、 创建存储信息的对象
	SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
	// 2、存储角色信息 正常是从数据库里获取
	info.addRole("admin");
	// 3、返回角色信息
	return info;
}

在这里插入图片描述

7.1.4、创建角色表

在实际的业务开发中,我们要想完成角色的认证授权,不能向上述一样,需要在数据库中获取相关的用户权限角色信息。

【权限表】

请添加图片描述

【创建权限实体类】

public class Role {
	/**
	 * 权限编号
	 */
	private Long id;
	/**
	 * 角色名称
	 */
	private String name;
	/**
	 * 描述
	 */
	private String desc;
	/**
	 * 显示名称
	 */
	private String realName;
}

【用户角色表】

请添加图片描述

【mapper的查询用户角色】

@Select("SELECT NAME FROM role r WHERE r.`id` IN (\r\n" + 
			"   SELECT ru.`role_id` FROM role_user ru WHERE ru.`user_id`=(\r\n" + 
			"      SELECT u.`role_id` FROM `user` u WHERE u.`name` = #{principal}  \r\n" + 
			"   )\r\n" + 
			")")
	List<String> getUserRolesInfoMapper(@Param("principal") String principal);

【Service接口方法】

List<String> getUserRolesInfo(String name);

【自定义MyShiroRealm】

@Resource
	private UserService UserService;

	/**
	 * 用户的登录信息 自定义授权方法
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// 进行授权信息
		System.out.println("进入自定义授权方法");
		// 有权限放行
		// 1、 创建存储信息的对象
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		// 2、存储角色信息 正常是从数据库里获取
//		info.addRole("admin");
		List<String> rolesInfo = UserService.getUserRolesInfo(principals.getPrimaryPrincipal().toString());
		rolesInfo.forEach(System.out::println);
		info.addRoles(rolesInfo);
		// 3、返回角色信息
		return info;
	}

7.2、授权访问

获取权限进行验证,首先是创建权限资源表

CREATE TABLE `shirodb`.`premissions`(  
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `name` VARCHAR(50) COMMENT '权限名称',
  `info` VARCHAR(30) COMMENT '权限信息',
  `desc` VARCHAR(50) COMMENT '描述',
  PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;

同理角色认证的编写,首先要在添加相应的接口方法查询数据库中对应角色下的权限集合。

【mapper】

@Select({
		"<script>",
		"select info from premissions where id in ",
		"(select permissions_id from role_ps where role_id in (",
		"select id from role where name in ",
		"<foreach collection='roles' item='name' open='(' separator=',' close=')'>",
		"#{name}",
		"</foreach>",
		"))",
		"</script>"
	})
	List<String> getUserPermissionsInfoMapper(@Param("roles") List<String> roles);

【Service接口】

List<String> getUserPermisssionsInfo(List<String> roles);

【Controller接口】

@RequiresPermissions(value = {"user:update","user:delete","user:add"})
	@RequestMapping(value = "/userLoginPermissions",method = RequestMethod.GET)
	@ResponseBody
	public String userLoginPermissions() {
		System.out.println("登陆权限验证标识");
		return "验证权限成功";
	}

注意🔥@RequiresPermissions注解的参数value是一个数组

最后要想真正能够的实现功能,还需要再自定义的Realm类里定义权限认证。

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// 进行授权信息
		System.out.println("进入自定义授权方法");
		// 有权限放行
		// 1、 创建存储信息的对象
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		// 2、存储角色信息 正常是从数据库里获取
//		info.addRole("admin");
		// 3、获取用户角色信息
		List<String> rolesInfo = UserService.getUserRolesInfo(principals.getPrimaryPrincipal().toString());
		// 3、获取用户的权限信息
		List<String> permisssionsInfo = UserService.getUserPermisssionsInfo(rolesInfo);
		permisssionsInfo.forEach(System.out::println);
		rolesInfo.forEach(System.out::println);
		// 4、存储到info、对象中
		info.addStringPermissions(permisssionsInfo); // 权限信息
		info.addRoles(rolesInfo); // 角色信息
		// 5、返回角色信息
		return info;
	}

八、自定义异常

创建认证异常处理类,使用@ControllerAdvice加@ExceptionHandler注解实现特殊异常处理

@ControllerAdvice
public class PermissionsException {
	
	/**
	* 描述:没有权限自定义异常
	* @Title: doNoPermissionException
	* @return
	* @author weiyongpeng
	* @date  2022年10月4日 上午8:21:39
	 */
	@ResponseBody
	@ExceptionHandler(UnauthorizedException.class)
	public String doNoPermissionException(Exception e) {
		return "对不起,您无权限访问";
	}
	
	/**
	* 描述:无身份认证异常
	* @Title: doNoAuthenticationException
	* @param e
	* @return
	* @author weiyongpeng
	* @date  2022年10月4日 上午8:23:26
	 */
	@ResponseBody
	@ExceptionHandler(AuthenticationException.class)
	public String doNoAuthenticationException(Exception e) {
		return "对不起,权限认证失败";
	}
}

九、前端角色权限认证

前面虽然说已经实现了基本的功能,但是在用户体验上效果不是很好,我们在平常的系统中,比如说银行的管理系统、绩效系统。行内人员有着不同的权限,比如说行长有全权限,经理有调动查看账务的权限。我们想要的效果是:不同权限的用户登录系统后,看到的界面根据所拥有的权限显示。

那么接下来,我们来实现一把:

9.1、引入依赖

因为我们使用的前端框架是Thymeleaf,所以:

     <!-- 配置Thyemleaf与Shiro的整合依赖 -->
        <dependency>
        	<groupId>com.github.theborakompanioni</groupId>
        	<artifactId>thymeleaf-extras-shiro</artifactId>
        	<version>2.0.0</version>
        </dependency>

配置Shiro的标签配置

// 配置类里配置解析Shiro标签的配置方法
@Bean
public ShiroDialect shiroDialect() {
	return new ShiroDialect();
}

在Thymeleaf中常用的Shiro标签属性:shiro:

引入完成后,直接启动应用会报错,因为thymeleaf-extras-shiro这个组件需要thymeleaf3.0支持,所以这里不再演示。

十、实现缓存

10.1、缓存工具Ehcache

Ehchche是一种广泛使用的开源Java分布式缓存框架,住哟啊面向的是通用缓存,JavaEE是轻量级的缓存容器。可以和大部分的Java项目无缝融合。例如Hibernate中使用的缓存技术就是Ehcache

Ehcache支持磁盘和内存中的存储,如果内存不够,可以放到磁盘中,也可以直接在JVM虚拟机中做缓存,高效,速度快,但是共享麻烦,分布式集群中不方便,主要做的是本地缓存。

10.2、Shiro整合Ehcache

Shiro官网提供了Shiro-ehcache的整合方案,减少对数据库的访问,提高项目的执行效率。

【添加依赖】

<!-- Shiro整合Ehcache框架 -->
<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-ehcache</artifactId>
	<version>1.4.2</version>
</dependency>
<dependency>
	<groupId>commons-io</groupId>
	<artifactId>commons-io</artifactId>
	<version>2.6</version>
</dependency>

【配置类配置缓存管理器】

/**
	* 描述:获取ehcache缓存管理器
	* @Title: getEhcacheManager
	* @return
	* @author weiyongpeng
	* @date  2022年10月4日 上午9:12:36
	 */
	@Bean
	public EhCacheManager getEhCacheManager() {
		EhCacheManager ehCacheManager = new EhCacheManager();
		InputStream stream = null;
		try {
			stream = ResourceUtils.getInputStreamForPath("classpath:ehcache/shiro-ehcache.xml");
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		CacheManager cacheManager = new CacheManager(stream);
		ehCacheManager.setCacheManager(cacheManager);
		return ehCacheManager;
	}

请添加图片描述

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Shiro 是一个强大易用的Java安全框架,可以帮助我们实现身份认证、授权、会话管理等安全相关功能。在 Spring Boot + Vue 商城中,我们可以使用 Shiro 框架来实现用户账户注册功能。 下面是一些实现步骤: 1. 添加 Shiro 相关依赖 在 pom.xml 文件中添加 Shiro 相关依赖: ``` <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.7.1</version> </dependency> ``` 2. 配置 Shiro 在 application.yml 文件中添加 Shiro 的配置: ``` # Shiro 配置 shiro: # 登录 URL login-url: /login # 登录成功 URL success-url: / # 未授权 URL unauthorized-url: /unauthorized # 过滤器链定义 filter-chain-definition-map: /login: anon /register: anon /logout: logout /static/**: anon /**: authc ``` 其中,filter-chain-definition-map 定义了 URL 的访问权限,anon 表示该 URL 可以匿名访问,authc 表示该 URL 需要登录后才能访问。 3. 实现用户注册功能 在用户注册的 Controller 中,我们可以使用 Shiro 的 Subject 对象来进行身份认证和授权操作。具体代码如下: ``` @PostMapping("/register") public Result register(@RequestBody User user) { // 创建用户信息 User newUser = new User(); newUser.setUsername(user.getUsername()); newUser.setPassword(user.getPassword()); newUser.setRoles("user"); // 对用户密码进行加密 String salt = UUID.randomUUID().toString(); String passwordHash = new SimpleHash("md5", newUser.getPassword(), salt, 2).toHex(); newUser.setPassword(passwordHash); newUser.setSalt(salt); // 保存用户信息 userRepository.save(newUser); // 登录用户 UsernamePasswordToken token = new UsernamePasswordToken(newUser.getUsername(), newUser.getPassword()); Subject subject = SecurityUtils.getSubject(); subject.login(token); // 返回结果 return Result.success("注册成功"); } ``` 在上述代码中,我们通过 Shiro 的 Subject 对象进行了用户登录操作,登录成功后,用户就可以访问需要登录才能访问的 URL 了。 以上就是使用 Shiro 框架Spring Boot + Vue 商城中实现用户账户注册的步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@WAT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值