这个小项目包含了注册与登录,使用了springboot+mybatis+shiro的技术栈;当用户在浏览器登录时发起请求时,首先这一系列的请求会被拦截器进行拦截(ShiroFilter),然后拦截器根据用户名去数据库寻找是否有相对应的user实体;如果有则返回封装到User类中(没有就用户名错误),然后比对密码是否一致;如果都通过了则认证成功;登录到主页面;然后主页面有不同的功能,不同的用户拥有不同的权限,有的能看到,有的则无法看到;然后如果不登陆直接访问主页面,会被强制跳转到登录页面;另外登录之后也可以退出登录;

通过分析上文:https://blog.csdn.net/Kevinnsm/article/details/11187650

1.首先快速创建一个springboot项目(使用spring向导)

<?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.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hao</groupId>
<artifactId>springboot-shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-shiro</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.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-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> <dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
</dependencies>
<!--########################################################-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build> </project>

2.数据库建表

这里的salt字段主要是为了使用md5+salt+散列加密时,需要保存salt字符串,以便在业务层比较密码正确与否时一致

3.注册、登录和主页面

<%@page contentType="text/html; utf-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>用户注册</h1>
<form action="${pageContext.request.contextPath}/user/register" method="post">
username:<input type="text" name="username"><br>
password:<input type="password" name="password"><br>
<input type="submit" value="注册">
</form>
</body>
</html>
<%@page contentType="text/html; utf-8" pageEncoding="UTF-8" isELIgnored="false" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>用户登录</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">
username:<input type="text" name="username"><br>
password:<input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
</body>
</html>
<%@page contentType="text/html; utf-8" pageEncoding="UTF-8" isELIgnored="false" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>系统主页,欢迎你的到来</h1>
<a href="${pageContext.request.contextPath}/user/outLogin">退出</a> <ul>
<shiro:hasAnyRoles name="user">
<li><a href="">用户管理</a>
<ul>
<shiro:hasPermission name="user:add:*">
<li><a href="">添加用户</a></li>
</shiro:hasPermission>
<li><a href="">删除用户</a></li>
<li><a href="">修改用户</a></li>
<li><a href="">查询用户</a></li>
</ul>
</li>
</shiro:hasAnyRoles>
<shiro:hasRole name="admin">
<li><a href="">商品管理</a> </li>
<li><a href="">订单管理</a> </li>
<li><a href="">物流管理</a> </li>
</shiro:hasRole>
<shiro:hasRole name="shper">
<li><a href="">终极格式化</a> </li>
</shiro:hasRole>
</ul>
</body>
</html>

4.配置文件

spring.application.name=shiro
server.servlet.context-path=/shiro spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=hao20001010 mybatis.type-aliases-package=com.hao.springboot.entity
mybatis.mapper-locations=classpath:mapper/*.xml logging.level.com.hao.springboot.dao=debug

5.编写mapper.xml映射文件

<?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.hao.springboot.dao.UserDao">
<insert id="save" parameterType="User">
insert into t_user values(#{id},#{username},#{password},#{salt})
</insert> <select id="findByUserName" resultType="User">
select * from t_user where username=#{username}
</select>
</mapper>

6.DAO层

@Repository
public interface UserDao { void save(User user); User findByUserName(String username);
}

7.service层

public interface UserService {
void register(User user); User findByUserName(String username);
}
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService { @Autowired
UserDao userDao; @Override
public void register(User user) {
//明文密码进行md5+salt+随机散列
//1.生成随机盐
String salt = SaltUtil.getSalt(8);
//2.将随机盐保存到数据库中
user.setSalt(salt);
Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
user.setPassword(md5Hash.toHex());
userDao.save(user);
} @Override
public User findByUserName(String username) {
return userDao.findByUserName(username);
}
}

8.controller层

/**
* @author:抱着鱼睡觉的喵喵
* @date:2020/12/29
* @description:
*/
@Controller
@RequestMapping("/user")
public class UserController { @Autowired
UserService userService;
/**
* 处理身份认证
* @param username
* @param password
* @return
*/
@RequestMapping("/login")
public String login(@RequestParam("username")String username,@RequestParam("password")String password){ //获取主体对象
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username,password));
return "redirect:/index.jsp";
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名错误");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
return "redirect:/login.jsp";
} /**
* 退出用户
* @return
*/
@RequestMapping("/outLogin")
public String outLogin(){
Subject subject = SecurityUtils.getSubject();
subject.logout(); //退出用户
return "redirect:/login.jsp";
} /**
* 用户注册
* @return
*/
@RequestMapping("/register")
public String register(User user){
try {
userService.register(user);
return "redirect:/login.jsp";
}catch (Exception e){
e.printStackTrace();
return "redirect:/register.jsp";
}
}
}

