Spring Security 认证授权安全框架

Spring Security概述

1.什么是Spring Security?

Spring Security是一个Java框架,用于保护应用程序的安全性。它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。Spring Security基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用程序中。它支持多种身份验证选项和授权策略,开发人员可以根据需要选择适合的方式。此外,Spring Security还提供了一些附加功能,如集成第三方身份验证提供商和单点登录,以及会话管理和密码编码等。总之,Spring Security是一个强大且易于使用的框架,可以帮助开发人员提高应用程序的安全性和可靠性。

什么是授权

这个根据用户的权限来控制用户使用资源的过程就是授权。

为什么要授权

认证是为了保护用户身份的合法性,授权则是为了更细的粒度的对隐私数据经行划分,授权是在认证通过后发生的,控制不同的用户能够访问不同的资源;

授权:授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问;

Spring Security入门体验

一.快速搭建Spring Security安全框架项目;

1.1--创建一个SpringBoot项目;

1.2--修改SpringBoot的版本号,并修改jdk版本号;

<?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>
    <groupId>com.example</groupId>
    <artifactId>SpringSecurity001</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringSecurity001</name>
    <description>SpringSecurity001</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.6.13</spring-boot.version>
        <!--SpringBoot版本号   这里使用的JDK版本为8-->
    </properties>
    <dependencies>
        <!--引入Spring Security安全框架依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--添加lombok依赖  实体类的get set方法生成-->
        <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>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.aaa.SpringSecurity001Application</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

1.3--引入SPringleSecurity依赖;

<!--引入Spring Security安全框架依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

1.4--创建一个HelloController,Controller层定义一个资源;

1.5--启动项目并访问资源路径;

访问路径:http://localhost:8080/hello

注:我们发现使用了security后在访问我们自己的接口,security会拦截并跳转到认证页面,认证后才可以访问。默认认证的账号uer密码在控制台;

二.创建Spring Security自定义账户和密码;

我们刚才使用的是security自带的账户和密码,我们自己也可以自定义账户和密码;

2.1--在application.properties配置文件中定义一个账户和密码;

自定义账户和密码文件如下:

#创建Spring Security自定义账户和密码

#账户
spring.security.user.name=admin
#密码
spring.security.user.password=123456
server.port=8080

注:配置完自定义文件和密码,重启运行,这是控制台中没有密码(控制台不会生成密码);

注:这种方式只能定义一个账户和密码,无法定义多个账户和密码;

三.自定义多个账户和密码

在配置文件中,只能定义一个账户和密码。我们可以定义一个配置类,完成多个账号和密码的定义;

1.创建Config层 MysecurityConfig;

配置类内容

package com.aaa.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication() //基于内存创建的账户和密码  未来可以用来连接数据库
                //账户
                .withUser("zhangsan")
                //密码
                .password("123456")
                //用户具备的角色
                .roles("admin")
                .and() //连接符
                .withUser("lisi")
                .password("012345")
                .roles("test")
                .and()
                .withUser("wangwu")
                .password("567890")
                .roles("group");

    }
}

注:如果使用了配置类,那么之前在配置文件中定义的账户和密码不在生效;

控制台错误提示:需要给它指定的加密器;

解决方式:添加加密器

//可以把该方法返回的对象交于Spring容器管理
    @Bean
    public PasswordEncoder passwordEncoder(){
        return  new BCryptPasswordEncoder();
    }
package com.aaa.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    //可以把该方法返回的对象交于Spring容器管理
    @Bean
    public PasswordEncoder passwordEncoder(){
        return  new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication() //基于内存创建的账户和密码  未来可以用来连接数据库
                //账户
                .withUser("zhangsan")
                //密码
                .password(passwordEncoder().encode("123456"))
                //用户具备的角色
                .roles("admin")
                .and() //连接符
                .withUser("lisi")
                .password(passwordEncoder().encode("012345"))
                .roles("test")
                .and()
                .withUser("wangwu")
                .password(passwordEncoder().encode("567890"))
                .roles("group");

    }
}

注:把明文密码通过加密器经行加密;

再次访问资源

四.密码加密器

密码加密器,可以把把明文转换为密文 encode

同时也可以匹配密码 matches;

package com.aaa.Test;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

