“全面揭秘:如何利用SpringSecurity和OAuth2构建坚不可摧的权限管理系统——从基础到高级实践的完整指南”
🎼个人主页:【Y小夜】
😎作者简介:一位双非学校的大三学生,编程爱好者,
专注于基础和实战分享,欢迎私信咨询!
🎆入门专栏:🎇【MySQL,Java基础,Rust】
🎈热门专栏:🎊【Python,Javaweb,Springboot】
感谢您的点赞、关注、评论、收藏、是对我最大的认可和支持!❤️
目录
🎄Spring Security
🎏基本介绍
🎏入门身份验证案例
🍔注意事项
🍟@{logout}的作用
🍟页面样式无法加载的问题
🎏默认提供什么?
🎏底层原理
🍔DelegatingFilterProxy(委托过滤器代理)
🍔FilterChainProxy (过滤器链代理)
🍔SecurityFilterChain
🍔Multiple SecurityFilterChain
🎏启动与运行
🍔DefaultSecurityFilterChain
🍔SecurityProperties
🎏基于内存的用户认证
🍔创建自定义配置
🍔底层原理
🎏基于数据库的用户认证
🍔 执行Sql文件
🍔 引入依赖
🍔 配置数据源
🍔实体类
🍔Mapper
🍔Service
🍔Controller
🍔测试
🎏基于数据库的用户认证
🍔认证流程
🍔定义DBUserDetailsManager
🎏SpringSecurity的默认配置
🎏添加用户功能
🍔Controller
🍔Service
🍔修改配置
🍔使用Swagger测试
🍔关闭csrf攻击防御
🎏密码加密算法
🍔一些加密的方式
🍔Hash算法
🍔彩虹表
🍔加盐密码
🍔自适应单向函数
🎏密码加密测试
🎏DelegatingPasswordEncoder
🎏自定义登录页面
🍔创建登录Controller
🍔登录页面
🍔配置SecurityFilterChain
🎏用户认证流程(前后端分离)
🎏引入fastjson
🎏认证成功的响应
🍔成功结果处理
🍔SecurityFilterChain配置
🎏认证失败响应
🍔失败结果处理
🍔SecurityFilterChain配置
🎏注销响应
🍔注销结果处理
🍔SecurityFilterChain配置
🎏请求未认证的接口
🍔SecurityFilterChain配置
🎏跨域
🎏用户认证信息
🍔在Controller中获取用户信息
🎏会话并发处理
🍔实现处理器接口
🍔SecurityFilterChain配置
🎏基于request的授权
🍔用户-权限-资源
🍔用户-角色-资源
🍔用户-角色-权限-资源
🎏基于方法的授权
🍔开启方法授权
🍔给用户授予角色和权限
🍔常用授权注解
🎄OAuth2
🎏简介
🍔是什么OAuth2最简向导:
🍔角色
🍔使用场景
🍟开放系统间授权
🍟现代微服务安全
🍟企业内部应用认证授权
🍔授权模式
🍟授权码
🍟隐藏式
🍟密码式
🍟凭证式
🍔授权类型的选择
🎏Spring中的OAuth2
🍔Spring中的实现
🎏相关依赖
🎏实战
🍔创建应用
🍔创建项目
🍔配置OAuth客户端属性
🍔创建Controller
🍔创建html页面
🍔启动应用程序
🎄Spring Security
🎏基本介绍
这里咱们先看官网:Spring Security :: Spring Security
这句话大概是说:Spring Security 是一个框架,它可以提供:
-
身份认证(authentication):身份认证是验证谁正在访问系统资源,判断用户是否为合法用户。认证用户的常见方式是要求用户输入用户名和密码。
-
授权(authorization):用户进行身份认证后,系统会控制谁能访问哪些资源,这个过程叫做授权。用户无法访问没有权限的资源。
-
防御常见攻击(protection against common attacks):CSRF、HTTP Headers、HTTP Requests
🎏入门身份验证案例
咱们先去它的仓库中看一下:spring-projects/spring-security-samples
这里我使用的是用传统的servlet实现的实例,在这个目录下,可以找到,所给的实例代码
接下来创建一个springboot工程,并选择者三个依赖
创建一个controller包,然后创建一个IndexController类
package com.yan.securitydemo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class IndexController { @GetMapping("/") public String index(){ return "index"; } }
然后再资源目录下,创建一个index.html页面
Hello Security!
Hello Security
Log Out直接运行文件,这里没有配置路径,默认访问8080端口,当我们访问8080端口时,会弹出 登录页,这是springsecurity自带的安全验证,用户名为:user,密码在控制台上自动输出。
然后我们进行登录,然后点击Log out,进行退出
🍔注意事项
🍟@{logout}的作用
通过使用@{logout},Thymeleaf将自动处理生成正确的URL,以适应当前上下文路径。这样无论应用程序部署在哪个上下文路径下,生成的URL下,生成的URl都是正确的指向注销功能。
例如:如果我们在配置文件中添加如下内容
server.servlet.context-path=/demo
那么@{/logout}可以自动处理url为正确的相对路径
但是如果是普通的/logout,路径就会不正确
🍟页面样式无法加载的问题
其实页面并不是上面那个样子,而是有一个css样式,页面样式bootstrap.min.css是一个CDN地址,需要通过科学上网的方式访问
否则你的登录页会加载很久,并且看到的页面是上面那样的,这里其实需要网络代理进行访问(俗称梯子),关于怎么搞,自己去找教程,这里不多介绍,开启后,就会出现下面的样式(登录按钮没有样式文件渲染,但是不影响登录功能的执行)
🎏默认提供什么?
-
保护应用程序URL,要求对应用程序的任何交互进行身份验证。
-
程序启动时生成一个默认用户“user”。
-
生成一个默认的随机密码,并将此密码记录在控制台上。
-
生成默认的登录表单和注销页面。
-
提供基于表单的登录和注销流程。
-
对于Web请求,重定向到登录页面;
-
对于服务请求,返回401未经授权。
-
处理跨站请求伪造(CSRF)攻击。
-
处理会话劫持攻击。
-
写入Strict-Transport-Security以确保HTTPS。
-
写入X-Content-Type-Options以处理嗅探攻击。
-
写入Cache Control头来保护经过身份验证的资源。
-
写入X-Frame-Options以处理点击劫持攻击。
🎏底层原理
底层原理,可以先去看官网:
Architecture :: Spring Security
Spring Security之所以默认帮助我们做了那么多事情,它的底层原理是传统的Servlet过滤器
因为我们主要是使用的Springboot,SpringMVC、Spring的方式使用的,所以我们渴望将这些Filter对象转化为Bean对象进行管理。
🍔DelegatingFilterProxy(委托过滤器代理)
概念:DelegatingFilterProxy 是 Spring Security 提供的一个 Filter 实现,可以在 Servlet 容器和 Spring 容器之间建立桥梁。通过使用 DelegatingFilterProxy,这样就可以将Servlet容器中的 Filter 实例放在 Spring 容器中管理。
🎏
🍔FilterChainProxy (过滤器链代理)
概念:复杂的业务中不可能只有一个过滤器。因此FilterChainProxy是Spring Security提供的一个特殊的Filter,它允许通过SecurityFilterChain将过滤器的工作委托给多个Bean Filter实例。
🍔SecurityFilterChain
概念:SecurityFilterChain 被 FilterChainProxy 使用,负责查找当前的请求需要执行的Security Filter列表。
🍔Multiple SecurityFilterChain
概念:可以有多个SecurityFilterChain的配置,FilterChainProxy决定使用哪个SecurityFilterChain。如果请求的URL是/api/messages/,它首先匹配SecurityFilterChain0的模式/api/**,因此只调用SecurityFilterChain 0。假设没有其他SecurityFilterChain实例匹配,那么将调用SecurityFilterChain n。
🎏启动与运行
🍔DefaultSecurityFilterChain
其实Java有默认的securityFilterChain,我们现在来看一下:
双击shift,输入DefaultSecurityFilterChain,进行搜索,然后可以找到源码
当我们debug时,可以看到 这里面包含十五种过滤器,
🍔SecurityProperties
默认情况下Spring Security将初始的用户名和密码存在了SecurityProperties类中。这个类中有一个静态内部类User,配置了默认的用户名(name = "user")和密码(password = uuid)
可以去看一下源码:
这个是可以自己修改密码的,SpringBoot的配置文件中:在application.properties中配置自定义用户名和密码:
spring.security.user.name=用户名 spring.security.user.password=密码
🎏基于内存的用户认证
🍔创建自定义配置
老样子,先看官方文档:Java Configuration :: Spring Security
首先创建一个配置类
package com.yan.securitydemo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; @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; } }
UserDetailsService用来管理用户信息,InMemoryUserDetailsManager是UserDetailsService的一个实现,用来管理基于内存的用户信息。
咱们可以看一下UserDetailService接口的源码(选中接口源码,然后点ctrl+h)
然后这里用户名和密码已经改了,自己可以去测试一下。
🍔底层原理
-
程序启动时:
-
创建InMemoryUserDetailsManager对象
-
创建User对象,封装用户名密码
-
使用InMemoryUserDetailsManager将User存入内存
-
-
校验用户时:
-
SpringSecurity自动使用InMemoryUserDetailsManager的loadUserByUsername方法从内存中获取User对象
-
在UsernamePasswordAuthenticationFilter过滤器中的attemptAuthentication方法中将用户输入的用户名密码和从内存中获取到的用户信息进行比较,进行用户认证
🎏基于数据库的用户认证
🍔 执行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);
🍔 引入依赖
mysql mysql-connector-java 8.0.30 com.baomidou mybatis-plus-boot-starter 3.5.4.1 org.mybatis mybatis-spring org.mybatis mybatis-spring 3.0.3 org.projectlombok lombok
🍔 配置数据源
#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
🍔实体类
@Data public class User { @TableId(value = "id", type = IdType.AUTO) private Integer id; private String username; private String password; private Boolean enabled; }
这里设置id字段为自增字段。
🍔Mapper
接口
package com.atguigu.securitydemo.mapper; @Mapper public interface UserMapper extends BaseMapper { }
xml
resources/mapper/UserMapper.xml
🍔Service
接口
package com.atguigu.securitydemo.service; public interface UserService extends IService { }
实现
package com.atguigu.securitydemo.service.impl; @Service public class UserServiceImpl extends ServiceImpl implements UserService { }
🍔Controller
package com.atguigu.securitydemo.controller; @RestController @RequestMapping("/user") public class UserController { @Resource public UserService userService; @GetMapping("/list") public List getList(){ return userService.list(); } }
🍔测试
然后让我们开始测试:
这里不出意外的话要出意外了。其实按这样写,会出现一个:org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'ddlApplicationRunner' is expected to be of type 'org.springframework.boot.Runner' but was actually of type 'org.springframework.beans.factory.support.NullBean'的错误。错误原因是因为MyBatis plus和Springboot3的版本不兼容。这里我们将MyBatis plus的依赖修改一下
com.baomidou mybatis-plus-spring-boot3-starter 3.5.7
接下来再次测试,输入:localhost:8080/user/list,输入用户名和密码,这里使用的是上面自定义配置里面的用户名和密码,登入后就可以看到信息了。
🎏基于数据库的用户认证
🍔认证流程
-
程序启动时:
-
创建DBUserDetailsManager类,实现接口 UserDetailsManager, UserDetailsPasswordService
-
在应用程序中初始化这个类的对象
-
-
校验用户时:
-
SpringSecurity自动使用DBUserDetailsManager的loadUserByUsername方法从数据库中获取User对象
-
在UsernamePasswordAuthenticationFilter过滤器中的attemptAuthentication方法中将用户输入的用户名密码和从数据库中获取到的用户信息进行比较,进行用户认证
🍔定义DBUserDetailsManager
这里可以自己仿照着 InMemoryUserDetailsManager 这个类比葫芦画瓢。这里就不多将了,我把主要方法发给出来
QueryWrapper queryWrapper = new QueryWrapper(); queryWrapper.eq("username", username); User user = userMapper.selectOne(queryWrapper); if (user == null) { throw new UsernameNotFoundException(username); } else { Collection authorities = new ArrayList(); return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), user.getEnabled(), true, //用户账号是否过期 true, //用户凭证是否过期 true, //用户是否未被锁定 authorities); //权限列表 }
🍔初始化配置类
修改WebSecurityConfig中的userDetailsService方法如下
@Bean public UserDetailsService userDetailsService() { DBUserDetailsManager manager = new DBUserDetailsManager(); return manager; }
或者直接在DBUserDetailsManager类上添加@Component注解
然后启动程序,用户名和密码都是数据库中的。
🎏SpringSecurity的默认配置
其实如果我们什么都不配置的话,其实会有一个默认的配置
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { //authorizeRequests():开启授权保护 //anyRequest():对所有请求开启授权保护 //authenticated():已认证请求会自动被授权 http .authorizeRequests(authorize -> authorize.anyRequest().authenticated()) .formLogin(withDefaults())//表单授权方式 .httpBasic(withDefaults());//基本授权方式 return http.build(); }
🎏添加用户功能
这里现在controller中写入添加用户的方法
🍔Controller
@PostMapping("/add") public void add(@RequestBody User user){ userService.saveUserDetails(user); }
🍔Service
void saveUserDetails(User user);
UserServiceImpl实现中添加方法
@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); }
🍔修改配置
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); }
🍔使用Swagger测试
com.github.xiaoymin knife4j-openapi3-jakarta-spring-boot-starter 4.1.0
登录后访问:http://localhost:8080/doc.html,找到添加的方法,用户数据添加
{ "username": "wangwu", "password": "password" }
不过这里会报一个403错误,意思是没有权限访问
这是因为Springsecurity 默认开启 csrf攻击防御功能的。
🍔关闭csrf攻击防御
//关闭csrf攻击防御 http.csrf((csrf) -> { csrf.disable(); });
重试上面的操作添加成功了!
🎏密码加密算法
老样子,请先去看官方文档:Password Storage :: Spring Security
🍔一些加密的方式
明文密码:这种方式最害怕的是用户SQL注入。
最初,密码以明文形式存储在数据库中。但是恶意用户可能会通过SQL注入等手段获取到明文密码,或者程序员将数据库数据泄露的情况也可能发生。
🍔Hash算法
Spring Security的PasswordEncoder接口用于对密码进行单向转换,从而将密码安全地存储。对密码单向转换需要用到哈希算法,例如MD5、SHA-256、SHA-512等,哈希算法是单向的,只能加密,不能解密。
因此,数据库中存储的是单向转换后的密码,Spring Security在进行用户身份验证时需要将用户输入的密码进行单向转换,然后与数据库的密码进行比较。
因此,如果发生数据泄露,只有密码的单向哈希会被暴露。由于哈希是单向的,并且在给定哈希的情况下只能通过暴力破解的方式猜测密码。
🍔彩虹表
恶意用户创建称为彩虹表的查找表。
彩虹表就是一个庞大的、针对各种可能的字母组合预先生成的哈希值集合,有了它可以快速破解各类密码。越是复杂的密码,需要的彩虹表就越大,主流的彩虹表都是100G以上,目前主要的算法有LM, NTLM, MD5, SHA1, MYSQLSHA1, HALFLMCHALL, NTLMCHALL, ORACLE-SYSTEM, MD5-HALF。
这样的人太坏了,希望大家不要这样做,遵守网络环境规章制度。
🍔加盐密码
为了减轻彩虹表的效果,开发人员开始使用加盐密码。不再只使用密码作为哈希函数的输入,而是为每个用户的密码生成随机字节(称为盐)。盐和用户的密码将一起经过哈希函数运算,生成一个唯一的哈希。盐将以明文形式与用户的密码一起存储。然后,当用户尝试进行身份验证时,盐和用户输入的密码一起经过哈希函数运算,再与存储的密码进行比较。唯一的盐意味着彩虹表不再有效,因为对于每个盐和密码的组合,哈希都是不同的。
🍔自适应单向函数
emmm,因为随着发展加盐也不再特别安全,就衍生出自适应单项函数。
随着硬件的不断发展,加盐哈希也不再安全。原因是,计算机可以每秒执行数十亿次哈希计算。这意味着我们可以轻松地破解每个密码。
现在,开发人员开始使用自适应单向函数来存储密码。使用自适应单向函数验证密码时,故意占用资源(故意使用大量的CPU、内存或其他资源)。自适应单向函数允许配置一个“工作因子”,随着硬件的改进而增加。我们建议将“工作因子”调整到系统中验证密码需要约一秒钟的时间。这种权衡是为了让攻击者难以破解密码。
自适应单向函数包括bcrypt(Springsecurity默认使用)、PBKDF2、scrypt和argon2。
🎏密码加密测试
听我讲了这么多关于密码加密的理论知识,你是不是也迫不及待大展身手了。让我们一起来做一个测试吧。
测试函数
@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), "密码不一致"); }
注意代码中的工作因子,值越大运行速度越慢。
你会发现,运行两次,生成的密码也是不一样的。但是这里不要担心用户进行验证的时候,和数据库中的密码不同,生成的时候用的是随机salt,校验时用的存储的明文salt和加密时的一致,所以校验不会有问题。
🎏DelegatingPasswordEncoder
当我们对比上面生成的密码和数据库中的密码时,会很容易的发现不同:我们生成的密码没有前缀,而数据库中的密码有前缀。原因如下:
我们在使用默认的用户时候,使用的是 withDefaultPasswordEncoder
目的:方便随时做密码策略的升级,兼容数据库中的老版本密码策略生成的密码
里面的 PasswordEncoderFactories方法对所有可能得密码加密算法放在了map中
🎏自定义登录页面
我相信你也这个想法很久了,就是默认的登录页太丑了!!!,早就想换掉了,那正好,现在我们就来实现一下自定义登录页面,你也可以比葫芦画瓢,设计出自己满意的登录页。
🍔创建登录Controller
有人可能会问,这里为什么用Controller,而不是RestController,因为配置了tf肯定用普通的Controller,又不是前后端分离的,RestController返回的是json。
@Controller public class LoginController { @GetMapping("/login") public String login() { return "login"; } }
🍔登录页面
这里是html内容,就不过多赘述了。
登录
登录
错误的用户名和密码.现在我们进行登录,当你输入登录地址后会发现,访问的还是默认的登录页面,而不是我们自定义的,其实我们需要自己配置一下SecurityFilterChain
🍔配置SecurityFilterChain
将配置文件中的表单登录方式修改一下
.formLogin( form -> { form .loginPage("/login").permitAll() //登录页面无需授权即可访问 .usernameParameter("username") //自定义表单用户名参数,默认是username .passwordParameter("password") //自定义表单密码参数,默认是password .failureUrl("/login?error") //登录失败的返回地址 ; }); //使用表单授权方式
我们可以看到,可以使用自己设置了登录页了
🎏用户认证流程(前后端分离)
-
登录成功后调用:AuthenticationSuccessHandler
-
登录失败后调用:AuthenticationFailureHandler
所以我们这里最重要的是重写这两个方法
🎏引入fastjson
因为在这两个类中要返回json数据,所以需要引入fastjson
com.alibaba.fastjson2 fastjson2 2.0.37
🎏认证成功的响应
🍔成功结果处理
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); } }
🍔SecurityFilterChain配置
form.successHandler(new MyAuthenticationSuccessHandler()) //认证成功时的处理
然后进行测试,登录成功后,得到json数据
🎏认证失败响应
🍔失败结果处理
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); } }
🍔SecurityFilterChain配置
form.failureHandler(new MyAuthenticationFailureHandler()) //认证失败时的处理
然后进行登录测试
🎏注销响应
🍔注销结果处理
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); } }
🍔SecurityFilterChain配置
http.logout(logout -> { logout.logoutSuccessHandler(new MyLogoutSuccessHandler()); //注销成功时的处理 });
然后进行测试
🎏请求未认证的接口
官网:docs.spring.io
当访问一个需要认证之后才能访问的接口的时候,Spring Security会使用AuthenticationEntryPoint将用户请求跳转到登录页面,要求用户提供登录凭证。
这里我们也希望系统返回json结果,因此我们定义类实现AuthenticationEntryPoint接口
🍔实现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); } }
🍔SecurityFilterChain配置
//错误处理 http.exceptionHandling(exception -> { exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());//请求未认证的接口 });
然后进行测试
🎏跨域
跨域全称是跨域资源共享(Cross-Origin Resources Sharing,CORS),它是浏览器的保护机制,只允许网页请求统一域名下的服务,同一域名指=>协议、域名、端口号都要保持一致,如果有一项不同,那么就是跨域请求。在前后端分离的项目中,需要解决跨域的问题。
比如我们前端和后端在不同的服务器上面,ip就可能不同,这就可能导致跨域。前端一般发起的ajax请求,这个如果解决很简单,添加如下配置即可:
//跨域 http.cors(withDefaults());
🎏用户认证信息
-
SecurityContextHolder:SecurityContextHolder 是 Spring Security 存储已认证用户详细信息的地方。
-
SecurityContext:SecurityContext 是从 SecurityContextHolder 获取的内容,包含当前已认证用户的 Authentication 信息。
-
Authentication:Authentication 表示用户的身份认证信息。它包含了用户的Principal、Credential和Authority信息。
-
Principal:表示用户的身份标识。它通常是一个表示用户的实体对象,例如用户名。Principal可以通过Authentication对象的getPrincipal()方法获取。
-
Credentials:表示用户的凭证信息,例如密码、证书或其他认证凭据。Credential可以通过Authentication对象的getCredentials()方法获取。
-
GrantedAuthority:表示用户被授予的权限
总结起来,SecurityContextHolder用于管理当前线程的安全上下文,存储已认证用户的详细信息,其中包含了SecurityContext对象,该对象包含了Authentication对象,后者表示用户的身份验证信息,包括Principal(用户的身份标识)和Credential(用户的凭证信息)。
🍔在Controller中获取用户信息
SecurityContext context = SecurityContextHolder.getContext();//存储认证对象的上下文 Authentication authentication = context.getAuthentication();//认证对象 String username = authentication.getName();//用户名 Object principal =authentication.getPrincipal();//身份 Object credentials = authentication.getCredentials();//凭证(脱敏) Collection
-
-
-
-
-