SpringBoot整合SpringSecurity实现一个简单的认证与授权应用

news2024/11/24 13:51:54

1、SpringSecurity 的简介

Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架,它是 Spring 项目组中用来提供安全认证服务的框架,能够为基于 Sprin g的企业应用系统提供声明式的安全访问控制解决方案。

Spring Security 的前身是 Acegi Security。它是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。Spring Security 采用了 AOP(面向切面编程)思想,并基于 Servlet 过滤器实现。

下面将介绍 Spring Boot 整合 Spring Security 实现一个简单的认证与授权应用,执行结果如下如:

(1)登录页面

(2)登录成功后,跳转至首页:

2、数据库准备

使用 MySQL 数据库,设计一个自定义的数据表结构,并添加数据。

-- 创建数据库
CREATE DATABASE IF NOT EXISTS db_admin; 

USE db_admin;


-- 创建自定义的用户信息表
DROP TABLE IF EXISTS user_info;

CREATE TABLE user_info(
    id BIGINT(20) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
    username VARCHAR(50) NOT NULL COMMENT '用户名称',
    PASSWORD VARCHAR(60) COMMENT '用户密码',
    ENABLE TINYINT(4) NOT NULL DEFAULT 1 COMMENT '是否启用',
    roles VARCHAR(100) COMMENT '用户角色,多个角色之间用英文逗号分割',
    KEY key_username (username)
);

-- 插入数据
INSERT INTO user_info(username,PASSWORD,ENABLE,roles) VALUES('admin','123456',1,'ROLE_ADMIN,ROLE_USER');
INSERT INTO user_info(username,PASSWORD,ENABLE,roles) VALUES('user','123456',1,'ROLE_USER');
INSERT INTO user_info(username,PASSWORD,ENABLE,roles) VALUES('panjunbiao','123456',1,'ROLE_USER');

-- 查询
SELECT * FROM user_info;

3、创建项目

【示例】SpringBoot 整合 SpringSecurity 创建一个简单的认证应用。

3.1 创建 Spring Boot 项目

创建 SpringBoot 项目,项目结构如下图:

3.1 添加 Maven 依赖

在 pom.xml 配置文件中添加 Spring Security、MyBatis、JDBC、Thymeleaf 模板引擎、Lombok 依赖。

<!-- Spring Security 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.7.18</version>
</dependency>

<!-- MyBatis 与 Spring Boot 整合依赖 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>

<!-- MySQL 的 JDBC 数据库驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- Lombok 依赖 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

<!-- 引入Thymeleaf模板引擎 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

3.3 配置数据库连接参数

在项目的 application.yml 文件中,添加数据库连接的配置。

spring:
  # 使用Thymeleaf模板引擎
  thymeleaf:
    mode: HTML5
    encoding: UTF-8
    cache: false
    servlet:
      content-type: text/html
  # 数据库连接
  datasource:
    url: jdbc:mysql://localhost:3306/db_admin?useSSL=false&amp&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

4、整合 MyBatis 框架实现持久化

4.1 创建实体类(Entity 层

在项目的 entity 层,创建 UserInfo 类(用户信息实体类),并继承 UserDetails 类。

package com.pjb.securitydemo.entity;

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

/**
 * 用户信息实体类
 */
@Data
public class UserInfo implements UserDetails
{
    private Long id; //主键ID
    private String username; //用户名称
    private String password; //用户密码
    private String roles; //用户角色
    private boolean enable; //是否启用

    private List<GrantedAuthority> authoritys; //权限集合

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities()
    {
        return this.authoritys;
    }

    public void setAuthoritys(List<GrantedAuthority> authoritys)
    {
        this.authoritys = authoritys;
    }

    @Override
    public boolean isAccountNonExpired()
    {
        return true;
    }

    @Override
    public boolean isAccountNonLocked()
    {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired()
    {
        return true;
    }

    @Override
    public boolean isEnabled()
    {
        return true;
    }
}

实现 UserDetails 定义的几个方法:

  • isAccountNonExpired、isAccountNonLocked 和 isCredentialsNonExpired 暂且用不到,统一返回 true,否则 Spring Security 会认为账号异常。
  • isEnabled 对应 enable 字段,将其代入即可。
  • getAuthorities 方法本身对应的是 roles 字段,但由于机构不一致,所以此次新建一个,并在后续进行填充。

4.2 Mapper动态代理接口(Mapper层)

在 Mapper层,创建 UserInfoMapper 类(用户信息Mapper动态代理接口)

package com.pjb.securitydemo.mapper;

import com.pjb.securitydemo.entity.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

/**
 * 用户信息Mapper动态代理接口
 **/
@Repository
@Mapper
public interface UserInfoMapper
{
    @Select("SELECT * FROM user_info WHERE username=#{username}")
    UserInfo findUserName(@Param("username") String username);
}

5、整合 Spring Security 框架实现认证与授权

5.1 服务类(Service  层)

创建 LoginService 类(登录服务类),实现 UserDetailsService 接口,重写 loadUserByUsername 方法,实现登录认证功能。

package com.pjb.securitydemo.service;

import com.pjb.securitydemo.entity.UserInfo;
import com.pjb.securitydemo.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 登录服务类
 */
@Service
public class LoginService implements UserDetailsService
{
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        //获取用户信息
        UserInfo userInfo = userInfoMapper.findUserName(username);

        //用户不存在,抛出异常
        if (userInfo == null)
        {
            throw new UsernameNotFoundException("用户不存在");
        }

        //将数据库形式的 roles 解析为 UserDetails 的权限集
        // AuthorityUtils.commaSeparatedStringToAuthorityList 方法是 Spring Security
        //提供的,该方法用于将逗号分割的权限集字符串切割成可用权限集对象列表
        List<GrantedAuthority> grantedAuthorityList = AuthorityUtils.commaSeparatedStringToAuthorityList(userInfo.getRoles());
        userInfo.setAuthoritys(grantedAuthorityList);

        return userInfo;
    }
}

5.2 处理类(Handler 层)

(1)登录成功处理类

package com.pjb.securitydemo.handler;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登录成功处理类
 */
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler
{
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException
    {
        //重定向至首页
        httpServletResponse.sendRedirect("/");
    }
}

(2)登录失败处理类

package com.pjb.securitydemo.handler;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 登录失败处理类
 */
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler
{
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authenticationException) throws IOException, ServletException
    {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter out = httpServletResponse.getWriter();
        out.write("登录失败");
    }
}

 (3)403无权限处理类