public class test {
    public static void main(String[] args) {
        //创建加密器对象
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        //使用加密器
        String encode = passwordEncoder.encode("123456");
        System.out.println(encode);
        //匹配密码  第一个参数是客户输得密码   第二个参数是加密器密文
        //把客户输得密码和加密过的密码经行比对是否一致,一致返回true,不一致返回flase;
        boolean matches = passwordEncoder.matches("123456", encode);
        //结果为true
        System.out.println(matches);   
    }
}

 

问题:对称加密和非对称加密

答:对称加密:加密和解密使用的密钥是同一个; --这种加密的方式可以破解

      非对称加密: 加密和解密使用的密钥不是同一个;--这种加密的方式无法破解

五.获取当前用户的信息

获取登录成功者的信息

package com.aaa.Controller;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "hello资源·······";
    }

    @GetMapping("/info")
    public Authentication info(){
        //把当前信息保存到SecurityContext类中;
        SecurityContext securityContext = SecurityContextHolder.getContext();
        //把当前用户封装到Authentication类中  账户  密码 权限 角色等信息
        Authentication authentication = securityContext.getAuthentication();
        return authentication;
    }
}

Security框架会把当前用户信息封装到Authentication中,并把该类对象存放到SecurityContext中;

访问资源路径http://localhost:8080/info

六.修改它的登录页面

默认security提供了一个登录页面,如果不想使用它提供的页面,我们可以指定我们自己的登录页面;

1.自己在static静态资源文件下创建一个登录页面;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页</title>
  <style>
    body {
      background-color: #222;
      font-family: Arial, sans-serif;
      color: #fff;
      display: flex;
      align-items: center;
      justify-content: center;
      height: 100vh;
    }

    .login-container {
      background-color: #333;
      padding: 40px;
      border-radius: 10px;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
    }

    h1 {
      text-align: center;
    }

    .form-group {
      margin-bottom: 20px;
    }

    .form-group label {
      display: block;
      margin-bottom: 5px;
    }

    .form-group input[type="text"],
    .form-group input[type="password"] {
      width: 100%;
      padding: 10px;
      border-radius: 5px;
      border: none;
      background-color: #555;
      color: #fff;
    }

    .form-group input[type="submit"] {
      width: 100%;
      padding: 10px;
      border-radius: 5px;
      border: none;
      background-color: #007bff;
      color: #fff;
      cursor: pointer;
      transition: background-color 0.3s ease;
    }

    .form-group input[type="submit"]:hover {
      background-color: #0056b3;
    }
  </style>
</head>
<body>
<div class="login-container">
  <h1>Welcome to Cool Login Page</h1>
  <form action="login" method="post">
    <div class="form-group">
      <label for="username">Username:</label>
      <!--账户-->
      <input type="text" id="username" name="username" required>
    </div>
    <div class="form-group">
      <label for="password">Password:</label>
      <!--密码-->
      <input type="password" id="password" name="password" required>
    </div>
    <div class="form-group">
      <input type="submit" value="Login">
    </div>
  </form>
</div>
</body>
</html>

2.修改security配置类;在config层 HelloConfig文件中重写configure方法;

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        //设置表单登录信息
        http.formLogin()
                //设置自己的登录页面
                .loginPage("/login.html")
                //设置登录表单的提交路径 要和login.html中的action一致
                .loginProcessingUrl("/login")
                //这个页面允许放行
                .permitAll();

        //禁止跨域伪造请求验证
        http.csrf().disable();
        //其他请求路径都要认证
        http.authorizeRequests().anyRequest().authenticated();

    }

访问路径访问info或者hello都可以进入到自己的登录页面;

七.设置跳转登录成功页面

默认登录成功 / 或原来的访问路径

创建SuccessController

package com.aaa.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@Controller
public class SuccessController {

    @PostMapping("/success")
    public String success(){
        return "redirect:/success.html";
    }
}

创建一个success.html登录成功页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>登录成功</h1>
</body>
</html>

输入账号和密码

登录成功

授权的实现

授权:用户具有的权限和资源绑定的过程就是授权;

第一步:修改config  MySecurityConfig配置文件

 第二步:创建自定义资源访问  controller层  AuthController资源文件

package com.aaa.Controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

//资源
@RestController
public class AuthController {