9.User实体类

@Data
@ToString
@AllArgsConstructor
@Accessors(chain = true)
@NoArgsConstructor
public class User { private String id;
private String username;
private String password;
private String salt; }

10.创建ShiroFilter拦截器(拦截所有请求)

import com.hao.springboot.shiro.realm.CustomerRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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; import java.util.HashMap;
import java.util.Map; /**
* @author:抱着鱼睡觉的喵喵
* @date:2020/12/29
* @description:
*/
@Configuration
public class ShiroConfig {
/**
* 1.创建ShiroFilter
* 负责拦截所有请求
* @return shiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactory(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); //配置系统首先资源
//配置系统公共资源
Map<String,String> map=new HashMap<>();
map.put("/user/login","anon"); //设置为公共资源,防止登录死循环
map.put("/user/register","anon");
map.put("/register.jsp","anon");
map.put("/**","authc"); //authc 这个资源需要认证和授权
shiroFilterFactoryBean.setFilterChainDefinitionMap(map); //默认界面路径
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
return shiroFilterFactoryBean;
}
/**
* 安全管理器
* @return defaultWebSecurityManager
*/
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
} /**
*
* @return
*/
@Bean("realm")
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
//修改凭证校验匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法为md5
credentialsMatcher.setHashAlgorithmName("MD5");
//设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
return customerRealm;
}
}

11.自定义realm

package com.hao.springboot.shiro.realm;

import com.hao.springboot.entity.User;
import com.hao.springboot.service.UserService;
import com.hao.springboot.utils.ApplicationContextUtils;
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.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.util.ObjectUtils; import java.util.Arrays; /**
* @author:抱着鱼睡觉的喵喵
* @date:2020/12/29
* @description: 自定义realm完成用户认证和授权
*/
public class CustomerRealm extends AuthorizingRealm {
/**
* 用户授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("调用权限认证:"+principalCollection);
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
if ("Ronin".equals(primaryPrincipal)){
simpleAuthorizationInfo.addRoles(Arrays.asList("user","admin","shaper"));
simpleAuthorizationInfo.addStringPermission("user:add:*");
return simpleAuthorizationInfo;
}
// else if("tom".equals(primaryPrincipal)){
// simpleAuthorizationInfo.addRole("admin");
// return simpleAuthorizationInfo;
// }
return null;
} /**
* 用户认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal(); //在工厂中获取业务对象
UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
User user = userService.findByUserName(principal);
if (!ObjectUtils.isEmpty(user)){
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());
}
return null;
}
}

12.编写salt(因为要使用md5+salt+散列对密码加密)

package com.hao.springboot.utils;

import java.util.Random;

/**
* @author:抱着鱼睡觉的喵喵
* @date:2020/12/29
* @description:
*/
public class SaltUtil {
/**
* 生成salt的静态方法
* @param n
* @return
*/
public static String getSalt(int n){
char[] chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()".toCharArray();
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < n; i++) {
char aChar = chars[new Random().nextInt(chars.length)];
stringBuilder.append(aChar);
}
return stringBuilder.toString();
}
//测试一下
public static void main(String[] args) {
System.out.println(getSalt(8));
}
}

13.我们想要在自定义realm中的doGetAuthenticationInfo()方法调用service层,所以需要
使用静态类获取