package com.pjb.securitydemo.handler;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 403无权限处理类
 */
@Component
public class PermissionDeniedHandler implements AccessDeniedHandler
{
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException
    {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter out = httpServletResponse.getWriter();
        out.write("403无权限");
    }
}

5.3 配置类(Config 层)

创建 WebSecurityConfig 类(Spring Security 配置类),并添加 @EnableWebSecurity 注解和继承 WebSecurityConfigurerAdapter 类。

package com.pjb.securitydemo.config;

import com.pjb.securitydemo.handler.LoginFailureHandler;
import com.pjb.securitydemo.handler.LoginSuccessHandler;
import com.pjb.securitydemo.handler.PermissionDeniedHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * Spring Security 配置类
 * @author pan_junbiao
 **/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Autowired
    private LoginFailureHandler loginFailureHandler;

    @Autowired
    private PermissionDeniedHandler permissionDeniedHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http.authorizeRequests() //返回一个URL拦截注册器
                .antMatchers("/admin/api/**").hasRole("ADMIN") //设置授权角色
                .antMatchers("/user/api/**").hasRole("USER") //设置授权角色
                .antMatchers("/app/api/**", "/captcha.jpg").permitAll() //公开其权限
                .anyRequest() //匹配所有的请求
                .authenticated() //所有匹配的URL都需要被认证才能访问
                .and() //结束当前标签,让上下文回到 HttpSecurity
                .formLogin() //启动表单认证
                .loginPage("/myLogin.html") //自定义登录页面
                .loginProcessingUrl("/auth/form") //指定处理登录请求路径
                .permitAll() //使登录页面不设限访问
                //.defaultSuccessUrl("/index") //登录认证成功后的跳转页面
                .successHandler(loginSuccessHandler) //指定登录成功时的处理
                .failureHandler(loginFailureHandler) //指定登录失败时的处理
                .and()
                .exceptionHandling().accessDeniedHandler(permissionDeniedHandler) //403无权时的返回操作
                .and().csrf().disable(); //关闭CSRF的防御功能
    }

    /**
     * 由于5.x版本之后默认启用了委派密码编译器,
     * 因而按照以往的方式设置内存密码将会读取异常,
     * 所以需要暂时将密码编码器设置为 NoOpPasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return NoOpPasswordEncoder.getInstance();
    }
}

6、前端页面

6.1 控制器层(Controller层)

创建 IndexController 类(首页控制器),实现获取当前登录用户名并跳转至首页。

package com.pjb.securitydemo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import java.security.Principal;

/**
 * 首页控制器
 * @author pan_junbiao
 **/
@Controller
public class IndexController
{
    /**
     * 首页
     */
    @RequestMapping("/")
    public String index(HttpServletRequest request)
    {
        //获取当前登录人
        String userName = "未登录";
        Principal principal = request.getUserPrincipal();
        if(principal!=null)
        {
            userName = principal.getName();
        }

        //返回页面
        request.setAttribute("userName",userName);
        return "/index.html";
    }

}

6.2 编写登录页面

在 resources\static 静态资源目录下,创建 myLogin.html 页面。

