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 {@Beanpublic 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 {@Resourcepublic 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 {@Resourceprivate UserMapper userMapper;@Overridepublic 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); //权限列表}}@Overridepublic UserDetails updatePassword(UserDetails user, String newPassword) {return null;}@Overridepublic void createUser(UserDetails user) {}@Overridepublic void updateUser(UserDetails user) {}@Overridepublic void deleteUser(String username) {}@Overridepublic void changePassword(String oldPassword, String newPassword) {}@Overridepublic 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 {@Resourceprivate DBUserDetailsManager dbUserDetailsManager;@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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() {@Overridepublic String getAuthority() {return "USER_LIST";}
});
authorities.add(new GrantedAuthority() {@Overridepublic 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读取

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

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

相关文章

Java 基础学习(二十)Maven、XML与WebServer

1 Maven 1.1 什么是Maven 1.1.1 Maven概述 Maven是一种流行的构建工具&#xff0c;用于管理Java项目的构建&#xff0c;依赖管理和项目信息管理。它使用XML文件来定义项目结构和构建步骤&#xff0c;并使用插件来执行各种构建任务。Maven可以自动下载项目依赖项并管理它们的…

iOS模拟器 Unable to boot the Simulator —— Ficow笔记

本文首发于 Ficow Shen’s Blog&#xff0c;原文地址&#xff1a; iOS模拟器 Unable to boot the Simulator —— Ficow笔记。 内容概览 前言终结模拟器进程命令行改权限清除模拟器缓存总结 前言 iOS模拟器和Xcode一样不靠谱&#xff0c;问题也不少。&#x1f602; 那就有病治…

探索超融合服务器:是否助力企业飞跃数字化挑战?

在数字化不断深化的今天&#xff0c;企业面临着前所未有的转型压力和机遇。IT基础设施作为支撑企业运营的重要基石&#xff0c;其性能、效率及可管理性都直接关系到企业的竞争力。超融合服务器作为一种创新的IT架构&#xff0c;被许多业内专家视为应对现代业务挑战的有效手段。…

【LabVIEW FPGA入门】FPGA不同传递数据方法比较

数据共享方法的选择应基于应用的需要。根据应用程序的重要特性&#xff0c;所讨论的任何一种方法都可能是合适的。 传输方法FPGA资源损耗&#xff1f;不同时钟源之间传递数据&#xff1f;新数据通知&#xff1f;常见用途变量逻辑片是是否提取最新数据存储器存储器是否否提取最新…

思腾合力受邀出席文化和旅游虚拟现实应用推广交流活动并作主题演讲

3月21日&#xff0c;由文化和旅游部产业发展司主办&#xff0c;中国信息通信研究院、北京市石景山区文化和旅游局、中国动漫集团有限公司承办的“数字赋能文旅场景建设行动——文化和旅游虚拟现实应用推广交流活动”在北京首钢一高炉SoReal科幻乐园成功举办。 思腾合力CMO徐莉受…

Kubernetes kafka系列 | Strimzi 快速部署kafka集群 (可外部通信)

一、Strimzi介绍 Strimzi 是一个用于 Apache Kafka 在 Kubernetes 上部署和管理的开源项目。它提供了一组 Kubernetes 自定义资源定义(Custom Resource Definitions,CRDs)、控制器和操作符,使得在 Kubernetes 环境中轻松地部署、管理和操作 Kafka 集群成为可能。Strimzi 项…

dubbo 源码系列之-集群三板斧---负载均衡(-)

dubbo 源码系列之-负载均衡 概述核心接口 LoadBalanceDubbo 提供了 5 种负载均衡实现&#xff0c;分别是&#xff1a;LoadBalance 接口AbstractLoadBalance ConsistentHashLoadBalance 一致性hash1. 一致性 Hash 简析1.0 hash 算法2.0 一致性Hash算法3.0 一致性hash算法 引入槽…

米多论文怎么用 #学习方法#职场发展

米多论文是一款专为论文写作者设计的工具&#xff0c;可以帮助用户进行论文的查重和降重。它的使用非常简单&#xff0c;只需将需要检测的论文内容粘贴到相应的输入框中&#xff0c;点击“检测”按钮即可开始查重。米多论文通过比对用户提交的论文和互联网上已经存在的内容&…

STM32 使用gcc编译介绍

文章目录 前言1. keil5下的默认编译工具链用的是哪个2. Arm编译工具链和GCC编译工具链有什么区别吗&#xff1f;3. Gcc交叉编译工具链的命名规范4. 怎么下载gcc-arm编译工具链参考资料 前言 我们在STM32上进行开发时&#xff0c;一般都是基于Keil5进行编译下载&#xff0c;Kei…

阿里云服务器地域怎么选择?可用区是什么?

阿里云服务器地域和可用区怎么选择&#xff1f;地域是指云服务器所在物理数据中心的位置&#xff0c;地域选择就近选择&#xff0c;访客距离地域所在城市越近网络延迟越低&#xff0c;速度就越快&#xff1b;可用区是指同一个地域下&#xff0c;网络和电力相互独立的区域&#…

2024 3.16~3.22 周报

一、引入——DenseNet DenseNet架构&#xff1a;以前馈的方式&#xff0c;将每层与每层都连接起来&#xff0c;它建立的是前面所有层与后面层的密集连接&#xff08;dense connection&#xff09;。 为了确保最大程度的信息在网络中各层之间流动&#xff0c;将所有层彼此直接连…

移相全桥DC-DC变换器

本篇将基于PPEC-86CA3A移相全桥数字电源控制芯片以及PPEC Workbench开发软件带领大家进行实际移相全桥DC-DC变换器的设计与开发 。 一、移相全桥变换器设计与开发 1、外围电路设计与硬件平台搭建 1&#xff09;外围电路设计 这里给出了PPEC-86CA3A移相全桥数字电源控制芯片的…