Spring Boot学习篇(九)
shiro安全框架使用篇(一)
1.shiro初始了解
Apache Shiro 是一个强大易用的 Java 安全框架,提供了认证、授权、加密和会话管理等功能:
①认证 - 用户身份识别,常被称为用户“登录”;
②授权 - 访问控制;
③密码加密 - 保护或隐藏数据防止被偷窥;
④会话管理 - 每用户相关的时间敏感的状态。
对于任何一个应用程序,Shiro 都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro 要简单的多。
2.使用shiro实现简单的登录
2.1 准备工作
2.1.1 导入依赖
a shiro相关依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.9.1</version>
</dependency>
b 完整版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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>boot-shiro</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.7.2</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.1</version>
</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>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.9.1</version>
</dependency>
</dependencies>
</project>
2.1.2 在java文件夹创建com包.zlz包,并创建ShiroStart类(springboot项目的启动类),其内容如下所示
package com.zlz;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
@SpringBootApplication
public class ShiroStart {
public static void main(String[] args) {
SpringApplication.run(ShiroStart.class);
}
}
2.1.3 在数据库里面运行如下的sql语句(建表和插入数据)
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
`roleid` int NOT NULL DEFAULT 0,
`perid` int NOT NULL DEFAULT 0,
PRIMARY KEY (`roleid`, `perid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
INSERT INTO `sys_role_permission` VALUES (1, 9);
INSERT INTO `sys_role_permission` VALUES (2, 1);
INSERT INTO `sys_role_permission` VALUES (2, 2);
INSERT INTO `sys_role_permission` VALUES (2, 3);
INSERT INTO `sys_role_permission` VALUES (2, 4);
INSERT INTO `sys_role_permission` VALUES (2, 5);
INSERT INTO `sys_role_permission` VALUES (2, 6);
INSERT INTO `sys_role_permission` VALUES (2, 7);
INSERT INTO `sys_role_permission` VALUES (2, 8);
INSERT INTO `sys_role_permission` VALUES (3, 5);
INSERT INTO `sys_role_permission` VALUES (3, 6);
INSERT INTO `sys_role_permission` VALUES (3, 7);
INSERT INTO `sys_role_permission` VALUES (3, 8);
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` int NOT NULL,
`username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`salt` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
INSERT INTO `sys_user` VALUES (1, 'admin', '52663b302f9af774ed8f72186e750549', 'q1');
INSERT INTO `sys_user` VALUES (2, 'aaa', '5cae9116958db501061ac45449bdebe6', 'q2');
INSERT INTO `sys_user` VALUES (3, 'bbb', '46078596764bcea6cf58d1c770a26689', 'q3');
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` int NOT NULL,
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
INSERT INTO `sys_role` VALUES (1, '经理');
INSERT INTO `sys_role` VALUES (2, '组长');
INSERT INTO `sys_role` VALUES (3, '组员');
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`id` int NOT NULL,
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
INSERT INTO `sys_permission` VALUES (1, '添加商品');
INSERT INTO `sys_permission` VALUES (2, '删除商品');
INSERT INTO `sys_permission` VALUES (3, '修改商品');
INSERT INTO `sys_permission` VALUES (4, '查询商品');
INSERT INTO `sys_permission` VALUES (5, '添加订单');
INSERT INTO `sys_permission` VALUES (6, '删除订单');
INSERT INTO `sys_permission` VALUES (7, '修改订单');
INSERT INTO `sys_permission` VALUES (8, '查询订单');
INSERT INTO `sys_permission` VALUES (9, '*');
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`userid` int NOT NULL DEFAULT 0,
`roleid` int NOT NULL DEFAULT 0,
PRIMARY KEY (`userid`, `roleid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
INSERT INTO `sys_user_role` VALUES (1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2);
INSERT INTO `sys_user_role` VALUES (3, 3);
2.1.4 运行完成后数据库里应该有五张表
2.1.5 在sys_user表中将密码改成如下图所示的数据
2.1.6 在sys_user表增加锁定字段suo(数字0代表正常用户,数字1代表该用户被锁定了)
2.1.7 在zlz包下创建好代码生成器MyGenerator类并运行该类
package com.zlz;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import java.util.Collections;
public class MyGenerator {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3305/db0618", "root", "root")
.globalConfig(builder -> {
builder.author("zlz")
.dateType(DateType.ONLY_DATE)
.outputDir("F:\\boot\\boot-shiro\\src\\main\\java");
})
.packageConfig(builder -> {
builder.parent("com.zlz")
.pathInfo(Collections.singletonMap(OutputFile.xml, "F:\\boot\\boot-shiro\\src\\main\\resources\\mapper"));
})
.strategyConfig(builder -> {
builder.entityBuilder().enableLombok();
builder.addInclude("sys_user");
})
.execute();
}
}
2.1.8 变更实体类SysUser类的内容
package com.zlz.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import lombok.*;
@AllArgsConstructor
@NoArgsConstructor
@Data
@TableName("sys_user")
public class SysUser implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO )
private Integer id;
private String username;
private String password;
private String salt;
private Integer suo;
}
2.1.9 在zlz包下的config包下创建Mybatis配置类PlusConfig
package com.zlz.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.zlz.mapper")
public class PlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor pi = new PaginationInnerInterceptor();
pi.setDbType(DbType.MYSQL);
pi.setOverflow(true);
interceptor.addInnerInterceptor(pi);
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
2.1.10 配置application.yml文件,其内容如下所示
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource #连接池类型
url: jdbc:mysql://127.0.0.1:3305/db0618
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
thymeleaf:
cache: false #开发阶段关闭缓存 上线后 开启
mybatis-plus:
type-aliases-package: com.zlz.entity #实体别名扫描
#配置日志 查看具体的sql执行语句
logging:
level:
com.zlz.mapper: debug
2.2 在SysUserMapper接口中写findUser方法,对应的代码如下所示
package com.zlz.mapper;
import com.zlz.entity.SysUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
public interface SysUserMapper extends BaseMapper<SysUser> {
SysUser findUser(String username);
}
2.3 在SysUserMapper.xml写findUser方法所对应的SELECT标签(自定义sql)
<?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">
<mapper namespace="com.zlz.mapper.SysUserMapper">
<select id="findUser" resultType="sysUser">
select * from sys_user where username=#{username}
</select>
</mapper>
2.4 在zlz包下的config包下创建域(认证、授权需要调用的dao类)
package com.zlz.config;
import com.zlz.entity.SysUser;
import com.zlz.mapper.SysUserMapper;
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.springframework.beans.factory.annotation.Autowired;
public class MysqlRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection p) {
return null;
}
@Autowired
SysUserMapper userMapper;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken a) throws AuthenticationException {
String username = (String) a.getPrincipal();
SysUser user = userMapper.findUser(username);
System.out.println(username+"123");
if (user == null) {
throw new UnknownAccountException();
}
if(user.getSuo()==1){
throw new LockedAccountException();
}
SimpleAuthenticationInfo sai=new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),getName());
return sai;
}
}
2.5 在config下创建Shiro配置类ShiroConfig,其内容如下所示
package com.zlz.config;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfig {
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager dws = new DefaultWebSecurityManager();
dws.setRealm(MyRealm());
dws.setSessionManager(new DefaultWebSessionManager());
return dws;
}
@Bean
public Realm MyRealm(){
MysqlRealm mysqlRealm = new MysqlRealm();
return mysqlRealm;
}
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean factoryBean(){
ShiroFilterFactoryBean sff=new ShiroFilterFactoryBean();
sff.setSecurityManager(securityManager());
sff.setLoginUrl("");
sff.setUnauthorizedUrl("");
return sff;
}
}
2.6 在controll包下的SysUserController配置登录流程的第一步和最后一步(保存对象和显示当前状态)
package com.zlz.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@RequestMapping("/users")
public class SysUserController {
@RequestMapping("login")
public String login(String username, String password, RedirectAttributes ra){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try {
subject.login(token);
return "index";
} catch (UnknownAccountException e) {
ra.addFlashAttribute("msg", "用户名不存在");
}catch (IncorrectCredentialsException e) {
ra.addFlashAttribute("msg", "密码输入错误");
}catch (LockedAccountException e) {
ra.addFlashAttribute("msg", "账户已被锁定");
}
return "redirect:/";
}
}
2.7 配置WelcomeController(默认显示页面以及页面的跳转)
package com.zlz.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
@Controller
public class WelcomeController {
@RequestMapping("/")
public String welcome(){
return "login";
}
@RequestMapping("tohtml")
public String tohtml(HttpServletRequest request){
return (String)request.getAttribute("pageName");
}
}
2.8 测试
2.8.1 当账户有误时
a 点击登录按钮前
b 点击登录按钮后
2.8.2 当账户被锁定时
a 点击登录按钮前
b 点击登录按钮后
2.8.3 当密码输入有误时(该用户并没有被锁定)
a 点击登录按钮前
b 点击登录按钮后
2.8.4 当账户密码输入均正确时
a 点击登录按钮前
b 点击登录按钮后