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 {@Overrideprotected 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容器管理@Beanpublic 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容器管理@Beanpublic PasswordEncoder passwordEncoder(){return  new BCryptPasswordEncoder();}@Overrideprotected 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);//结果为trueSystem.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方法;

 @Overrideprotected 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容器管理@Beanpublic PasswordEncoder passwordEncoder(){return  new BCryptPasswordEncoder();}@Overrideprotected 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();}@Overrideprotected 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 {@Autowiredprivate UserDao userDao;@Autowiredprivate PermissionDao permissionDao;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//1. 根据账户查找用户信息QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("username",username);User user = userDao.selectOne(queryWrapper);//2.判断用户是否为nullif(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 {@Autowiredprivate MyUserDetailService userDetailService;//添加加密器@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//使用数据库账号和密码auth.userDetailsService(userDetailService);}@Overrideprotected 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数据

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

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

相关文章

指针笔试题(C语言进阶)

目录 前言 1、案例一 1.1 答案 1.2 解析 2、案例二 2.1 答案 2.2 解析 3、案例三 3.1 答案 3.2 解析 4、案例四 4.1 答案 4.2 解析 5、案例五 5.1 答案 5.2 解析 总结 前言 “纸上得来终觉浅&#xff0c;绝知此事要躬行”。本篇通过对指针实际案例的分析&…

Google重磅开源!Gemma 2B/7B小模型登场,6万亿Tokens喂饱,聊天编程两不误,LLaMA也黯然失色?

Google又有大动作&#xff01; 近日&#xff0c;他们发布了Gemma 2B和7B两个开源AI模型&#xff0c;与大型封闭模型不同&#xff0c;它们更适合小型任务&#xff0c;如聊天和文本摘要。 这两个模型在训练过程中使用了6万亿个Tokens的数据&#xff0c;包括网页文档、代码和数学…

收单外包机构备案2023年回顾和2024年展望

孟凡富 本文原标题为聚合支付深度复盘与展望&#xff0c;首发于《支付百科》公众号&#xff01; 收单外包服务机构在我国支付收单市场中占据着举足轻重的地位&#xff0c;其规模在政策引导和市场需求驱动下不断扩大。同时&#xff0c;随着行业自律管理体系的持续发展和完善&a…

文献速递:GAN医学影像合成--用生成对抗网络生成 3D TOF-MRA 体积和分割标签

文献速递&#xff1a;GAN医学影像合成–用生成对抗网络生成 3D TOF-MRA 体积和分割标签 01 文献速递介绍 深度学习算法在自然图像分析中的成功近年来已被应用于医学成像领域。深度学习方法已被用于自动化各种耗时的手动任务&#xff0c;如医学图像的分割和分类&#xff08;G…

顶刊中很出彩的二元变量图

导师希望你发顶刊, 但你的图纸差点意思, 那么,你不妨试试这个, 二元变量图, 在顶刊中都很出彩哦! 本次,我们来以“降水量”和“NDVI”两个数据为例,绘制二元变量分析图,表达“降水量”和“NDVI”之间的关系。 什么是二元变量图 首先还是先解释下“二元变量图”。顾…

扫盲贴:Svg动画和Canvas动画有什么区别

hello&#xff0c;我是贝格前端工场&#xff0c;网页中动画的实现有N种方式&#xff0c;比如css动画&#xff0c;js动画&#xff0c;svg动画&#xff0c;canvas动画等等&#xff0c;每一种动画都有对应的场景&#xff0c;本问重点介绍一下svg和canvas动画的异同点&#xff0c;欢…

大工程 从0到1 数据治理 数仓篇(sample database classicmodels _No.7)

大工程 从0到1 数据治理 之数仓篇 我这里还是sample database classicmodels为案列&#xff0c;可以下载&#xff0c;我看 网上还没有类似的 案列&#xff0c;那就 从 0-1开始吧&#xff01; 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参…

TRS 2024 论文阅读 | 基于点云处理和点Transformer网络的人体活动连续识别

无线感知/雷达成像部分最新工作<持续更新>: 链接地址 注1:本文系“无线感知论文速递”系列之一,致力于简洁清晰完整地介绍、解读无线感知领域最新的顶会/顶刊论文(包括但不限于 Nature/Science及其子刊; MobiCom, Sigcom, MobiSys, NSDI, SenSys, Ubicomp; JSAC, 雷达学…

提高代码质量的 10 条编码原则

提高代码质量的 10 条编码原则 本文转自 公众号 ByteByteGo&#xff0c;如有侵权&#xff0c;请联系&#xff0c;立即删除 今天来聊聊提高代码质量的 10 条编码原则。 软件开发需要良好的系统设计和编码标准。我们在下图中列出了 10 条良好的编码原则。 01 遵循代码规范 我们…

Studio One破解版和正版的区别 Studio One购买是永久的吗

在过去的很长一段时间里&#xff0c;很多小伙伴想要使用一款软件时&#xff0c;可能第一时间就去网上寻找破解版的资源&#xff0c; 白嫖的资源固然很香&#xff0c;但随着法制的健全和人们版权意识的增强&#xff0c;现在破解版的资源是越来越少了。同时破解版的资源也会伴随着…

大数据计算技术秘史(上篇)

在之前的文章《2024 年&#xff0c;一个大数据从业者决定……》《存储技术背后的那些事儿》中&#xff0c;我们粗略地回顾了大数据领域的存储技术。在解决了「数据怎么存」之后&#xff0c;下一步就是解决「数据怎么用」的问题。 其实在大数据技术兴起之前&#xff0c;对于用户…

java面试JVM虚拟机篇

1 JVM组成 1.1 JVM由那些部分组成&#xff0c;运行流程是什么&#xff1f; 难易程度&#xff1a;☆☆☆ 出现频率&#xff1a;☆☆☆☆ JVM是什么 Java Virtual Machine Java程序的运行环境&#xff08;java二进制字节码的运行环境&#xff09; 好处&#xff1a; 一次编写&a…

Excel之index、MATCH面试题、VLOOKUP函数,

VLOOKUP() 在表格的首列查找指定的数值&#xff0c;并返回表格当前行中指定列处的数值。 结构&#xff1a;VLOOKUP(查找值,查找区域,列序数,匹配条件) 解释&#xff1a;VLOOKUP(找谁,在哪里找,第几列,0或1) 1.目的&#xff1a;根据【产品】查找【销量】 公式&#xff1a;V…

pikachu靶场-XSS

XSS&#xff1a; XSS&#xff08;跨站脚本&#xff09;概述 Cross-Site Scripting 简称为“CSS”&#xff0c;为避免与前端叠成样式表的缩写"CSS"冲突&#xff0c;故又称XSS。一般XSS可以分为如下几种常见类型&#xff1a; 1.反射性XSS; 2.存储型XSS; 3.DOM型XSS; …

QT的UI入门

二、UI入门 QWidget类&#xff08;熟悉&#xff09; QWidget类是所有组件和窗口的基类&#xff0c;内部包含了一些基础的界面特性。 常用属性&#xff1a; 修改坐标 x : const int 横坐标&#xff0c;每个图形的左上角为定位点&#xff0c;横轴的零点在屏幕的最左边&#xff0c…

171基于matlab的随机共振微弱信号检测

基于matlab的随机共振微弱信号检测&#xff0c;随机共振描述了过阻尼布朗粒子受周期性信号和随机噪声的共同作用下,在非线性双稳态系统中所发生的跃迁现象. 随机共振可用于弱信号的检测。程序已调通&#xff0c;可直接运行。

HashMap 源码学习-jdk1.8

1、一些常量的定义 这里针对MIN_TREEIFY_CAPACITY 这个值进行解释一下。 java8里面&#xff0c;HashMap 的数据结构是数组 &#xff08;链表或者红黑树&#xff09;&#xff0c;每个数组节点下可能会存在链表和红黑树之间的转换&#xff0c;当同一个索引下面的节点超过8个时…

【Webpack】处理字体图标和音视频资源

处理字体图标资源 1. 下载字体图标文件 打开阿里巴巴矢量图标库open in new window选择想要的图标添加到购物车&#xff0c;统一下载到本地 2. 添加字体图标资源 src/fonts/iconfont.ttf src/fonts/iconfont.woff src/fonts/iconfont.woff2 src/css/iconfont.css 注意字体…

[计算机网络]---TCP协议

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一 、TCP协…

Java并发基础:原子类之AtomicBoolean全面解析

本文概要 AtomicBoolean类优点在于能够确保布尔值在多线程环境下的原子性操作&#xff0c;避免了繁琐的同步措施&#xff0c;它提供了高效的非阻塞算法实现&#xff0c;可以大大提成程序的并发性能&#xff0c;AtomicBoolean的API设计非常简单易用。 AtomicBoolean核心概念 …