注意:myLogin.html 页面必须放在 resources\static 静态资源目录下,否则页面无法加载。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <meta name="author" content="pan_junbiao的博客">
</head>
<body>
<form name="myForm" action="/auth/form" method="post">
    <table align="center">
        <caption>用户登录</caption>
        <tr>
            <td>登录账户:</td>
            <td>
                <input type="text" name="username" placeholder="请输入登录账户" value="panjunbiao" />
            </td>
        </tr>
        <tr>
            <td>登录密码:</td>
            <td>
                <input type="password" name="password" placeholder="请输入登录密码" value="123456" />
            </td>
        </tr>
        <!-- 以下是提交、取消按钮 -->
        <tr>
            <td colspan="2" style="text-align: center; padding: 5px;">
                <input type="submit" value="提交" />
                <input type="reset" value="重置" />
            </td>
        </tr>
    </table>
</form>
</body>
</html>

6.3 编写首页

在 resources\templates 资源目录下,创建 index.html 页面。

注意:首页 index.html 页面中使用 Thymeleaf 模板 。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
    <meta name="author" content="pan_junbiao的博客">
</head>
<body>
    <h1 style="color: red">Hello,Spring Security</h1>
    <p>博客信息:您好,欢迎访问 pan_junbiao的博客</p>
    <p>博客地址:https://blog.csdn.net/pan_junbiao</p>
    <p th:text="'当前登录人:' + ${userName}"></p>
    <a href="/logout" onclick="return confirm('确认注销吗?');">登出</a>
</body>
</html>

7、运行项目

7.1 登录页面

7.2 登录成功后,跳转至首页

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2246689.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CPU命名那些事

一、Intel CPU命名 1. 命名结构 Intel CPU 的命名通常包含以下几个部分&#xff1a; 品牌 产品线 系列 代数 具体型号 后缀 例如&#xff1a;Intel Core i7-13700K 2. 各部分含义 品牌 Intel&#xff1a;表示厂商&#xff08;几乎所有命名中都有&#xff09;。不同品…

爬虫与反爬-Ja3指纹风控(Just a moment...)处理方案及参数说明

概述&#xff1a;本文将针对 Ja3 指纹检测风控进行处理&#xff0c;举例了一个案例并使用两种不同的破解方案进行突破&#xff0c;同时深入了解指纹间不同字符所代表的含义 指纹检测背景&#xff1a; 1、每一个设备、软件都有独属于自己的设备信息、版本号、加密算法、椭圆算…

一篇快速上手 Axios,一个基于 Promise 的网络请求库(涉及原理实现)

Axios 1. 介绍1.1 什么是 Axios&#xff1f;1.2 axios 和 ajax 的区别 2. 安装使用3. Axios 基本使用3.1 Axios 发送请求3.2 其他方式发送请求3.3 响应结构3.4 Request Config3.5 默认配置3.6 创建实例对象发送请求 3.7 拦截器3.8 取消请求 4. 模拟 Axios4.1 axios 对象创建过程…

Java项目实战II基于SpringBoot前后端分离的网吧管理系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着互联网技术的不断发展…

【微软:多模态基础模型】(4)统一视觉模型