    @GetMapping("select")
    public String select(){
        System.out.println("查询资源");
        return "查询资源";
    }
    @GetMapping("update")
    public String update(){
        System.out.println("修改资源");
        return "修改资源";
    }
    @GetMapping("delete")
    public String delete(){
        System.out.println("删除资源");
        return "删除资源";
    }
    @GetMapping("xxx")
    public String xxx(){
        System.out.println("共享资源");
        return "共享资源";
    }
}

第三步: 权限和资源进行绑定   config  MySecurityConfig配置文件

package com.aaa.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    //可以把该方法返回的对象交于Spring容器管理
    @Bean
    public PasswordEncoder passwordEncoder(){
        return  new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //设置表单登录信息
        http.formLogin()
                //设置自己的登录页面
                .loginPage("/login.html")
                //设置登录表单的提交路径 要和login.html中的action一致
                .loginProcessingUrl("/login")
                //表示登录成功跳转路径 提交方式必须为post请求
                .successForwardUrl("/success")
                //这个页面允许放行
                .permitAll();

        //设定资源和权限进行绑定
        http.authorizeRequests()
                .antMatchers("/select").hasAnyAuthority("user:select")
                .antMatchers("/update").hasAnyAuthority("user:update")
                .antMatchers("/delect").hasAnyAuthority("user:delect")
                .antMatchers("/xxx").hasAnyAuthority("user:xxx");

        //异常处理页面
        http.exceptionHandling().accessDeniedPage("/403.html");
        //禁止跨域伪造请求验证
        http.csrf().disable();
        //其他请求路径都要认证
        http.authorizeRequests().anyRequest().authenticated();

    }




    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication() //基于内存创建的账户和密码  未来可以用来连接数据库
                //账户
                .withUser("zhangsan")
                //密码
                .password(passwordEncoder().encode("123456"))
                //用户具备的角色
                .roles("admin")
                //设定用户权限
                .authorities("user:select","user:update","user:delete")
                .and() //连接符
                .withUser("lisi")
                .password(passwordEncoder().encode("012345"))
                .roles("test")
                .authorities("user:xxx")
                .and()
                .withUser("wangwu")
                .password(passwordEncoder().encode("567890"))
                .roles("group");


    }
}

第四步:访问资源

上面再访问没有的权限资源时,出现上面的错误界面,这种界面对客户友好。跳转到一个权限不足的界面;  

使用security注解完成授权

(思考: 上面权限和资源得到绑定 需要手动一一绑定。真正再开发中我们具有的权限和资源是非常多的。如果手动一一绑定是很麻烦的。)

第一步:开启security权限注解驱动

第二步:再相应的资源上使用注解  

controller层  AuthController资源文件

package com.aaa.Controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

//资源
@RestController
public class AuthController {


    @GetMapping("select")
    @PreAuthorize(value = "hasAuthority('user:select')")
    public String select(){
        System.out.println("查询资源");
        return "查询资源";
    }
    @GetMapping("update")
    @PreAuthorize(value = "hasAuthority('user:upate')")
    public String update(){
        System.out.println("修改资源");
        return "修改资源";
    }
    @GetMapping("delete")
    @PreAuthorize(value = "hasAuthority('user:delete')")
    public String delete(){
        System.out.println("删除资源");
        return "删除资源";
    }
    @GetMapping("xxx")
    @PreAuthorize(value = "hasAuthority('user:xxx')")
    public String xxx(){
        System.out.println("共享资源");
        return "共享资源";
    }
}

第三步:修改security配置

第四步:访问资源,流程是一样的  略

了解security认证的流程(源码)

核心过滤器:UsernamePasswordAuthenticationFilter

使用Spring Security连接数据库(自定义认证--需要连接数据库)

第一步:创建securtiy数据库

DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission`  (
  `perid` int(0) NOT NULL AUTO_INCREMENT,
  `pername` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `percode` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`perid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, '用户查询', 'user:query');
