SpringSecurity6.x

文章目录

  • 一.什么是SpringSecurity
  • 二.SpringSecurity的特征
  • 三.SpringSecurity的第一个例子
    • 3.1 创建SpringBoot项目
    • 3.2 创建IndexController
    • 3.3 创建index.html
    • 3.4 启动项目
    • 3.5 Spring Security默认做了什么
  • 四.SpringSecurity的整体架构
    • 4.1 Filter
    • 4.2 DelegatingFilterProxy
    • 4.3 FilterChainProxy
    • 4.4 SecurityFilterChain
    • 4.5 Multiple SecurityFilterChain
  • 五.Spring Security自定义配置
    • 5.1 基于内存的用户认证
      • 5.1.1 自定义配置
    • 5.1.2 基于内存的用户认证流程
    • 5.2 基于数据库的数据源
      • 5.2.1 SQL
      • 5.2.2 引入依赖
      • 5.2.3 配置数据源
      • 5.2.4 实体类
      • 5.2.5 Mapper
      • 5.2.6 Service
      • 5.2.7 Controller
    • 5.3 基于数据库的用户认证
      • 5.3.1 基于数据库的用户认证流程
      • 5.3.2 定义DBUserDetailsManager
      • 5.3.3 初始化UserDetailsService
    • 5.4 SpringSecurity的默认配置
    • 5.5 添加用户功能
      • 5.5.1 Controller
      • 5.5.2 Service
      • 5.5.3 修改配置
      • 5.5.4 使用Swagger测试
    • 5.6 密码加密算法
      • 5.6.1 密码测试
    • 5.7 定义登录页面
      • 5.7.1 创建Controller
      • 5.7.2 准备登录页面
      • 5.7.3 配置SecurityFilterChain
  • 六. 前后端分离
    • 6.1 认证流程
    • 6.2 引入fastjson
    • 6.3 认证成功的响应
      • 6.3.1 成功结果处理
      • 6.3.2 SecurityFilterChain配置
    • 6.4 认证失败响应
      • 6.4.1 失败结果处理
      • 6.4.2 SecurityFilterChain配置
    • 6.5 注销响应
      • 6.5.1 注销结果处理
      • 6.5.2 SecurityFilterChain配置
    • 6.6 请求未认证的接口
      • 6.6.1 实现AuthenticationEntryPoint接口
      • 6.6.2 SecurityFilterChain配置
    • 6.7 跨域
  • 七.身份认证
    • 7.1 身份认证信息
    • 7.2 会话并发处理
      • 实现处理器接口
      • SecurityFilterChain配置
  • 八.授权
    • 基于request的授权
      • 用户-权限-资源
        • 配置权限
        • 授予权限
        • 请求未授权的接口
      • 用户-角色-资源
        • 配置角色
        • 授予角色
      • 用户-角色-权限-资源
    • 基于方法的授权
      • 开启方法授权
      • 给用户授予角色和权限
      • 常用授权注解
  • 九.Spring Security OAuth2
    • 9.1 OAuth2介绍
      • 9.1.1 角色介绍
      • 9.1.2 OAuth2怎么用
      • 9.1.3 OAuth2的授权模式
    • 9.2 为什么要用OAuth2

一.什么是SpringSecurity

Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于 Spring 的应用程序的事实上的标准。

Spring Security 是一个专注于为 Java 应用程序提供身份验证和授权的框架。与所有 Spring 项目一样,Spring Security 的真正强大之处在于它可以轻松扩展以满足自定义需求。

二.SpringSecurity的特征

● spring-security对spring整合较好,使用起来更加方便;
● 有更强大的spring社区进行支持;
● 支持第三方的 oauth 授权,官方网站:spring-security-oauth

三.SpringSecurity的第一个例子

3.1 创建SpringBoot项目

项目名:security-demo

JDK:17

SpringBoot:3.2.0(依赖了Spring Security 6.2.0)

Dependencies:Spring Web、Spring Security、Thymeleaf
在这里插入图片描述

3.2 创建IndexController

@Controller
public class IndexController {

    @GetMapping("/")
    public String index() {
        return "index";
    }
}

3.3 创建index.html

<html xmlns:th="https://www.thymeleaf.org">
<head>
  <title>Hello Security!</title>
</head>
<body>
<h1>Hello Security</h1>
<!--通过使用@{/logout},Thymeleaf将自动处理生成正确的URL,以适应当前的上下文路径。
这样,无论应用程序部署在哪个上下文路径下,生成的URL都能正确地指向注销功能。-->
<a th:href="@{/logout}">Log Out</a>
</body>
</html>

3.4 启动项目

浏览器中访问:http://localhost:8080/
在这里插入图片描述
输入用户名:user

输入密码:在控制台的启动日志中查找初始的默认密码

点击"Sign in"进行登录,浏览器就跳转到了index页面