欢迎关注[【youcans的AGI学习笔记】](https://blog.csdn.net/youcans/category_12244543.html&#xff09;原创作品 【微软&#xff1a;多模态基础模型】&#xff08;1&#xff09;从专家到通用助手 【微软&#xff1a;多模态基础模型】&#xff08;2&#xff09;视觉理解 【微…

动态规划算法--01背包问题详细讲解步骤

举个例子 要确定哪些物品被放入背包以达到最大价值&#xff0c;可以在计算 dp 数组的同时记录选择的物品。具体来说&#xff0c;可以使用一个额外的数组来记录每个状态的选择情况。以下是一个详细的步骤和代码实现&#xff1a; n 3 W 5 weights [2, 1, 3] values [6, 3…

Jenkins的环境部署

day22 回顾 Jenkins 简介 官网Jenkins Jenkins Build great things at any scale The leading open source automation server, Jenkins provides hundreds of plugins to support building, deploying and automating any project. 用来构建一切 其实就是用Java写的一个项目…

微软发布Win11 24H2系统11月可选更新KB5046740!

系统之家11月22日报道&#xff0c;微软针对Win11 24H2系统推出2024年11月最新可选更新补丁KB5046740&#xff0c;更新后系统版本后升至26100.2454&#xff0c;此次更新后修复当应用程序以PDF和XLSX格式导出图表对象时停止响应、无法使用API查找旋转信息等问题。以下小编将给大家…

JavaEE 实现 登录+注册(采用注解方式链接数据库)

&#xff08;Spring MVC的Controller练习&#xff09; 工具&#xff1a;Tomcat 10.0.23&#xff0c;MySQL&#xff0c;JDK18 一、运行效果展示 点击运行Tomcat首先进入index.jsp页面 若已有账号点击登录即可进行登录&#xff0c;这里先点击“获取ROY6账号”去注册&#xff0…

用 React18 构建点击计分小游戏

本教程将带你创建一个简单的点击计分游戏&#xff0c;使用 React 和基本的 Hooks。游戏规则很简单&#xff1a;在 10 秒内尽可能多地点击按钮以获取高分。 项目结构 确保你的项目结构如下&#xff1a; 编写 ClickGame 组件 在 src/ClickGame.js 文件中&#xff0c;编写如下…

Halo 正式开源: 使用可穿戴设备进行开源健康追踪

在飞速发展的可穿戴技术领域&#xff0c;我们正处于一个十字路口——市场上充斥着各式时尚、功能丰富的设备&#xff0c;声称能够彻底改变我们对健康和健身的方式。 然而&#xff0c;在这些光鲜的外观和营销宣传背后&#xff0c;隐藏着一个令人担忧的现实&#xff1a;大多数这些…

数据结构:链表进阶

链表进阶 1. ArrayList的缺陷2. 链表2.1 链表的概念及结构2.2 链表的实现 3.链表面试题4.LinkedList的使用5.1 什么是LinkedList4.2 LinkedList的使用 5. ArrayList和LinkedList的区别 1. ArrayList的缺陷 通过源码知道&#xff0c;ArrayList底层使用数组来存储元素&#xff1…

第二十二周机器学习笔记:动手深度学习之——线性代数

第二十周周报 摘要Abstract一、动手深度学习1. 线性代数1.1 标量1.2 向量1.3 矩阵1.4 张量1.4.1 张量算法的基本性质 1.5 降维1.5.1 非降维求和 1.6 点积1.6.1 矩阵-向量积1.6.2 矩阵-矩阵乘法 1.7 范数 总结 摘要 本文深入探讨了深度学习中的数学基础&#xff0c;特别是线性代…

Flink-Source的使用

Data Sources 是什么呢&#xff1f;就字面意思其实就可以知道&#xff1a;数据来源。 Flink 做为一款流式计算框架&#xff0c;它可用来做批处理&#xff0c;也可以用来做流处理&#xff0c;这个 Data Sources 就是数据的来源地。 flink在批/流处理中常见的source主要有两大类…

分公司如何纳税

分公司不进行纳税由总公司汇总纳税“子公司具有法人资格&#xff0c;依法独立承担民事责任;分公司不具有法人资格&#xff0c;其民事责任由公司承担。”企业设立分支机构&#xff0c;使其不具有法人资格&#xff0c;且不实行独立核算&#xff0c;则可由总公司汇总缴纳企业所得税…

亚马逊搜索关键词怎么写?

在亚马逊这个全球领先的电子商务平台&#xff0c;如何让自己的产品被更多的消费者发现&#xff0c;是每一个卖家都需要深入思考的问题。而搜索关键词&#xff0c;作为连接卖家与买家的桥梁&#xff0c;其重要性不言而喻。那么&#xff0c;如何撰写有效的亚马逊搜索关键词呢&…

跨视角差异-依赖网络用于体积医学图像分割|文献速递-生成式模型与transformer在医学影像中的应用

Title 题目 Cross-view discrepancy-dependency network for volumetric medical imagesegmentation 跨视角差异-依赖网络用于体积医学图像分割 01 文献速递介绍 医学图像分割旨在从原始图像中分离出受试者的解剖结构&#xff08;例如器官和肿瘤&#xff09;&#xff0c;并…

基本功能实现

目录 1、环境搭建 2、按键控制灯&电机 LED 电机 垂直按键(机械按键) 3、串口调试功能 4、定时器延时和定时器中断 5、振动强弱调节 6、万年历 7、五方向按键 1、原理及分析 2、程序设计 1、环境搭建 需求: 搭建一个STM32F411CEU6工程 分析: C / C 宏定义栏…

C++11新特性探索:Lambda表达式与函数包装器的实用指南

文章目录 前言&#x1f349;一、Lambda表达式&#xff08;匿名函数&#xff09;&#x1f353;1.1 Lambda 表达式的基本语法&#x1f353;1.2 示例&#xff1a;基本 Lambda 表达式&#x1f353;1.3 捕获列表&#xff08;Capture&#xff09;&#x1f353;1.4 使用 Lambda 表达式…

msvcp110.dll丢失修复的多种科学方法分析,详细解析msvcp110.dll文件

遇到“msvcp110.dll丢失”的错误时&#xff0c;这表明你的系统缺少一个关键文件&#xff0c;但解决这一问题比较直接。本文将指导你通过几个简单的步骤迅速修复此错误&#xff0c;确保你的程序或游戏可以顺利运行。接下来的操作将非常简洁明了&#xff0c;易于理解和执行。 一.…