package com.hao.springboot.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component; /**
* @author:抱着鱼睡觉的喵喵
* @date:2020/12/29
* @description:
*/
@Component
public class ApplicationContextUtils implements ApplicationContextAware { private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context=applicationContext;
}
//
public static Object getBean(String beanName){
return context.getBean(beanName);
}
}

14,测试

访问aaaa

强制到登录页面

以下是事先的数据


输入Ronin && 123登录

可以看到Ronin用户能够看所有的权限信息
接着点击退出
输入tom && 123

点击登录

可以发现tom用户什么都看不到,因为没有赋给tom用户任何权限


使用代码控制角色权限

@Controller
@RequestMapping("/limit")
public class LimitController { @RequestMapping("/save")
@RequiresRoles(value = {"admin","user"})
@RequiresPermissions("user:update:*")
public String save(){
// Subject subject = SecurityUtils.getSubject();
// if (subject.hasRole("admin")) {
// System.out.println("保存订单!");
// }else {
// System.out.println("无权访问");
// }
System.out.println("进入方法");
return "redirect:/index.jsp";
}
}

我从这个小案例中我学到了什么
1.使用spring框架,所有的bean都是通过容器管理的;想要使用某个组件直接从容器中拿就行了,比如service注入到controller即可;本案例中的sevice层需要被自定义的realm调用,进行密码认证,但是怎么调用就成了问题,不可能new一个吧,所有bean对象都是通过IOC容器管理的;所以那怎么办呢?我们不能直接注入service,因为bean的实例化在filter之后,因为,能力有限,以后有能力再深究吧!(Autowired底层待分析)
所以只有使用静态类,将该静态类加入容器中(使用@Component注解)

@Component
public class ApplicationContextUtils implements ApplicationContextAware { private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context=applicationContext;
}
//
public static Object getBean(String beanName){
return context.getBean(beanName);
}
}

2.复习了ApplicationContext和BeanFactory的区别
3.shiro与springboot的整合关键在于ShiroFilter拦截器的使用
4.对于解析源码的渴望(奈何实力差太多)



以上内容只是为了记录自己的学习过程,理清自己的思路,以便以后能力更强时,不断对其补充和更改;

Shiro+springboot+mybatis(md5+salt+散列)认证与授权-01的更多相关文章

  1. Shiro+springboot+mybatis(md5+salt+散列)认证与授权-02

    代码延续地址:Shiro+springboot+mybatis(md5+salt+散列)认证与授权-01 1.创建t_role角色表(比如管理员admin,普通用户user等),创建t_pers权限表 ...

  2. Shiro+springboot+mybatis+EhCache(md5+salt+散列)认证与授权-03

    从上文:Shiro+springboot+mybatis(md5+salt+散列)认证与授权-02 当每次进行刷新时,都会从数据库重新查询数据进行授权操作,这样无疑给数据库造成很大的压力,所以需要引入 ...

  3. SpringBoot整合Shiro+MD5+Salt+Redis实现认证和动态权限管理(上)----筑基中期

    写在前面 通过前几篇文章的学习,我们从大体上了解了shiro关于认证和授权方面的应用.在接下来的文章当中,我将通过一个demo,带领大家搭建一个SpringBoot整合Shiro的一个项目开发脚手架, ...

  4. SpringBoot整合Shiro+MD5+Salt+Redis实现认证和动态权限管理|前后端分离(下)----筑基后期

    写在前面 在上一篇文章<SpringBoot整合Shiro+MD5+Salt+Redis实现认证和动态权限管理(上)----筑基中期>当中,我们初步实现了SpringBoot整合Shiro ...

  5. MD5随机散列加密算法

    项目中需要在登录验证用户名.密码的时候对密码进行加密处理,由于是比较商业化的软件,所以安全方面还是必须要考虑的.而使用MD5随机散列加密算法使得密码加密后不可逆,很大程度上提升了安全性.废话不多说,看 ...

  6. 加密算法和MD5等散列算法的区别(转)

    本文转自http://www.cnblogs.com/eternalwt/archive/2013/03/21/2973807.html 感谢作者 1.在软件开发的用户注册功能中常出现MD5加密这个概 ...

  7. SpringBoot + SpringSecurity + Mybatis-Plus + JWT实现分布式系统认证和授权

    1. 简介   Spring Security是一个功能强大且易于扩展的安全框架,主要用于为Java程序提供用户认证(Authentication)和用户授权(Authorization)功能.    ...

  8. shiro+springboot分析思路

    文章目录 前言 一.为什么要使用shiro 二.使用步骤 1.如何认证和授权 2.如何获取数据 总结 前言 shiro和spring security等安全框架可以用户管理和权限认证 一.为什么要使用 ...

  9. SpringBoot + SpringSecurity + Mybatis-Plus + JWT + Redis 实现分布式系统认证和授权(刷新Token和Token黑名单)

    1. 前提   本文在基于SpringBoot整合SpringSecurity实现JWT的前提中添加刷新Token以及添加Token黑名单.在浏览之前,请查看博客:   SpringBoot + Sp ...