3.5 Spring Security默认做了什么

我们会讲述为什么,我们启动项目之后,springsecurity会发生什么?具体的思路如下:

  1. 当用户登录时,前端将用户输入的用户名、密码信息传输到后台,后台用一个类对象将其封装起来,通常使用的是UsernamePasswordAuthenticationToken这个类。
  2. 程序负责验证这个类对象。验证方法是调用Service根据username从数据库中取用户信息到实体类的实例中,比较两者的密码,如果密码正确就成功登陆,同时把包含着用户的用户名、密码、所具有的权限等信息的类对象放到SecurityContextHolder(安全上下文容器,类似Session)中去。
  3. 用户访问一个资源的时候,首先判断是否是受限资源。如果是的话还要判断当前是否未登录,没有的话就跳到登录页面。
  4. 如果用户已经登录,访问一个受限资源的时候,程序要根据url去数据库中取出该资源所对应的所有可以访问的角色,然后拿着当前用户的所有角色一一对比,判断用户是否可以访问(这里就是和权限相关)。

但SpringSecurity会帮我做什么事情呢?具体的SpringSecurity会帮我们做的事情如下:

  • 保护应用程序URL,要求对应用程序的任何交互进行身份验证。
  • 程序启动时生成一个默认用户“user”。
  • 生成一个默认的随机密码,并将此密码记录在控制台上。
  • 生成默认的登录表单和注销页面。
  • 提供基于表单的登录和注销流程。
  • 对于Web请求,重定向到登录页面;
  • 对于服务请求,返回401未经授权。
  • 处理跨站请求伪造(CSRF)攻击。
  • 处理会话劫持攻击。
  • 写入Strict-Transport-Security以确保HTTPS。
  • 写入X-Content-Type-Options以处理嗅探攻击。
  • 写入Cache Control头来保护经过身份验证的资源。
  • 写入X-Frame-Options以处理点击劫持攻击。

四.SpringSecurity的整体架构

具体可以先参考官网:Spring Security的底层原理
Spring Security之所以默认帮助我们做了那么多事情,它的底层原理是传统的Servlet过滤器

4.1 Filter

下图展示了处理一个Http请求时,过滤器和Servlet的工作流程:
在这里插入图片描述
因此我们可以在过滤器中对请求进行修改或增强。

4.2 DelegatingFilterProxy

Spring 提供了一个Filter名为 的实现DelegatingFilterProxy,允许在 Servlet 容器的生命周期和 Spring 的ApplicationContext. Servlet容器允许Filter使用自己的标准注册实例,但它不知道Spring定义的Bean。您可以DelegatingFilterProxy通过标准 Servlet 容器机制进行注册,但将所有工作委托给实现Filter.
在这里插入图片描述

4.3 FilterChainProxy

Spring Security 的 Servlet 支持包含在FilterChainProxy. FilterChainProxy是 Spring Security 提供的特殊功能Filter,允许Filter通过 委托给许多实例SecurityFilterChain。由于FilterChainProxy是一个 Bean,因此它通常包装在DelegatingFilterProxy中。
在这里插入图片描述

4.4 SecurityFilterChain

SecurityFilterChainFilterChainProxy 使用它来确定Filter应为当前请求调用哪些 Spring
在这里插入图片描述

4.5 Multiple SecurityFilterChain

