2.Shiro框架(1)---认证

有道云:有道云笔记

一. 认证与授权

在学习 Shiro 框架之前,我们先来了解一下什么是认证与授权。

1.认证就是校验身份的正确性,看一下你是不是这个平台的用户。系统一般会将用户输入的账号密码和数据库的信息做比对,从而判断身份是否合法。

2.授权就是确定你是否有权访问系统的某些资源,比如查看某个页面、点击某个按钮、操作某一行数据等。

二.Shiro

Shiro 是 Apache 基金会下面一个开源的 java 权限管理框架,它可以进行身份验证、授权、密码和会话管理。

Apache Shiro | Simple. Java. Security.【官网】

【Shiro 核心架构】

0

对于上面复杂的架构,我们只需要记住以下几个核心模块:

1.Subject

主体,你可以理解为访问系统的用户。

2.SecurityManager

安全管理器。用户进行认证和授权都是通过 securityManager 进行,你可以理解为 shiro 的老大。

3.authenticator

认证器,用户通过 authenticator 进行认证。

4.authorizer

授权器,用户通过 authorizer 进行授权。

5.realm

领域,相当于数据源。

在 realm 中,我们通过查询数据库的信息,然后对用户进行认证和授权。

所以 authenticator 和 authorizer 其实是调用了 realm 中 认证和授权的方法。

6.cryptography

密码管理。shiro 提供了一套加密和解密的组

三.Springboot 整合 Shiro

1.新建 springboot 项目

0

0

2.引入依赖

这里我们主要引入了 web、mybatis-plus、shiro、mysql 的依赖

<!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency> <!--引入 shiro 整合 Springboot 依赖--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.5.3</version> </dependency> <!--数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>

3.修改配置文件

server.servlet.context-path=/shiro server.port=8080 spring.mvc.view.suffix=.jsp spring.mvc.view.prefix=/ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql:///shiroDB spring.datasource.username=root spring.datasource.password=sasa mybatis-plus.mapper-locations=classpath*:com/lfz/mapper/*Mapper.xml mybatis-plus.type-aliases-package=com.lfz.shirodemo.entity mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

4.创建用户表

CREATE TABLE `t_user` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'id', `name` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '姓名', `username` VARCHAR(20) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '账号', `password` VARCHAR(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '密码', `salt` VARCHAR(20) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '盐', `created_date` DATETIME DEFAULT NULL COMMENT '创建时间', `updated_date` DATETIME DEFAULT NULL, `is_deleted` INT DEFAULT '0', PRIMARY KEY (`id`) USING BTREE ) ENGINE=INNODB AUTO_INCREMENT=1500052815584731139 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;

5.使用 mybatis-plus 生成实体类、service、dao、mapper文件

四、Shiro认证

1.文件数据源:因为 shiro 的默认数据源是配置文件数据源,所以我们先用配置文件的方式模拟数据库信息。

配置文件数据源

0