INSERT INTO `sys_permission` VALUES (2, '用户添加', 'user:add');
INSERT INTO `sys_permission` VALUES (3, '用户修改', 'user:update');
INSERT INTO `sys_permission` VALUES (4, '用户删除', 'user:delete');
INSERT INTO `sys_permission` VALUES (5, '用户导出', 'user:export');

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `roleid` int(0) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`roleid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, '管理员');
INSERT INTO `sys_role` VALUES (2, '测试人员');
INSERT INTO `sys_role` VALUES (3, '普通用户');

-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission`  (
  `perid` int(0) NULL DEFAULT NULL,
  `roleid` int(0) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES (2, 1);
INSERT INTO `sys_role_permission` VALUES (1, 1);
INSERT INTO `sys_role_permission` VALUES (3, 1);
INSERT INTO `sys_role_permission` VALUES (4, 1);
INSERT INTO `sys_role_permission` VALUES (2, 2);
INSERT INTO `sys_role_permission` VALUES (1, 2);
INSERT INTO `sys_role_permission` VALUES (3, 2);
INSERT INTO `sys_role_permission` VALUES (1, 3);
INSERT INTO `sys_role_permission` VALUES (5, 3);

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `userid` int(0) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `userpwd` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `sex` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `address` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`userid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, '张三', '$2a$10$cI7e7bgSs9.9nNHhxKO9LuK/Ll.AeZwgUyZb77oD2y3UwwZyZhWG6', '男', '郑州');
INSERT INTO `sys_user` VALUES (2, '李四', '$2a$10$cI7e7bgSs9.9nNHhxKO9LuK/Ll.AeZwgUyZb77oD2y3UwwZyZhWG6', '男', '北京');
INSERT INTO `sys_user` VALUES (3, '王五', '$2a$10$cI7e7bgSs9.9nNHhxKO9LuK/Ll.AeZwgUyZb77oD2y3UwwZyZhWG6', '女', '杭州');

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `userid` int(0) NOT NULL,
  `roleid` int(0) NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2);
INSERT INTO `sys_user_role` VALUES (3, 3);
INSERT INTO `sys_user_role` VALUES (1, 2);

SET FOREIGN_KEY_CHECKS = 1;

第二步:创建一个SpringBoot项目--------引入相关的依赖(lombok依赖   mysql驱动依赖  mybatis-plus依赖,security启动依赖)

 <dependencies>
        <!--加入security安全框架依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--mysql驱动依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--mybatis-plus依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.4</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

第三步:创建实体类   entity层 (User  Role  Permission)

User实体类

package com.aaa.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("sys_user")   //实体类和数据库表名不一致时,使用该注解进行映射
public class User {
    @TableId(type = IdType.AUTO)    //主键自增
    private Integer userid;
    private String username;
    private String userpwd;
    private String sex;
    private String address;
}

Role实体类

package com.aaa.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("sys_role")    //实体类和数据库表名不一致时,使用该注解进行映射
public class Role {
    @TableId(type = IdType.AUTO)    //主键自增
    private Integer roleid;
    private  String rolename;
}

Permission实体类

package com.aaa.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("sys_permission")    //实体类和数据库表名不一致时,使用该注解进行映射
public class Permission {
    @TableId(type = IdType.AUTO)    //主键自增
    private Integer perid;
    private String pername;
    private String percode;
}

第四步:创建相应的Dao接口   Dao层  (UserDao  RoleDao  PermissionDao)

UserDao接口

package com.aaa.dao;

import com.aaa.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface UserDao extends BaseMapper<User> {

}

ReloDao接口

package com.aaa.dao;

import com.aaa.entity.Role;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface RoleDao extends BaseMapper<Role> {
}

PermissionDao接口

package com.aaa.dao;

import com.aaa.entity.Permission;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface PermissionDao extends BaseMapper<Permission> {


    //查找用户的权限
    @Select("select distinct p.* from sys_user_role u join sys_role_permission r on u.roleid=r.roleid join sys_permission p on r.perid=p.perid where userid=#{userId}")
    public List<Permission> selectByUserId(Integer userId);

}

第五步:创建业务层 Service     MyUserDetailService代码

package com.aaa.service;

import com.aaa.dao.PermissionDao;
import com.aaa.dao.UserDao;
import com.aaa.entity.Permission;
import com.aaa.entity.User;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.ArrayList;
import java.util.Collection;
import java.util.List;

@Service
public class MyUserDetailService implements UserDetailsService {
    @Autowired
    private UserDao userDao;
    @Autowired
    private PermissionDao permissionDao;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1. 根据账户查找用户信息
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username",username);
        User user = userDao.selectOne(queryWrapper);
        //2.判断用户是否为null
        if(user!=null){
            //3.查找用户的具体权限
            List<Permission> permissions = permissionDao.selectByUserId(user.getUserid());
            /**
             * String username, 账户
             * String password, 密码-->而是数据库中存在的密码
             * Collection<? extends GrantedAuthority> authorities 当前用户具有的权限
             */
            Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
            //把List<Permission>转化为Collection<SimpleGrantedAuthority>
            for(Permission per:permissions){
                SimpleGrantedAuthority simpleGrantedAuthority=new SimpleGrantedAuthority(per.getPercode());
                authorities.add(simpleGrantedAuthority);
            }
            org.springframework.security.core.userdetails.User userDetail=new org.springframework.security.core.userdetails.User(user.getUsername(),user.getUserpwd(),authorities);
            return userDetail;
        }
        return null;
    }
}

第六步:创建Security配置文件   config层   mySecurityConfig文件

package com.aaa.config;

import com.aaa.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyUserDetailService userDetailService;
    //添加加密器
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //使用数据库账号和密码
        auth.userDetailsService(userDetailService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //设置表单信息
        //使用自己的前端登录页面
        http.formLogin().loginPage("/login.html")
                .loginProcessingUrl("/login")
                .successForwardUrl("/success")
                .permitAll();

        //异常处理页面
        http.exceptionHandling().accessDeniedPage("/403.html");
        //禁用跨域伪造响应
        http.csrf().disable();

        //其他所有路径通过都需要认证
        http.authorizeRequests().anyRequest().authenticated();


    }
}

第七步:配置application.properties文件,连接数据库

server.port=8080
spring.datasource.url=jdbc:mysql://localhost:3306/security
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root

第八步:在SpringSecurity002Application  启动类中添加注解让其扫描到com.aaa.dao层

package com;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
//添加注解  包扫描到dao层

@MapperScan(basePackages = "com.aaa.dao")
public class SpringSecurity002Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurity002Application.class, args);
    }

}

第九步:访问资源

使用security认证授权----完成前后端分离的模式

熟悉前后端分离

JWT概述 

1.什么是JWT?

json web token(JWT)是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。它是以JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中还可以完成数据加密、签名等相关处理。

2.JWT能做什么?

授权
这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。
信息交换
JSON Web Token是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可验证内容是否遭到篡改。

3.Session认证与JWT认证的区别?

基于传统的Session认证策略#

a.认证方式#
http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行。
因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息(sessionId)会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
b.Session认证的问题
  • 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大
  • 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
  • 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
  • 在前后端分离系统中就更加痛苦!也就是说前后端分离在应用解耦后增加了部署的复杂性。
    • 通常用户一次请求就要转发多次。如果用session 每次携带sessionid 到服务器,服务器还要查询用户信息。同时如果用户很多。这些信息存储在服务器内存中,给服务器增加负担。
    • CSRF(跨站伪造请求攻击)攻击,session是基于cookie进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
    • sessionid 就是一个特征值,表达的信息不够丰富。不容易扩展。而且如果你后端应用是多节点部署。那么就需要实现session共享机制。 不方便集群应用。

基于JWT认证的策略#

认证流程#
  • 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
  • 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同xxx.yyy.zzz的字符串
    即token = head.payload.singurater
  • 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
  • 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题) HEADER
  • 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。
  • 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

4.jwt优势#

  • 简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快

  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库

  • 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。

  • 不需要在服务端保存会话信息,特别适用于分布式微服务。1.登录成功----返回json数据

5.JWT的结构#

令牌组成#

  • 1.标头(Header)
  • 2.有效载荷(Payload)
  • 3.签名(Signature)

因此,JWT通常如下所示:xxxxx.yyyyy.zzzzz 也就是 Header.Payload.Signature

Header的组成信息#

  • 标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。它会使用 Base64 编码组成 JWT 结构的第一部分。

  • 注意:Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

// header的组成信息
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload组成信息#

令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户信息)和其他数据的声明。同样的,它会使用 Base64 编码组成 JWT 结构的第二部分

// payload组成信息
{
  "id": "823",
  "name": "Code Duck",
  "role": "admin"
}

Signature的组成信息#

header和payload都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们自己的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。

例如:HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret );

# 签名目的
- 最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

# 信息安全问题
- 在这里大家一定会问一个问题:Base64是一种编码,是可逆的,那么我的信息不就被暴露了吗?

- 是的。所以,在JWT中,不应该在负载里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。因此JWT适合用于向Web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。

以上三部分进行整合#

JWT的真实面目:

(Header)eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.

(payLoad)eyJyb2xlIjoiZW1wbG95ZWUiLCJpZCI6IjQiLCJleHAiOjE1OTcxMjc3NjUsInVzZXJuYW1lIjoiamFzb24ifQ.

(Signature)WxIiTf7V4UaboMONu0UpPu-uQSuDQFZqepKKxLstnaU

1.登录成功---返回json数据

2.登录失败----返回json数据

3.权限不足----返回json数据

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【海贼王的数据航海:利用数据结构成为数据海洋的霸主】顺序表

目录 1 -> 线性表 2 -> 顺序表 2.1 -> 概念及结构 2.2 -> 接口声明 2.3 -> 接口实现 2.3.1 -> 初始化 2.3.2 -> 销毁 2.3.3 -> 检查 2.3.4 -> 打印 2.3.5 -> 尾插 2.3.6 -> 头插 2.3.7 -> 尾删 2.3.8 -> 头删 2.3.9 ->…

【牛客】【刷题节】美团2024届秋招笔试第二场编程真题

1.小美的加法【简单题】 题意理解&#xff1a; 给定一个数组做连加操作&#xff0c;其中只能将一个加号变成乘号 将哪个加号变成乘号&#xff0c;使式子最后的结果最大 解题思路&#xff1a; 只有将两个相邻且乘机最大的数之间变成乘号后&#xff0c;才能保证整个式子结果最大 …

Spring之AOP源码解析(下)

前言 在上一遍文章中,我们主要讲解了ProxyFactory在Spring完成AOP动态代理的过程中发挥的作用。这一篇我们主要讲解这些注解都是如何注入Advisors,然后分析这些Advisors生效的条件 注解都是如何注入Advisor并匹配的 EnableTransactionManagement注解 我们在之前提到EnableT…

乡村研学|乡村研学小程序|基于微信小程序的乡村研学平台设计与实现(源码+数据库+文档)

乡村研学小程序目录 目录 基于微信小程序的乡村研学平台设计与实现 一、前言 二、系统功能设计 三、系统实现 1、微信小程序前台 2、管理员后台 &#xff08;1&#xff09;乡村研学管理 &#xff08;2&#xff09;商品信息管理 &#xff08;3&#xff09;商品类型管理 …

计算机网络:思科实验【2-MAC地址、IP地址、ARP协议及总线型以太网的特性】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;Cisco Packet Tracer实验 本文对应的实验报告源文件请关注微信公众号程序员刘同学&#xff0c;回复思科获取下载链接。 实验目的实验环境实验内容MAC地址、IP地址、ARP协议总线型以太网的…

【DAY04 软考中级备考笔记】数据结构基本结构和算法

数据结构基本结构和算法 2月25日 – 天气&#xff1a;晴 周六玩了一天&#xff0c;周天学习。 1. 什么是数据结构 数据结构研究的内容是一下两点&#xff1a; 如何使用程序代码把现实世界的问题信息化如何用计算机高效地处理这些信息从创造价值 2. 什么是数据 数据是信息的…

零基础C++开发上位机--基于QT5.15的串口助手(一)

嵌入式开发的过程中&#xff0c;大部分我们的代码是无法一次成功的。这时候我们大部分的工程师可能最熟练的调试方法是printf函数&#xff0c;打印随意一个数据&#xff0c;来观察当前运行的函数是否执行正确。我们连接的工具有各个大神做的串口助手。另外&#xff0c;在做一般…

从0开始python学习-53.python中flask创建简单接口

目录 1. 创建一个简单的请求,没有写方法时默认为get 2. 创建一个get请求 3. 创建一个post请求&#xff0c;默认可以使用params和表单传参 4. 带有参数的post请求 1. 创建一个简单的请求,没有写方法时默认为get from flask import Flask, request# 初始化一个flask的对象 ap…

【贪心算法】:LeetCode860.柠檬水找零

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本专栏是关于各种算法的解析&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数据结构专栏&…

Ubuntu22.04和Windows10双系统安装

概要 本篇演示Ubuntu22.04和Windows10双系统的安装。先安装Ubuntu22.04&#xff0c;再安装Windows10。 一、说明 1、电脑 笔者的电脑品牌是acer(宏碁/宏基) 电脑开机按F2进入BIOS 电脑开机按F12进入Boot Manager 2、U盘启动盘 需要用到两个U盘启动盘 &#xff08;1&a…

kubernetes集群搭建(1.26版本)

集群搭建 1.初始化安装k8s集群的实验1.1修改主机名称1.2关闭防火墙1.3关闭SELINUX1.4配置主机hosts文件&#xff0c;相互之间通过主机名访问1.5配置主机之间无密码登录1.6关闭交换分区swap&#xff0c;提升性能1.7修改机器内核参数1.9配置阿里云的repo源1.10配置安装k8s组件需要…

力扣● 343. 整数拆分 ● 96.不同的二叉搜索树

● 343. 整数拆分 想不到&#xff0c;要勇于看题解。 关键在于理解递推公式。 1、DP数组及其下标的含义&#xff1a;dp[i]是分解i这个数得到的最大的乘积。 2、DP数组如何初始化&#xff1a;dp[0]和dp[1]都没意义&#xff0c;所以直接不赋值&#xff0c;初始化dp[2]1即可。…

让程序员设计B端界面,好比武大郎招聘:向我看齐。不忍直视!

hello&#xff0c;我是大美B端工场&#xff0c;B端系统的要求越来越高了&#xff0c;很多公司还让程序员负责页面&#xff0c;页面搞的没法看&#xff0c;也怪不得程序员。程序员来搞页面&#xff0c;那还不是武大郎招聘——向我看齐&#xff0c;以我的标准为标准吗&#xff1f…

python 基础知识点(蓝桥杯python科目个人复习计划49)

今日复习内容&#xff1a;做复习题 例题1&#xff1a;希尔排序 题目描述&#xff1a; 希尔排序是直接插入排序算法的一种更高效的改进版本&#xff0c;但它是非稳定排序算法。希尔排序是基于插入排序的以下两点性质而提出的改进方法之一&#xff1a; 1.插入排序在对几乎已经…

预训练-微调范式在人工智能领域的深远影响

预训练-微调范式的出现是人工智能领域的一大里程碑&#xff0c;它深刻改变了深度学习模型的训练方式和应用模式&#xff0c;并对整个行业产生了多方面的深远影响&#xff1a; 数据效率提升&#xff1a; 通过在大规模无标注数据上进行预训练&#xff0c;模型能够学习到丰富的语言…

linux常用的网络命令实战分享

文章目录 ifup/down命令ifconfig命令观察网络接口信息修改接口参数增加虚拟网络接口 route命令查看路由表增加路由表规则删除路由表规则 IP 命令ip linkip addr设定路由 ip route arp 命令 在实际研发运维工作中常常会涉及到网关相关的操作和知识&#xff0c;这里对linux下常用…

(详细使用指南)Linux下交叉编译带ffmpeg的opencv并移植到RK3588等ARM端

一 问题背景 瑞芯微RK3588等嵌入式板作为边缘端设备为算法模型的部署提供了便利&#xff0c;目前很多分类或好检测模型针对边缘端做了优化或量化&#xff0c;使得在边缘端也能达到实时稳定的识别和检测效果。 但嵌入式设备普遍的flash emmc不大&#xff0c;一般在32G左…

【数据结构与算法】(20)高级数据结构与算法设计之 Greedy Algorithm 贪心算法 代码示例与详细讲解

目录 4.2 Greedy Algorithm1) 贪心例子DijkstraPrimKruskal 2) 零钱兑换问题有几个解&#xff08;零钱兑换 II&#xff09;Leetcode 518最优解&#xff08;零钱兑换&#xff09;- 穷举法 Leetcode 322最优解&#xff08;零钱兑换&#xff09;- 贪心法 Leetcode 322 3) Huffman …

9.5K Star,又一款超棒开源轻量自动化运维平台

Hi&#xff0c;骚年&#xff0c;我是大 G&#xff0c;公众号「GitHub指北」会推荐 GitHub 上有趣有用的项目&#xff0c;一分钟 get 一个优秀的开源项目&#xff0c;挖掘开源的价值&#xff0c;欢迎关注。 一个好的运维平台就变得非常重要了&#xff0c;可以节省大量的人力和物…

【HarmonyOS】低代码开发—使用低代码开发服务卡片

DevEco Studio还支持使用低代码开发功能开发服务卡片&#xff0c;目前只支持JS语言&#xff0c;且compileSdkVersion必须为7或以上。 下面以创建一个新的服务卡片为例进行说明。 1.打开一个工程&#xff0c;创建服务卡片&#xff0c;创建方法包括如下两种方式&#xff1a; 选…