可以有多个SecurityFilterChain的配置,FilterChainProxy决定使用哪个SecurityFilterChain。如果请求的URL是/api/messages/,它首先匹配SecurityFilterChain0的模式/api/**,因此只调用SecurityFilterChain 0。假设没有其他SecurityFilterChain实例匹配,那么将调用SecurityFilterChain n。
在这里插入图片描述

五.Spring Security自定义配置

5.1 基于内存的用户认证

实际开发的过程中,我们需要应用程序更加灵活,可以在SpringSecurity中创建自定义配置文件。

官方文档:Java自定义配置

UserDetailsService用来管理用户信息,InMemoryUserDetailsManager是UserDetailsService的一个实现,用来管理基于内存的用户信息。

5.1.1 自定义配置

@Configuration
@EnableWebSecurity//Spring项目总需要添加此注解,SpringBoot项目中不需要
public class WebSecurityConfig {

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser( //此行设置断点可以查看创建的user对象
                User
                        .withDefaultPasswordEncoder()
                        .username("huan") //自定义用户名
                        .password("password") //自定义密码
                        .roles("USER") //自定义角色
                        .build()
        );
        return manager;
    }
}

5.1.2 基于内存的用户认证流程

  • 程序启动时:
    • 创建InMemoryUserDetailsManager对象
    • 创建User对象,封装用户名密码
    • 使用InMemoryUserDetailsManager将User存入内存
  • 校验用户时:
    • SpringSecurity自动使用InMemoryUserDetailsManagerloadUserByUsername方法从内存中获取User对象
    • UsernamePasswordAuthenticationFilter过滤器中的attemptAuthentication方法中将用户输入的用户名密码和从内存中获取到的用户信息进行比较,进行用户认证

5.2 基于数据库的数据源

5.2.1 SQL

-- 创建数据库
CREATE DATABASE `security-demo`;
USE `security-demo`;

-- 创建用户表
CREATE TABLE `user`(
	`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
	`username` VARCHAR(50) DEFAULT NULL ,
	`password` VARCHAR(500) DEFAULT NULL,
	`enabled` BOOLEAN NOT NULL
);
-- 唯一索引
CREATE UNIQUE INDEX `user_username_uindex` ON `user`(`username`); 

-- 插入用户数据(密码是 "abc" )
INSERT INTO `user` (`username`, `password`, `enabled`) VALUES
('admin', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('Helen', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('Tom', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE);

5.2.2 引入依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.4.1</version>
    <exclusions>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>3.0.3</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

5.2.3 配置数据源

#MySQL数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/security-demo
spring.datasource.username=root
spring.datasource.password=123456
#SQL日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

5.2.4 实体类

@Data
public class User {

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    private String username;

    private String password;

    private Boolean enabled;

5.2.5 Mapper

@Mapper
public interface UserMapper extends BaseMapper<User> {
}
<?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.demo.securitydemo.mapper.UserMapper">

</mapper>

5.2.6 Service

public interface UserService extends IService<User> {
}

实现类

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

5.2.7 Controller

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    public UserService userService;

    @GetMapping("/list")
    public List<User> getList(){
        return userService.list();
    }
}

5.3 基于数据库的用户认证

5.3.1 基于数据库的用户认证流程

  • 程序启动时:
    • 创建DBUserDetailsManager类,实现接口 UserDetailsManager, UserDetailsPasswordService
    • 在应用程序中初始化这个类的对象
  • 校验用户时:
    • SpringSecurity自动使用DBUserDetailsManagerloadUserByUsername方法从数据库中获取User对象
    • UsernamePasswordAuthenticationFilter过滤器中的attemptAuthentication方法中将用户输入的用户名密码和从数据库中获取到的用户信息进行比较,进行用户认证

5.3.2 定义DBUserDetailsManager

public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
    
    @Resource
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        User user = userMapper.selectOne(queryWrapper);
        if (user == null) {
            throw new UsernameNotFoundException(username);
        } else {
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            return new org.springframework.security.core.userdetails.User(
                    user.getUsername(),
                    user.getPassword(),
                    user.getEnabled(),
                    true, //用户账号是否过期
                    true, //用户凭证是否过期
                    true, //用户是否未被锁定
                    authorities); //权限列表
        }
    }

    @Override
    public UserDetails updatePassword(UserDetails user, String newPassword) {
        return null;
    }

    @Override
    public void createUser(UserDetails user) {

    }

    @Override
    public void updateUser(UserDetails user) {

    }

    @Override
    public void deleteUser(String username) {

    }

    @Override
    public void changePassword(String oldPassword, String newPassword) {

    }

    @Override
    public boolean userExists(String username) {
        return false;
    }
}

5.3.3 初始化UserDetailsService

修改WebSecurityConfig中的userDetailsService方法如下

@Bean
public UserDetailsService userDetailsService() {
    DBUserDetailsManager manager = new DBUserDetailsManager();
    return manager;
}

5.4 SpringSecurity的默认配置

在WebSecurityConfig中添加如下配置

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    //authorizeRequests():开启授权保护
    //anyRequest():对所有请求开启授权保护
    //authenticated():已认证请求会自动被授权
    http
        .authorizeRequests(authorize -> authorize.anyRequest().authenticated())
        .formLogin(withDefaults())//表单授权方式
        .httpBasic(withDefaults());//基本授权方式

    return http.build();
}

5.5 添加用户功能

5.5.1 Controller

@PostMapping("/add")
public void add(@RequestBody User user){
    userService.saveUserDetails(user);
}

5.5.2 Service

UserService接口中添加方法

void saveUserDetails(User user);

UserServiceImp实现方法

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Resource
    private DBUserDetailsManager dbUserDetailsManager;

    @Override
    public void saveUserDetails(User user) {

        UserDetails userDetails = org.springframework.security.core.userdetails.User
                .withDefaultPasswordEncoder()//使用默认的密码加密方案
                .username(user.getUsername()) //自定义用户名
                .password(user.getPassword()) //自定义密码
                .build();
        dbUserDetailsManager.createUser(userDetails);

    }
    }

5.5.3 修改配置

DBUserDetailsManager中添加方法

@Override
public void createUser(UserDetails userDetails) {

    User user = new User();
    user.setUsername(userDetails.getUsername());
    user.setPassword(userDetails.getPassword());
    user.setEnabled(true);
    userMapper.insert(user);
}

5.5.4 使用Swagger测试

引入Swagger依赖

<!--swagger测试-->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
    <version>4.1.0</version>
</dependency>

Swagger测试地址:http://localhost:8080/demo/doc.html
在这里插入图片描述

5.6 密码加密算法

参考文档
明文密码:

最初,密码以明文形式存储在数据库中。但是恶意用户可能会通过SQL注入等手段获取到明文密码,或者程序员将数据库数据泄露的情况也可能发生。
Hash算法:

Spring Security的PasswordEncoder接口用于对密码进行单向转换,从而将密码安全地存储。对密码单向转换需要用到哈希算法,例如MD5、SHA-256、SHA-512等,哈希算法是单向的,只能加密,不能解密

因此,数据库中存储的是单向转换后的密码,Spring Security在进行用户身份验证时需要将用户输入的密码进行单向转换,然后与数据库的密码进行比较。

因此,如果发生数据泄露,只有密码的单向哈希会被暴露。由于哈希是单向的,并且在给定哈希的情况下只能通过暴力破解的方式猜测密码

5.6.1 密码测试

@Test
void testPassword() {

    // 工作因子,默认值是10,最小值是4,最大值是31,值越大运算速度越慢
    PasswordEncoder encoder = new BCryptPasswordEncoder(4);
    //明文:"password"
    //密文:result,即使明文密码相同,每次生成的密文也不一致
    String result = encoder.encode("password");
    System.out.println(result);

    //密码校验
    Assert.isTrue(encoder.matches("password", result), "密码不一致");
}

5.7 定义登录页面

5.7.1 创建Controller

@Controller
public class LoginController {

    @GetMapping("/login")
    public String login() {
        return "login";
    }
}

5.7.2 准备登录页面

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
    <title>登录</title>
</head>
<body>
<h1>登录</h1>
<div th:if="${param.error}">
    错误的用户名和密码.</div>

<!--method必须为"post"-->
<!--th:action="@{/login}" ,
使用动态参数,表单中会自动生成_csrf隐藏字段,用于防止csrf攻击
login: 和登录页面保持一致即可,SpringSecurity自动进行登录认证-->
<form th:action="@{/login}" method="post">
    <div>
        <!--name必须为"username"-->
        <input type="text" name="username" placeholder="用户名"/>
    </div>
    <div>
        <!--name必须为"password"-->
        <input type="password" name="password" placeholder="密码"/>
    </div>
    <input type="submit" value="登录" />
</form>
</body>
</html>

5.7.3 配置SecurityFilterChain

SecurityConfiguration:

.formLogin( form -> {
    form
        .loginPage("/login").permitAll() //登录页面无需授权即可访问
        .usernameParameter("username") //自定义表单用户名参数,默认是username
        .passwordParameter("password") //自定义表单密码参数,默认是password
        .failureUrl("/login?error") //登录失败的返回地址
        ;
}); //使用表单授权方式

六. 前后端分离

表单登录配置模块提供了successHandler()和failureHandler()两个方法,分别处理登录成功和登录失败的逻辑。其中,successHandler()方法带有一个Authentication参数,携带当前登录用户名及其角色等信息;而failureHandler()方法携带一个AuthenticationException异常参数。具体处理方式需按照系统的情况自定义。

6.1 认证流程

  • 登录成功后调用:AuthenticationSuccessHandler
  • 登录失败后调用:AuthenticationFailureHandler
    在这里插入图片描述

6.2 引入fastjson

<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.37</version>
</dependency>

6.3 认证成功的响应

6.3.1 成功结果处理

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        //获取用户身份信息
        Object principal = authentication.getPrincipal();

        //创建结果对象
        HashMap result = new HashMap();
        result.put("code", 0);
        result.put("message", "登录成功");
        result.put("data", principal);

        //转换成json字符串
        String json = JSON.toJSONString(result);

        //返回响应
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
    }
}

6.3.2 SecurityFilterChain配置

form.successHandler(new MyAuthenticationSuccessHandler()) //认证成功时的处理

6.4 认证失败响应

6.4.1 失败结果处理

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {

        //获取错误信息
        String localizedMessage = exception.getLocalizedMessage();

        //创建结果对象
        HashMap result = new HashMap();
        result.put("code", -1);
        result.put("message", localizedMessage);

        //转换成json字符串
        String json = JSON.toJSONString(result);

        //返回响应
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
    }
}

6.4.2 SecurityFilterChain配置

form.failureHandler(new MyAuthenticationFailureHandler()) //认证失败时的处理

6.5 注销响应

6.5.1 注销结果处理

public class MyLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        //创建结果对象
        HashMap result = new HashMap();
        result.put("code", 0);
        result.put("message", "注销成功");

        //转换成json字符串
        String json = JSON.toJSONString(result);

        //返回响应
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
    }
}

6.5.2 SecurityFilterChain配置

http.logout(logout -> {
    logout.logoutSuccessHandler(new MyLogoutSuccessHandler()); //注销成功时的处理
});

6.6 请求未认证的接口

6.6.1 实现AuthenticationEntryPoint接口

Servlet Authentication Architecture :: Spring Security
当访问一个需要认证之后才能访问的接口的时候,Spring Security会使用AuthenticationEntryPoint将用户请求跳转到登录页面,要求用户提供登录凭证。

public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {

        //获取错误信息
        //String localizedMessage = authException.getLocalizedMessage();

        //创建结果对象
        HashMap result = new HashMap();
        result.put("code", -1);
        result.put("message", "需要登录");

        //转换成json字符串
        String json = JSON.toJSONString(result);

        //返回响应
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
    }
}

6.6.2 SecurityFilterChain配置

//错误处理
http.exceptionHandling(exception  -> {
    exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());//请求未认证的接口
});

6.7 跨域

跨域全称是跨域资源共享(Cross-Origin Resources Sharing,CORS),它是浏览器的保护机制,只允许网页请求统一域名下的服务,同一域名指=>协议、域名、端口号都要保持一致,如果有一项不同,那么就是跨域请求。在前后端分离的项目中,需要解决跨域的问题。

在SpringSecurity中解决跨域很简单,在配置文件中添加如下配置即可

//跨域
http.cors(withDefaults());

七.身份认证

7.1 身份认证信息

在这里插入图片描述

  1. SecurityContextHolder:SecurityContextHolder 是 Spring Security 存储已认证用户详细信息的地方。
  2. SecurityContext:SecurityContext 是从 SecurityContextHolder 获取的内容,包含当前已认证用户的 Authentication 信息。
  3. Authentication:Authentication 表示用户的身份认证信息。它包含了用户的Principal、Credential和Authority信息。
  4. Principal:表示用户的身份标识。它通常是一个表示用户的实体对象,例如用户名。Principal可以通过Authentication对象的getPrincipal()方法获取。
  5. Credentials:表示用户的凭证信息,例如密码、证书或其他认证凭据。Credential可以通过Authentication对象的getCredentials()方法获取。
  6. GrantedAuthority:表示用户被授予的权限

总结起来,SecurityContextHolder用于管理当前线程的安全上下文,存储已认证用户的详细信息,其中包含了SecurityContext对象,该对象包含了Authentication对象,后者表示用户的身份验证信息,包括Principal(用户的身份标识)和Credential(用户的凭证信息)。

在Spring Security框架中,SecurityContextHolder、SecurityContext、Authentication、Principal和Credential是一些与身份验证和授权相关的重要概念。它们之间的关系如下:

7.2 会话并发处理

后登录的账号会使先登录的账号失效

public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {

        //创建结果对象
        HashMap result = new HashMap();
        result.put("code", -1);
        result.put("message", "该账号已从其他设备登录");

        //转换成json字符串
        String json = JSON.toJSONString(result);

        HttpServletResponse response = event.getResponse();
        //返回响应
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
    }
}

实现处理器接口

实现接口SessionInformationExpiredStrategy

public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {

        //创建结果对象
        HashMap result = new HashMap();
        result.put("code", -1);
        result.put("message", "该账号已从其他设备登录");

        //转换成json字符串
        String json = JSON.toJSONString(result);

        HttpServletResponse response = event.getResponse();
        //返回响应
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
    }
}

SecurityFilterChain配置

//会话管理
http.sessionManagement(session -> {
    session
        .maximumSessions(1)
        .expiredSessionStrategy(new MySessionInformationExpiredStrategy());
});

八.授权

授权管理的实现在SpringSecurity中非常灵活,可以帮助应用程序实现以下两种常见的授权需求:

  • 用户-权限-资源:例如张三的权限是添加用户、查看用户列表,李四的权限是查看用户列表

  • 用户-角色-权限-资源:例如 张三是角色是管理员、李四的角色是普通用户,管理员能做所有操作,普通用户只能查看信息

基于request的授权

用户-权限-资源

需求:

  • 具有USER_LIST权限的用户可以访问/user/list接口
  • 具有USER_ADD权限的用户可以访问/user/add接口
配置权限

SecurityFilterChain

//开启授权保护
http.authorizeRequests(
        authorize -> authorize
    			//具有USER_LIST权限的用户可以访问/user/list
                .requestMatchers("/user/list").hasAuthority("USER_LIST")
    			//具有USER_ADD权限的用户可以访问/user/add
    			.requestMatchers("/user/add").hasAuthority("USER_ADD")
                //对所有请求开启授权保护
                .anyRequest()
                //已认证的请求会被自动授权
                .authenticated()
        );
授予权限

DBUserDetailsManager中的loadUserByUsername方法:

Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(()->"USER_LIST");
authorities.add(()->"USER_ADD");

/*authorities.add(new GrantedAuthority() {
    @Override
    public String getAuthority() {
        return "USER_LIST";
    }
});
authorities.add(new GrantedAuthority() {
    @Override
    public String getAuthority() {
        return "USER_ADD";
    }
});*/
请求未授权的接口