package com.lfz.shirodemo; 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.mgt.DefaultSecurityManager; import org.apache.shiro.realm.text.IniRealm; import org.apache.shiro.subject.Subject; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class ShiroDemoApplicationTests { @Test void contextLoads() { // 1.创建安全管理器 DefaultSecurityManager securityManager = new DefaultSecurityManager(); // 2.设置 realm securityManager.setRealm(new IniRealm("classpath:shiro.ini")); // 3.设置安全管理器 SecurityUtils.setSecurityManager(securityManager); // 4.获取当前主体对象 Subject subject = SecurityUtils.getSubject(); // 5.将用户登录时的账号和密码封账成 UsernamePasswordToken 对象 UsernamePasswordToken token = new UsernamePasswordToken("admin", "123456"); try { System.out.println("认证前状态:" + subject.isAuthenticated()); subject.login(token); System.out.println("认证后状态:" + subject.isAuthenticated()); } catch (UnknownAccountException e) { System.out.println("用户不存在"); } catch (IncorrectCredentialsException e) { System.out.println("账号或密码不正确"); } } }

认证结果:

0

0

0

认证流程讲解:

1,你需要创建安全管理器 SecurityManager

2,为安全管理器设置数据源 Realm,Realm 的数据源默认是以配置文件的方式进行配置。

3,用户在前端输入账号和密码,shiro 框架获取当前要登录的主体(用户)对象Subject,然后将账号和密码封装成 UsernamePasswordToken 对象。

4,安全管理器通知数据源Realm 认证一下当前用户,Realm 将用户输入的账号密码信息和配置文件中的信息做比对,比对通过则认证成功,比对不通过则返回错误信息。

.2.【数据库数据】

使用数据库作为数据源需要我们自定义 Realm,其中自定义的 Realm 需要继承 AuthorizingRealm 类并实现两个方法。

0

并实现认证方法:

package com.lfz.shirodemo.shiro.realm; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.lfz.shirodemo.entity.User; import com.lfz.shirodemo.service.impl.UserService; import lombok.AllArgsConstructor; import lombok.Data; 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.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; /** *使用数据库作为数据源 */ @AllArgsConstructor public class MyRealm extends AuthorizingRealm { UserService userService; /** * 授权 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } /** * 认证 * @param authenticationToken * @return */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //用户登录名称 String principal = (String) authenticationToken.getPrincipal(); User user = userService.getOne(new QueryWrapper<User>().eq("username", principal)); //用户不为空 if (null != user) { //1.账号 2.密码 3.当前 realm 名字 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),this.getName()); return simpleAuthenticationInfo; } return null; } }

测试:修改安全管理器的 Realm 为自定义的 Realm:

0

测试结果:

0

数据库添加用户信息:

0

重新测试:

0

讲解:

用户登录的账号和密码被封装成了 token 对象

调用 subject.login() 方法之后,自定义 Realm 的认证方法会接收传递的 token 信息。

其中 UsernamePasswordToken 是 AuthenticationToken 接口的实现类

0

0

getPrincipal() 方法返回的是用户的账号信息,

getCredentials() 返回的是密码信息。

0

将用户填写的账号和密码与数据库中的信息做比对,比对通过则返回 SimpleAuthenticationInfo 对象,其中需要传递三个参数

1.数据库中的账号

2.数据库中的密码

3.当前的 realm 对象

0

3.Shiro 认证完整案例

这里我们采用 shiro 配置类和登录接口的方式来实现 shiro 的认证。

0

1.在 UserController 中新建登录接口:

package com.lfz.shirodemo.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.subject.Subject; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.stereotype.Controller; /** * <p> * 前端控制器 * </p> * * @author 悟道 */ @Controller @RequestMapping("/user") public class UserController { @RequestMapping("/login") public String login(String username, String password) { try { //获取主体对象 Subject subject = SecurityUtils.getSubject(); subject.login(new UsernamePasswordToken(username, password)); return "redirect:/index.jsp"; } catch (UnknownAccountException e) { System.out.println("用户不存在"); } catch (IncorrectCredentialsException e) { System.out.println("账号或者密码错误"); } catch (Exception e) { System.out.println("服务出错"); } return "redirect:/login.jsp"; } }

2.新建自定义 Realm,实现认证方法。【上一个案例中已经实现完成,这里我们重写写一个】

package com.lfz.shirodemo.shiro.realm; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.lfz.shirodemo.entity.User; import com.lfz.shirodemo.service.impl.UserService; 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.realm.AuthorizingRealm; import org.apache.shiro.realm.Realm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; public class CustomerRealm extends AuthorizingRealm { @Autowired private UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //用户登录名称 String principal = (String) authenticationToken.getPrincipal(); User user = userService.getOne(new QueryWrapper<User>().eq("username", principal)); //用户不为空 if (null != user) { //1.账号 2.密码 3.当前 realm 名字 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),this.getName()); return simpleAuthenticationInfo; } return null; } }

3.新建 shiro 的配置类

shiro 的配置类主要包含三个 bean:

  • shiroFilter:用来拦截所有请求
  • SecurityManager:安全管理器
  • Realm:自定义 realm

完整配置类:

package com.lfz.shirodemo.shiro.config; import com.lfz.shirodemo.shiro.realm.CustomerRealm; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.util.ThreadContext; 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; @Configuration public class ShiroConfig { /** *1.shiroFilter:负责拦截所有请求 */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManage") DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 设置安全管理器 shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); // 默认跳转页面---认证不通过时跳转 shiroFilterFactoryBean.setLoginUrl("/login.jsp"); return shiroFilterFactoryBean; } /** * 2.创建安全管理器 */ @Bean(name = "SecurityManage") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("customerRealm")Realm realm) { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); ThreadContext.bind(defaultWebSecurityManager); defaultWebSecurityManager.setRealm(realm); return defaultWebSecurityManager; } /** * 创建自定义 realm */ @Bean(name="customerRealm") public Realm getRealm() { CustomerRealm customerRealm = new CustomerRealm(); return customerRealm; } }

4.发起登录请求

0

0

0

登录成功并跳转到首页

0

测试错误密码登录

0

0

登录失败,并跳转至登录页面

0

4.密码加密

我们经常使用 MD5 进行加密,MD5 加密是一种不可逆的加密算法。使用 MD5 加密后的结果是一个 32 位的字符串。

使用 MD5 加密需要三个参数:

  • 密码
  • 随机盐:其实就是一串复杂的字符串,可以使加密后的密码更复杂
  • 散列次数:一般是 1024

0

在 Shiro 中,需要修改自定义 Realm 来完成 MD5 的加密解密:

0

0

@Bean(name="customerRealm") public Realm getRealm() { CustomerRealm customerRealm = new CustomerRealm(); //1.创建 hashed 的凭证匹配器 HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher(); //2.设置 md5 加密算法 credentialsMatcher.setHashAlgorithmName("md5"); //3.设置散列次数 credentialsMatcher.setHashIterations(1024); //4.设置 hashed 的凭证匹配器 customerRealm.setCredentialsMatcher(credentialsMatcher); return customerRealm; }

案例:

1.随机盐工具类

0

package com.lfz.shirodemo.shiro.util; import java.util.Random; public class SaltUtils { public static String getSalt(int n) { String str = "01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz(#$^@%&*!)"; char[] chars = str.toCharArray(); StringBuilder randomSalt = new StringBuilder(); Random random = new Random(); for (int i = 0; i < n; i++) { int number = random.nextInt(chars.length); char aChar = chars[number]; randomSalt.append(aChar); } return randomSalt.toString(); } }

2.新增测试用户信息

0

0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值