随机推荐

  1. MYSQL安装后自带用户的作用

    user表中host列的值的意义%                   匹配所有主机localhost      localhost不会被解析成IP地址,直接通过UNIXsocket连接127.0.0 ...

  2. Mysql-关系型数据库与非关系型数据库

    一.什么是数据库 数据库是数据的仓库. 与普通的"数据仓库"不同的是,数据库依据"数据结构"来组织数据,因为"数据结构",所以我们看到的数据 ...

  3. 【死磕NIO】— 跨进程文件锁:FileLock

    大家好,我是大明哥,一个专注于[死磕 Java]系列创作的程序员. [死磕 Java ]系列为作者「chenssy」 倾情打造的 Java 系列文章,深入分析 Java 相关技术核心原理及源码 死磕 ...

  4. pyhon反射

    一:反射 1.python面向对象中的反射: 通过字符串的形式操作对象相关的属性.python中的一切事物都是对象(都可以使用反射) 2.四个内置方法 hasattr 检测是否含有某属性 getatt ...

  5. T12焊台控制器制作教程 | T12烙铁 | PID增量式算法恒温控制 | 运算放大器-热电偶电压采集 | OLED屏幕显示-SPI通信 | 旋转编码器EC11用户操作

    前言 购买T12烙铁的相关配件已经1年多了,期间也尝试了一些开源的T12控制器,但都没有成功,要么是配套资料少,要么是英文的,其中51和arduino的居多,STM32的较少.求人不如求己,索性自己开 ...

  6. 从零开始,开发一个 Web Office 套件(13):删除、替换已选中文字

    这是一个系列博客,最终目的是要做一个基于 HTML Canvas 的.类似于微软 Office 的 Web Office 套件(包括:文档.表格.幻灯片--等等). 博客园:<从零开始, 开发一 ...

  7. JavaScript 的诞生历史

    看到一篇介绍JS诞生历史的文章,很有意思,文章里描述了很多的历史细节 https://webdevelopmenthistory.com/1995-the-birth-of-javascript/

  8. JAVA 用命令提示符执行java找不到或者无法加载主类

    使用cmd编译执行java文件时候,报错---找不到或者无法加载主类如下图 把红色部分去掉,再次编译执行如下解决问题 ,执行成功!!!!!! 2.当我们在eclipes中执行运行的时候 ggggggg ...

  9. win10关于后缀名无法关联相应程序默认打开方式的处理方法

    系统:win10 专业版 现象:以".chm"文件为例,每次都要重新选择一次打开方式才能打开chm文件,无法设置默认打开方式. 解决方法: 1.win+r打开运行,输入regedi ...

  10. Hibernate的一级缓存和二级缓存有什么区别?

    第一级缓存由Session实例维护,它是必选的,其中保持了Session当前所有关联实体的数据,也称为内部缓存.而第二级缓存则存在于SessionFactory层次,它是可选的.一级缓存只能为当前线程 ...