SecurityFilterChain

http.exceptionHandling(exception  -> {
    exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());//请求未认证的接口
    exception.accessDeniedHandler((request, response, e)->{ //请求未授权的接口

        //创建结果对象
        HashMap result = new HashMap();
        result.put("code", -1);
        result.put("message", "没有权限");

        //转换成json字符串
        String json = JSON.toJSONString(result);

        //返回响应
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
    });
});

用户-角色-资源

**需求:**角色为ADMIN的用户才可以访问/user/**路径下的资源

配置角色

SecurityFilterChain

//开启授权保护
http.authorizeRequests(
        authorize -> authorize
                //具有管理员角色的用户可以访问/user/**
                .requestMatchers("/user/**").hasRole("ADMIN")
                //对所有请求开启授权保护
                .anyRequest()
                //已认证的请求会被自动授权
                .authenticated()
);
授予角色

DBUserDetailsManager中的loadUserByUsername方法:

return org.springframework.security.core.userdetails.User
        .withUsername(user.getUsername())
        .password(user.getPassword())
        .roles("ADMIN")
        .build();

在这里插入图片描述

用户-角色-权限-资源

在这里插入图片描述

RBAC基于角色的权限访问控制(Role-Based Access Control)是商业系统中最常见的权限管理技术之一。RBAC是一种思想,任何编程语言都可以实现,其成熟简单的控制思想 越来越受广大开发人员喜欢。在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。在一个组织中,角色是为了完成各种工作而创造,用户则依据它的责任和资格来被指派相应的角色,用户可以很容易地从一个角色被指派到另一个角色。角色可依新的需求和系统的合并而赋予新的权限,而权限也可根据需要而从某角色中回收。

基于方法的授权

开启方法授权

DBUserDetailsManager中的loadUserByUsername方法:
在配置文件中添加如下注解
在这里插入图片描述

@EnableMethodSecurity

给用户授予角色和权限

//用户必须有 ADMIN 角色 并且 用户名是 admin 才能访问此方法
@PreAuthorize("hasRole('ADMIN') and authentication.name == 'admim'")
@GetMapping("/list")
public List<User> getList(){
    return userService.list();
}

//用户必须有 USER_ADD 权限 才能访问此方法
@PreAuthorize("hasAuthority('USER_ADD')")
@PostMapping("/add")
public void add(@RequestBody User user){
    userService.saveUserDetails(user);
}

常用授权注解

//用户必须有 ADMIN 角色 并且 用户名是 admin 才能访问此方法
@PreAuthorize("hasRole('ADMIN') and authentication.name == 'admim'")
@GetMapping("/list")
public List<User> getList(){
    return userService.list();
}

//用户必须有 USER_ADD 权限 才能访问此方法
@PreAuthorize("hasAuthority('USER_ADD')")
@PostMapping("/add")
public void add(@RequestBody User user){
    userService.saveUserDetails(user);
}

九.Spring Security OAuth2

9.1 OAuth2介绍

官网:https://oauth.net/2/
一种用于授权的开放标准,用于允许用户授权第三方应用访问其受保护的资源,而无需将其凭据直接提供给第三方应用。OAuth 2.0通过使用访问令牌来实现授权,该令牌由授权服务器颁发给第三方应用,以便访问用户受保护的资源。OAuth 2.0还提供了一种用于验证用户身份和授权的流程,包括重定向用户到授权服务器以获取授权码,然后交换该授权码以获取访问令牌。OAuth 2.0已经成为许多互联网服务和应用程序的标准授权机制,包括社交媒体平台、API服务和移动应用程序。

9.1.1 角色介绍

  1. 资源所有者(Resource Owner):即用户,资源的拥有人,想要通过客户应用访问资源服务器上的资源。
  2. 客户应用(Client):通常是一个Web或者无线应用,它需要访问用户的受保护资源。
  3. 资源服务器(Resource Server):存储受保护资源的服务器或定义了可以访问到资源的API,接收并验证客户端的访问令牌,以决定是否授权访问资源。
  4. 授权服务器(Authorization Server):负责验证资源所有者的身份并向客户端颁发访问令牌。
    在这里插入图片描述

9.1.2 OAuth2怎么用

OAuth2是目前最流行的授权协议,用来授权第三方应用,获取用户数据。 举个例子:快递员想要进入小区,有3种方式。1是业主远程开门,2是业主告诉门禁密码,3是使用令牌(Oauth2)。
在这里插入图片描述
令牌和密码的区别:令牌相当于火车票,密码相当于是钥匙。
● 令牌是短期的,自动失效。密码是长期有效。
● 令牌是可以撤销的,撤销立即生效。密码一般不允许他们撤销。
● 令牌有权限范围,如车票座位为10车A15座。密码一般是完整权限。

第三方登录演示(网易云客户端利用QQ扫码登录)
在这里插入图片描述
网易云客使用QQ扫码登录中Oauth2协议各个角色扮演者
● Rrsource Owner: 用户
● Client: 网易云
● Authorization Server: QQ
● Resource Server: QQ
● User Agent: 浏览器

9.1.3 OAuth2的授权模式

具体可以参考下面两个大佬的文章:
RFC6749:

RFC 6749 - The OAuth 2.0 Authorization Framework (ietf.org)

阮一峰:
OAuth 2.0 的四种方式 - 阮一峰的网络日志
Oauth2授权模式:
● 授权码模式:最完整和严谨的授权模式,第三方平台登录都是该模式。安全性最高。
● 简化模式:省略授权码阶段,客户端是纯静态页面采用该模式。安全性高。
● 密码模式:把用户名密码告诉客户端,对客户端高度信任,比如客户端和认证服务器是同一公司。安全性一般。
● 客户端模式:直接因客户端名义申请令牌,很少有。安全性最差。

9.2 为什么要用OAuth2

● cookie是不能跨域的,前后端分离分布式架构实现多系统SSO非常困难。
● 移动端应用没有cookie,所以对于移动端支持不友好。
● token基于header传递,部分解决了CSRF攻击。
● token比sessionID大,客户端存储在Local Storage中,可以直接被JS读取

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

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

相关文章

走进 Mybatis 内核世界:理解原理,释放更多生产力

目录 一、MyBatis 特点 二、 接口绑定实现原理 三、SpringBoot 加载 MyBatis 源码分析 四、MyBatis 执行性 五、MyBatis 分页原理 5.1 逻辑分页(内存分页) 5.2 物理分页 六、MyBatis 缓存 6.1 一级缓存 6.2 二级缓存 MyBatis 是一款优秀的持久层框架&#xff0c;它支持自…

网站引入 Prism,使得代码高亮显示,并一键复制代码块

曾几何时&#xff0c;苦恼如何将本地写好的博文&#xff0c;更好的展示读者屏幕前&#xff1f;若只是简简单单的文章&#xff0c;其实还是很好的解决它的&#xff01;可是&#xff0c;像我们这样写技术文章&#xff08;有点牵强&#xff09;的&#xff0c;在文章内容嵌入部分代…

【文献分享】Quantum Self-Consistent Ab-Initio Lattice Dynamics

题目&#xff1a;Quantum Self-Consistent Ab-Initio Lattice Dynamics 链接&#xff1a;Redirecting 量子自洽从头算晶格动力学 量子自洽Ab-Initio晶格动力学软件包&#xff08;QSCAILD&#xff09;是一个python库&#xff0c;用于计算晶体中与温度相关的有效2级和3级原子间…

【Java多线程】多线程的三种实现方式和多线程常用方法

目录 1、多线程的三种实现方式 1.1、继承Thread类的方式进行实现 1.2、实现Runnable接口的方式进行实现 1.3、利用Callable接口和Future接口方式实现 1.4、三种实现方式的优缺点 2、多线程常用方法 1、多线程的三种实现方式 在main()方法中&#xff0c;你可以创建和启动…

STL —— string(2)

本篇文章主要讲解string的用法。 目录 1. 迭代器&#xff08;Iterators&#xff09; 1.1 begin() 和 end() 1.2 rbegin() 和 rend() 2. 容量操作&#xff08;capacity&#xff09; 2.1 size()、length()、maxsize() 2.2 capacity() 2.3 empty()、clear() 2.4 reserve…

罗德与施瓦茨联合广和通全面验证RedCap模组FG132系列先进性能

近日&#xff0c;罗德与施瓦茨联合广和通完成Redcap(Reduce Capability)功能和性能验证。本次测试使用R&SCMX500 OBT(One Box Tester)无线通信测试仪&#xff0c;主要验证广和通RedCap模组FG132系列射频性能以及IP层吞吐量&#xff0c;包括RedCap上下行吞吐量和射频指标如矢…

【技巧】ChatGPT Prompt 提示语大全

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 主要来自&#xff1a;https://github.com/f/awesome-chatgpt-prompts ChatGPT SEO提示 Contributed by: StoryChief AI Reference: 7 Powerful ChatGPT Prompts to Create SEO Content Faster 供稿人&#xff1a;…

ruoyi-nbcio-plus基于vue3的flowable增加开始节点的表单绑定修改

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…

Python文件读写操作

文件操作注意点 注意点&#xff1a; 1. for line in file --> 会将偏移量移到末尾 2. buffering1 --> 缓冲区中遇到换行就刷新&#xff0c;即向磁盘中写入 3. 读操作结束后&#xff0c;文本偏移量就会移动到读操作结束位置 """编写一个程序,循环不停的写入…

快速区分清楚图形渲染中的AABB,KD树和BVH这些概念

快速区分清楚图形渲染中的AABB&#xff0c;KD树和BVH这些概念 主要想形象去区分好这些术语&#xff0c;目的是扫盲&#xff0c;先开好坑&#xff0c;内容持续填充。 0.先摆出这些词的全称 AABB&#xff1a; 原名&#xff1a;axis aligned bounding box&#xff1b;中文直译名…

Java语法学习八之认识String类

String类的重要性 在C语言中已经涉及到字符串了&#xff0c;但是在C语言中要表示字符串只能使用字符数组或者字符指针&#xff0c;可以使用标准库提供的字符串系列函数完成大部分操作&#xff0c;但是这种将数据和操作数据方法分离开的方式不符合面相对象的思想&#xff0c;而…

高效的Gitlab Flow最佳实践

文章目录 一、git flow二、github flow三、gitlab flow四、基于gitlab flow的最佳实践1.语义化版本号2.测试发布3.bug修复 参考 业界包含三种flow&#xff1a; Git flowGithub flowGitlab flow 三种工作流程&#xff0c;有一个共同点&#xff1a;都采用"功能驱动式开发&…

计算机二级Python基础操作题

题目来源&#xff1a;计算机二级Python半个月抱佛脚大法&#xff08;内呈上真题版&#xff09; - 知乎 第4&#xff0c;5&#xff0c;6&#xff0c;7&#xff0c;9&#xff0c;10&#xff0c;11套 1. 基础题1 sinput() print("{:\"^30x}".format(eval(s))) b …

xilinx linux AXI GPIO 驱动学习

vivado工程 vivado 配置一个 AXI GPIO&#xff0c; 全输出&#xff0c;宽度为1 设备树解读 生成的对应pl.dtsi设备树文件如下 axi_gpio: gpio40020000 {#gpio-cells <2>;clock-names "s_axi_aclk";clocks <&clkc 15>;compatible "xlnx,…

海外盲盒App开发:探索惊喜与乐趣的跨国新体验

在全球化日益加速的今天&#xff0c;人们对新鲜、有趣、充满惊喜的体验需求不断增长。盲盒作为一种充满神秘与乐趣的玩法&#xff0c;正迅速在全球范围内走红。为了满足广大海外用户的盲盒体验需求&#xff0c;我们致力于开发一款海外盲盒App&#xff0c;为用户带来跨国界的惊喜…

C#,图论与图算法,计算无向连通图中长度为n环的算法与源代码

1 无向连通图中长度为n环 给定一个无向连通图和一个数n,计算图中长度为n的环的总数。长度为n的循环仅表示该循环包含n个顶点和n条边。我们必须统计存在的所有这样的环。 为了解决这个问题,可以有效地使用DFS(深度优先搜索)。使用DFS,我们可以找到特定源(或起点)的长度…

高精度AI火灾烟雾检测算法,助力打造更加安全的楼宇环境

一、方案背景 近日&#xff0c;南京居民楼火灾事故导致15人死亡的新闻闹得沸沸扬扬&#xff0c;这一事件又激起了大家对楼宇火灾隐患的进一步担忧。事后我们除了思考政府、消防及物业部门应对此事的解决办法&#xff0c;我们还应该思考如何利用现有的技术帮助人们减少此类事情的…

手撕算法-无重复字符的最长子串

描述 分析 滑动窗口&#xff0c;记录窗口中的所有出现的字符&#xff0c;然后窗口左边界固定&#xff0c;右边界右滑&#xff0c;如果&#xff0c;窗口中不存在新的字符&#xff0c;则右滑成功&#xff0c;否则左边界右滑&#xff0c;直到窗口中不存在右边界的值。 描述感觉不…

Linux初学(八)磁盘管理

一、磁盘管理 1.1 简介 磁盘的工作原理&#xff1a; 添加磁盘对磁盘进行分区格式化磁盘挂载和使用磁盘 磁盘的类型&#xff1a; 固态机械 磁盘的接口类型&#xff1a; IDESTSTSCSI 磁盘工作的原理&#xff1a; 磁盘&#xff0c;特别是硬盘&#xff0c;和内存不同&#xff0c;…

【网络基础】网络层基本协议介绍

目录 一、IP数据包 1.1 网络层的功能 1.2 IP数据包格式 二、ICMP协议介绍 2.1 作用 2.2 常用命令 2.2.1 Ping命令 2.2.2 tracert命令 2.3 广播域 三、ARP协议介绍 3.1 作用 3.2 原理 一、IP数据包 1.1 网络层的功能 定义了基于IP协议的逻辑地址&#xff0c;就是I…