Spring Security 重点解析

Spring Security 重点解析

文章目录

  • Spring Security 重点解析
    • 1. 简介
    • 2. 依赖
    • 3. 登录认证
      • 3.1 登录校验流程
      • 3.2 Spring Security 默认登录的原理
        • 3.2.1 Spring Security 完整流程
        • 3.2.2 登录逻辑探究
      • 3.3 自定义改动
        • 3.3.1 自定义用户密码校验
        • 3.3.2 自定义 UserDetails 获取方式 F1
        • 3.3.3 自定义加密算法 F2
      • 3.4 自定义登录认证
        • 3.4.1 自定义登录接口
        • 3.4.2 自定义过滤器
        • 3.4.3 Security 配置
        • 3.4.4 退出登录
    • 4. 验权处理
      • 4.1 “权限”的存在形式
      • 4.2 开启权限管理
      • 4.3 设置权限要求
        • 4.3.1 通过注解
        • 4.3.2 通过配置
        • 4.3.3 自定义设置权限方法
      • 4.4 RBAC 权限模型
      • 4.5 封装权限信息
      • 4.6 查询权限用于构造 UserDetails
      • 4.7 在 SecurityContextHolder 记录时设置权限
    • 5. 自定义失败处理
      • 5.1 实现处理器类
      • 5.2 配置
    • 6. 跨域问题
      • 6.1 配置类
      • 6.2 Security 配置
    • 7. CSRF 防御
    • 8. 自定义成功处理
      • 8.1 处理器
      • 8.2 Security 配置
      • 8.3 自定义登录模式的成功处理器

1. 简介

Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。

  • 一般来说中大型的项目都是使用SpringSecurity 来做安全框架。小项目有Shiro的比较多,因为相比与SpringSecurity,Shiro的上手更加的简单。
  • 一般Web应用的需要进行认证和授权。

认证:

  • 验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户

授权:

  • 经过认证后判断当前用户是否有权限进行某个操作

而**认证和授权**也是SpringSecurity作为安全框架的核心功能。


接下来以用户名密码登录这个案例来讲解!

  • 利用 JWT 机制,文章:JWT 重点讲解-CSDN博客
  • Redis 缓存,文章:Redis 工具类 与 Redis 布隆过滤器-CSDN博客

Spring Security 推荐学习视频:SpringSecurity框架教程-Spring Security+JWT实现项目级前端分离认证授权-挑战黑马&尚硅谷_哔哩哔哩_bilibili

2. 依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId><version>3.0.4</version>
</dependency>

在这里插入图片描述

引入依赖后我们在尝试去访问之前的接口就会自动跳转到一个 SpringSecurity的默认登陆页面

  • 默认用户名是user,密码会输出在控制台。

    在这里插入图片描述

  • 必须登陆之后才能对接口进行访问。

    在这里插入图片描述

    在这里插入图片描述

    退出登录:

    在这里插入图片描述

等一下你就知道原理了~

3. 登录认证

3.1 登录校验流程

在这里插入图片描述

3.2 Spring Security 默认登录的原理

3.2.1 Spring Security 完整流程

SpringSecurity 的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。

  • 经过所有过滤器才能访问 API,过滤器也能处理响应啥的~

在这里插入图片描述

图中只展示了 核心过滤器,其它的非核心过滤器并没有在图中展示。

在这里插入图片描述

  1. UsernamePasswordAuthenticationFilter
    • 负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责
  2. ExceptionTranslationFilter
    • 处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException ,有异常抛出就肯定过不了这个过滤器了
  3. FilterSecurityInterceptor
    • 负责权限校验的过滤器。
3.2.2 登录逻辑探究

UsernamePasswordAuthenticationFilter 就是默认登录方式,默认是 Cookie-Session 机制

在这里插入图片描述

概念速查:

Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。

AuthenticationManager接口:定义了认证Authentication的方法

UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。

UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装

成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

  1. 访问 /test/hello 第一次没有会话,UsernamePasswordAuthenticationFilter 尝试获取,获取不到,强制让用户登录(重定向到[GET] /login 页面,本次请求不算数)

  2. 访问 [POST]/login 携带用户名密码,验证成功后,将 “用户相关信息(UserDetails)”构造出一个核心对象(Authentication),放入这次请求的 Security 框架上下文对象中,前往之后的拦截器

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  3. 其他过滤器都没问题了(登录没啥权限要求),请求成功到达[POST]/login ,进行对应逻辑,返回响应~

    在这里插入图片描述

  4. 访问 /test/hello 携带 Cookie,UsernamePasswordAuthenticationFilter 获取到会话记录 UserDetails,”构造出一个核心对象(Authentication),放入这次请求的 Security 框架上下文对象中,前往之后的拦截器

    会话记录:

    在这里插入图片描述

  5. 其他过滤器都没问题了(登录没啥权限要求),请求成功到达/test/hello ,进行对应逻辑,返回响应~

    在这里插入图片描述
    在这里插入图片描述

3.3 自定义改动

在这里插入图片描述

  1. 自定义用户名密码校验方式
  2. 自定义拦截器,可调节在哪个拦截器之前或之后,或者删除哪些拦截器
3.3.1 自定义用户密码校验
  1. 自定义的 UserDetails 获取方式 F1
  2. 自定义密码的加密算法 F2

[POST]/login 携带的用户名为 U,密码为 P,UserDetails 为 D

  • D = F1(U);
  • 判断 F2(P)D.getPassword(),是否相同
3.3.2 自定义 UserDetails 获取方式 F1

我知道我没有给出一些数据库与MP相关的代码,不过思路看得懂即可!

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {private SysUser user;private List<String> permissions;@JSONField(serialize = false)//代表,由不通过fastjson序列化,用系统redis其他的方式存入redisprivate List<GrantedAuthority> authorities;//com.alibaba.fastjson.JSONException: autoType is not support.@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {if(Objects.isNull(this.authorities)) {// 将 permissions 的权限信息封装成 SimpleGrantedAuthority 象,并且也是集合this.authorities = this.permissions.stream().parallel().map(SimpleGrantedAuthority::new).collect(Collectors.toList());}return this.authorities;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUserName();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}public LoginUser(SysUser user, List<String> permissions) {this.user = user;this.permissions = permissions;}
}
@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {@Resourceprivate SysMenuService menuService;// 查询用户信息@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {// 查询用户信息SysUser user = Db.lambdaQuery(SysUser.class).eq(SysUser::getUserName, s).one();if(Objects.isNull(user)) {log.warn("用户名或者密码错误");throw new RuntimeException("用户名或者密码错误");}// todo 查询对应的权限信息List<String> permissions = menuService.selectMenuById(user.getId());// 把数据封装成UserDetails返回return new LoginUser(user, permissions);}
}

认证的时候,就会用我们实现的加载数据的方法

3.3.3 自定义加密算法 F2
//BCryptPasswordEncoder的Bean对象加入到容器里
@Bean
public PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();
}

在认证的时候,就是用我们的这个 PasswordEncoder 去加密(默认不加密,不用加密算法)

用同种加密算法去注册两个账号:

    @Resourceprivate PasswordEncoder encoder;@Testpublic void createUser() {List<SysUser> list = new ArrayList<SysUser>() {{add(new SysUser(){{setId(1L);setNickName("马1号");setUserName("mara1");setPassword(encoder.encode("123456"));}});add(new SysUser(){{setId(2L);setNickName("马2号");setUserName("mara2");setPassword(encoder.encode("123456"));}});}};Db.saveBatch(list);}

3.4 自定义登录认证

对于以上内容,依赖默认的登录页面,获取是用户名密码的机制也比较单一,我们往往需要一些其他的登录方式~

接下来,我们来搞一下自定义登录(还是以用户名密码,但是这次是我们自己写的业务逻辑)

3.4.1 自定义登录接口

在这里插入图片描述

@Data
public class FormUserDTO {private String userName;private String password;}
@RestController
//@RequestMapping("/user")
public class LoginController {@Resourceprivate LoginService loginService;@PostMapping("/user/login")public ResponseResult login(@RequestBody FormUserDTO user) {//登录return loginService.login(user);}
}
public interface LoginService {ResponseResult login(FormUserDTO user);
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();
}

实现:

@Resource
private AuthenticationManager authenticationManager;@Resource
private RedisCache redisCache;@Override
public ResponseResult<Map<String, String>> login(FormUserDTO user) {// 获取AuthenticationManager authenticate认证UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());Authentication authenticate = authenticationManager.authenticate(authenticationToken);//如果认证不通过,提示if(Objects.isNull(authenticate)) {log.warn("登录失败");throw new RuntimeException("登录失败");}//如果认证通过了,用userid生成一个jwtLoginUser loginUser = (LoginUser) authenticate.getPrincipal();//认证的时候已经调用了getAuthorities了String userid = loginUser.getUser().getId().toString();String jwt = JwtUtil.createJWT(userid);//把完整的用户信息存入redis,userid作为keyredisCache.setCacheObject("login:" + userid, loginUser);//        redisCache.getCacheObject("login:" + userid);//是完完整整的存进去的// 符合jwtreturn new ResponseResult(200, "登录成功", new HashMap<String, String>(){{this.put("token", jwt);}});
}

在这里插入图片描述

这就相当于替 UsernamePasswordAuthenticationFilter 调用了认证方法获得 authenticate:

在这里插入图片描述

自然就是一轮认证:

在这里插入图片描述

认证失败也是在这个过程中抛出异常的

3.4.2 自定义过滤器
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {// 保证请求只会经过这个过滤器一次@Resourceprivate RedisCache redisCache;@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {// 获取tokenString token = httpServletRequest.getHeader("token");if(!StringUtils.hasText(token)) {filterChain.doFilter(httpServletRequest, httpServletResponse);//放行return;}// 解析tokenString userid = null;try {Claims claims = JwtUtil.parseJWT(token);userid = claims.getSubject();} catch (Exception e) {log.error("token非法");filterChain.doFilter(httpServletRequest, httpServletResponse);//放行return;}//从redis中获取用户信息String redisKey = "login:" + userid;LoginUser loginUser = redisCache.getCacheObject(redisKey);if(Objects.isNull(loginUser)) {filterChain.doFilter(httpServletRequest, httpServletResponse);//放行return;}// 存入SecurityContextHolderUsernamePasswordAuthenticationToken authenticationToken= new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());//代表已认证SecurityContextHolder.getContext().setAuthentication(authenticationToken);filterChain.doFilter(httpServletRequest, httpServletResponse);//放行}}

认证成功要记录!

在这里插入图片描述

3.4.3 Security 配置

yaml 配置文件去配置不灵活,不鲜明,在这里不演示

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启权限控制,不开启这个,注解的权限控制不能生效
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Resourceprivate AuthenticationEntryPoint authenticationEntryPoint;@Resourceprivate AccessDeniedHandler accessDeniedHandler;//BCryptPasswordEncoder的Bean对象加入到容器里@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {
//        super.configure(http); 默认是cookie session//内部指定表单,用户输入表单就去验证,也就是UsernamePasswordAuthenticationFilter的认证方式// 既然注释掉了,则代表这个过滤器(可能不止这一个)失效了~(不被调用 -> 过滤器不工作),并且还注释掉“控制”相关的配置http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers("/user/login") /*匿名访问*/.anonymous()// 添加过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);//添加过滤器在某个过滤器之前}
}

不必调用父类的configure方法,UsernamePasswordAuthenticationFilter就相当于没用了

UsernamePasswordAuthenticationFilter 无法进行重定向

  •  不请求`[POST]/login` ,UsernamePasswordAuthenticationFilter就不会调用认证的方法,SecurityContextHolder就不会有对应的那个Authentication对象的记录
    
  •  最终需要认证的接口就访问不了!
    
  • “哪些可以匿名访问不用认证,哪些需要进行认证”,只不过是一些“认证/权限等等的控制”罢了,没有实际的认证机制
  • 每个请求都会经过过滤器链,只不过一些没通过检查也无所谓!

这样我们就需要自己写个拦截器,去规定怎么样才算认证成功

  •  这样,必须提前在/user/login (接口内用了认证方法,这一步代表体检做认证),将userid存到redis,生成token返回
    
  •  请求必须携带token,由 jwtAuthenticationTokenFilter 进行认证是否有token,redis是否有记录
    
  •  有则代表提前做了认证,SecurityContextHolder 设置那个Authentication对象,放行即可,就不需要进行UsernamePasswordAuthenticationFilter的那个默认login页面去认证了
    

把默认的关了的话,UsernamePasswordAuthenticationFilter 就相当于没用了,UsernamePasswordAuthenticationFilter 并不会让认证结果有任何变化

没有 jwtAuthenticationTokenFilter 的话,那么 SecurityContextHolder 就没有记录,那么请求必然不通过认证/验权

jwtAuthenticationTokenFilter 没有让 SecurityContextHolder 有记录,那么请求也必然不通过认证/验权

3.4.4 退出登录
@GetMapping("/user/logout")
public ResponseResult logout() {return loginService.logout();
}
public interface LoginService {ResponseResult login(FormUserDTO user);ResponseResult logout();
}

实现:

@Override
public ResponseResult logout() {//获取用户idUsernamePasswordAuthenticationToken authentication= (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser = (LoginUser) authentication.getPrincipal();String userid = loginUser.getUser().getId().toString();// 删除redis中的值String redisKey = "login:" + userid;redisCache.deleteObject(redisKey);return new ResponseResult(200,  "注销成功");
}

在这里插入图片描述

补充,用户名为 null,在 loadUserByUsername 方法的 userName 为"NONE_PROVIDED"

  • 如果真的有用户名是这个,密码还没错,还真的能登录成功!

在这里插入图片描述

这是借用了 UsernamePasswordAuthenticationFilter 的认证方法,我们当然也可以自己写一个认证方法,构造UserDetails 啊

  • 也可以研究其他的 SpringSecurity 提供的认证方法~

4. 验权处理

4.1 “权限”的存在形式

例如一个学校图书馆的管理系统,如果是普通学生登录就能看到借书还书相关的功能,不可能让他看到并且去使用添加书籍信息,删除书籍信息等功能。但是如果是一个图书馆管理员的账号登录了,应该就能看到并使用添加书籍信息,删除书籍信息等功能。

总结起来就是不同的用户可以使用不同的功能。这就是权限系统要去实现的效果。

我们不能只依赖前端去判断用户的权限来选择显示哪些菜单哪些按钮。因为如果只是这样,如果有人知道了对应功能的接口地址就可以不通过前端,直接去发送请求来实现相关功能操作。

所以我们还需要在后台进行用户权限的判断,判断当前用户是否有相应的权限,必须具有所需权限才能进行相应的操作。

而我们这里的权限就是一个字符串

  • 例如"limit",获取用户的权限字符串列表,有"limit"就代表有权限,反之没有

而权限信息要交给 UserDetails,从而在认证成功后在 SecurityContextHolder 中记录,在FilterSecurityInterceptor 过滤器将其拿出来,对比 api 所需的权限,决定是否可以访问

在这里插入图片描述

4.2 开启权限管理

@EnableGlobalMethodSecurity(prePostEnabled = true)//开启权限控制,不开启这个,注解的权限控制不能生效

4.3 设置权限要求

4.3.1 通过注解

Spring表达式语言(Spring Expression Language,SpEL)是 Spring Framework 的核心技术之一,其支持在运行时查询和操作对象图。

SpEL语法类似于 Unified Expression Language,但提供了更加丰富的功能,最特别的是方法调用与字符串模板功能。

@RestController
@RequiredArgsConstructor
@Slf4j
@RequestMapping("/test")
public class HelloController {@GetMapping("/hello")@PreAuthorize("hasAuthority('system:test:list')")public String hello() {return "<h1>hello!</h1>";}@GetMapping("/hi")@PreAuthorize("hasAuthority('system:dept:list')")
//    @PreAuthorize("hasRole('system:dept:list')")//拼接ROLE_前缀 后去验证public String hi() {return "<h1>hi!</h1>";}@GetMapping("/haha")@PreAuthorize("hasAnyAuthority('system:dept:list', 'haha')")
//    @PreAuthorize("hasAnyRole('system:dept:list', 'haha')")//拼接ROLE_前缀 后去验证public String haha() {return "<h1>haha!</h1>";}
}

按住 ctrl 点击查看对应的方法~

在这里插入图片描述

4.3.2 通过配置

在这里插入图片描述

4.3.3 自定义设置权限方法
@Component("mex")
public class MacakuExpressionRoot {public boolean hasAuthority(String authority) {// 获取当前用户的权限Authentication authentication = SecurityContextHolder.getContext().getAuthentication();//判断用户权限集合中是否存在authorityreturn  authentication.getAuthorities().stream().map(x -> x.getAuthority()).distinct().collect(Collectors.toList()).contains(authority);}
}

在这里插入图片描述

4.4 RBAC 权限模型

Role-Based Access Control

在这里插入图片描述

一个用户可以有多个角色,一个角色可以有多个权限

  • 某个角色可以是不同用户,某个权限也可以由多个角色拥有

通过 id 查询权限的 mapper xml:

<select id="selectMenuById" resultMap="BaseResultMap" parameterType="java.lang.Long">SELECTDISTINCT m.`perms` permsFROMsys_user uLEFT JOIN `sys_user_role` ur ON u.`id` = ur.`user_id`LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`WHEREu.`id` = #{id, jdbcType=BIGINT}AND r.`status` = 0AND m.`status` = 0
</select>

4.5 封装权限信息

在这里插入图片描述

4.6 查询权限用于构造 UserDetails

在这里插入图片描述

tips:这里还可以查询角色,因为角色也可以看成是一种特殊的权限

记得加上“ROLE_”前缀

4.7 在 SecurityContextHolder 记录时设置权限

在这里插入图片描述

5. 自定义失败处理

不可以直接用异常处理器去解决问题,因为这个时候抛出异常并不会被我们的全局异常处理器捕获!

在这里插入图片描述

(业务逻辑抛的异常会导致 500)

  1. 认证失败 抛出的 AuthenticationException 异常 ,由 AuthenticationEntryPoint 处理
  2. 验权失败 抛出的 AccessDeniedException ,由 AccessDeniedHandler 处理

在这里插入图片描述

由于不能直接进行异常处理,所以自定义异常处理还是很有必要的~

  • 不过在处理的时候不要抛异常,因为这样会直接导致请求响应是 500(因为不会被我们的异常处理器捕获)
  • 可以重定向之类的~

提几个重定向的坑:

  1. HttpServletResponse 对象的 sendRedirect 方法,是相对路径下的重定向,不要填绝对路径否则会导致循环重定向

    • (例如 sendRedirect(“127.0.0.1:8081/user/login”))会导致重定向的请求是 “127.0.0.1:8081/127.0.0.1:8081/user/login”)
  2. 可以写成这样:

    public final static String LOCATION_HEADER = "Location";

    在这里插入图片描述

    • 通过 HttpServletRequest 对象获取的 URL 是 http 的(即使请求是 https ),如果要重定向到 https 要自己去解决!
    • 可以在配置文件中获取域名与协议啊~

5.1 实现处理器类

验权失败:

@Component
@Slf4j
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {log.warn(e.getMessage());ResponseResult responseResult = new ResponseResult(HttpStatus.FORBIDDEN.value(), "用户授权不足");//处理异常WebUtils.renderString(httpServletResponse, JSON.toJSONString(responseResult));}
}

认证失败:

@Component
@Slf4j
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {//@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {log.warn(e.getMessage());ResponseResult responseResult = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "用户认证失败请查询登录");//处理异常WebUtils.renderString(httpServletResponse, JSON.toJSONString(responseResult));}
}

5.2 配置

在这里插入图片描述

6. 跨域问题

浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。

前后端分离项目,前端项目和后端项目一般都不是同源的,所以肯定会存在跨域请求的问题。

所以我们就要处理一下,让前端能进行跨域请求。

6.1 配置类

@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {// 设置允许跨域的路径registry.addMapping("/**")// 设置允许跨域请求的域名.allowedOriginPatterns("*")// 是否允许cookie.allowCredentials(true)// 设置允许的请求方式.allowedMethods("GET", "POST", "DELETE", "PUT")// 设置允许的header属性.allowedHeaders("*")// 跨域允许时间.maxAge(3600);}
}

6.2 Security 配置

在这里插入图片描述

当然,你也可以写个过滤器,让响应带上一些东西来允许跨域~

@Component
public class CorsFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletResponse httpResponse = (HttpServletResponse) response;httpResponse.setHeader("Access-Control-Allow-Origin", "*"); // 可以设置允许访问的域,也可以是具体的域名httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");httpResponse.setHeader("Access-Control-Max-Age", "3600");httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");httpResponse.setHeader("Access-Control-Allow-Credentials", "true");chain.doFilter(request, response);}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// 可以在这里进行一些初始化操作}@Overridepublic void destroy() {// 可以在这里进行一些清理操作}
}

7. CSRF 防御

CSRF(Cross-Site Request Forgery)的相关介绍视频:CSRF 攻击和防御 - Web 安全常识_哔哩哔哩_bilibili

可以取消对 CSRF 的关闭

在这里插入图片描述

Spring Security默认情况下会启用CSRF防护。

Spring Security 对 CSRF 的防御手段是自定义请求头

通过csrf_token。后端会生成一个csrf_token,前端发起请求的时候需要携带这个csrf_token,后端会有过滤器进行校验,如果没有携带或者是伪造的就不允许访问。

我们可以发现 CSRF 攻击依靠的是 Cookie 中所携带的认证信息。但是在前后端分离的项目中我们的认证信息其实是token,而 token 并不是存储中 Cookie 中,并且需要前端代码去把 token 设置到请求头中才可以,所以 CSRF 攻击也就不用担心了。

8. 自定义成功处理

成功处理器只能在默认登录方式中加入,我也不知道为什么🤣,硬性限制~

8.1 处理器

登录成功:

@Component
@Slf4j
public class SQSAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,Authentication authentication) throws IOException, ServletException {log.warn("认证成功");//由于顶替了原本的成功处理,所以是没有跳转的}
}

还有个失败处理器(针对默认登录方式的)

@Component
@Slf4j
public class SQSAuthenticationFailHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {log.warn("认证失败");}
}

登出成功:

@Component
@Slf4j
public class SQSLogoutHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {log.warn("登出成功");}
}

8.2 Security 配置

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate AuthenticationSuccessHandler successHandler;@Resourceprivate AuthenticationFailureHandler failureHandler;@Resourceprivate LogoutSuccessHandler logoutSuccessHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().successHandler(successHandler).failureHandler(failureHandler);http.authorizeRequests().anyRequest().authenticated();http.logout().logoutSuccessHandler(logoutSuccessHandler);http.httpBasic();}
}

8.3 自定义登录模式的成功处理器

认证成功:

  • 不一定到达这个过滤器就一定是认证成功,所以要进行一些判断(只是模拟那个效果罢了)
@Component
public class AuthSuccessFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {if(SecurityContextHolder.getContext().getAuthentication().getDetails() instanceof LoginUser) {System.out.println("认证成功!");}filterChain.doFilter(httpServletRequest, httpServletResponse);//放行}
}

Security 配置 (判断成功的原理:在特定的过滤器之后加上对应的过滤器):

// 添加过滤器
http.addFilterAfter(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class).addFilterAfter(authSuccessFilter, JwtAuthenticationTokenFilter.class);

文章到此结束,Spring Security 还有很多知识,一篇文章是讲不完的,所以还要继续学习才行,不会的去查,边用边学~

但是大部分的功能,利用 Spring Security 加上自己的想法去灵活地自定义实现一些业务吧~

代码地址:java-research/SecurityDemo at master · CarefreeState/java-research (github.com)

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

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

相关文章

基于Spring Boot的安康旅游网站的设计与实现,计算机毕业设计(带源码+论文)

源码获取地址&#xff1a; 码呢-一个专注于技术分享的博客平台一个专注于技术分享的博客平台,大家以共同学习,乐于分享,拥抱开源的价值观进行学习交流http://www.xmbiao.cn/resource-details/1760645517548793858

SpringSecurity + OAuth2 详解

SpringSecurity入门到精通 ************************************************************************** SpringSecurity 介绍 **************************************************************************一、入门1.简介与选择2.入门案例-默认的登录和登出接口3.登录经过了…

不做内容引流,你凭什么在互联网上赚钱?

孩子们放寒假了&#xff0c;待在家里不是看电视&#xff0c;就是拿着手机刷视频&#xff0c;脸上是各种欢快和满足。只是一切换到写作业模式&#xff0c;孩子是各种痛苦表情包&#xff0c;家长则是使出浑身解数&#xff0c;上演亲子大战。可见娱乐常常让人愉悦&#xff0c;而学…

鼠标事件和滚轮事件

1. 介绍 QMouseEvent类用来表示一个鼠标事件&#xff0c;当在窗口部件中按下鼠标或者移动鼠标指针时&#xff0c;都会产生鼠标事件。利用QMouseEvent类可以获知鼠标是哪个键按下了&#xff0c;还有鼠标指针的当前位置等信息。通常是重定义部件的鼠标事件处理函数来进行一些自定…

ubuntu使用LLVM官方发布的tar.xz来安装Clang编译器

ubuntu系统上的软件相比CentOS更新还是比较快的&#xff0c;但是还是难免有一些软件更新得不那么快&#xff0c;比如LLVM Clang编译器&#xff0c;目前ubuntu 22.04版本最高还只能安装LLVM 15&#xff0c;而LLVM 18 rc版本都出来了。参见https://github.com/llvm/llvm-project/…

【STM32】Keil RTE使用记录

0 前言 最近因为任务需要&#xff0c;再次开始研究STM32&#xff0c;打算过一遍之前记录的笔记&#xff0c;在创建工程模板时&#xff0c;突然发现一个之前被自己忽略的东西&#xff0c;那就是创建项目时会弹出的Run-Time Environment&#xff0c;抱着好奇的心态去找了一些资料…

防御保护--入侵防御系统IPS

目录 DFI和DPI技术 --- 深度检测技术 入侵防御&#xff08;IPS&#xff09; 签名 入侵防御策略的配置 内容安全&#xff1a;攻击可能只是一个点&#xff0c;防御需要全方面进行 IAE引擎 DFI和DPI技术 --- 深度检测技术 DPI--深度包检测技术--主要针对完整的数据包&#xff0…

冒泡排序法的名字由来,排序步骤是什么,最坏情况下的排序次数如何计算得来的呢?

问题描述&#xff1a;冒泡排序法的名字由来&#xff0c;排序步骤是什么&#xff0c;最坏情况下的排序次数如何计算得来的呢&#xff1f; 问题解答&#xff1a; 冒泡排序法的名字来源于排序过程中较大的元素会像气泡一样逐渐“冒”到序列的顶端&#xff0c;而较小的元素则会逐…

员工离职倾向分析工具

很多公司都担心员工离职&#xff0c;尤其是工龄久的老员工&#xff0c;为什么呢&#xff1f; 很多离职员工带走上家机密&#xff0c;还有的辞职后开公司成为了上家企业的对手公司等等&#xff0c;这类事件非常常见&#xff0c;因此员工离职是一个敏感的话题。 员工离职的原因 …

基于springboot+vue的植物健康系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

力扣随笔之两数之和 Ⅱ -输入有序数组(中等167)

思路&#xff1a;在递增数组中找出满足相加之和等于目标数 定义左右两个指针&#xff08;下标&#xff09;从数组两边开始遍历&#xff0c;若左右指针所指数字之和大于目标数&#xff0c;则将右指针自减&#xff0c;若左右指针所指数字之和小于目标数&#xff0c;则左指针自加&…

前端(vue)数据存储方案

引言 本需求文档旨在明确前端项目中的数据存储需求&#xff0c;包括数据类型、数据结构、数据交互方式等。它定义了前端项目中需要存储和处理的数据&#xff0c;以及对这些数据进行访问和操作的要求。 功能需求 数据存储按数据类型分为 持久存储、内存存储&#xff08;响应式…

AD24-蛇形走线

一、单端蛇形走线 1、公差参数 2、布线-网络等长调节 3、参数说明 ①手工输入绕线的长度 ②参照个网络的长度绕线 ③按照自身设置的规绕线&#xff08;一般选用) 4、调节 5、最后 二、差分蛇形走线 1、布线-差分对网络等长调节 2、如在选中的时候出现问题&#xff0c;按CtrlD…

Linux学习方法-框架学习法——Linux应用程序编程框架

配套视频学习链接&#xff1a;https://www.bilibili.com/video/BV1HE411w7by?p4&vd_sourced488bc722b90657aaa06a1e8647eddfc 目录 Linux应用程序编程 Linux应用程序编程 Linux文件I/O(input/output) Linux文件I/O(五种I/O模型) Linux多进程 Linux多线程 网络通信(s…

集合、List、Set、Map、Collections、queue、deque

概述 相同类型的数据进行统一管理操作&#xff0c;使用数据结构、链表结构&#xff0c;二叉树 分类&#xff1a;Collection、Map、Iterator 集合框架 List接口 有序的Collection接口&#xff0c;可以对列表中的每一个元u尿素的插入位置进行精确的控制&#xff0c;用户可以根…

Qt事件过滤器

1. 事件过滤器 void QObject::installEventFilter(QObject *filterObj) bool eventFilter(QObject *obj, QEvent *event); filterObj表示事件筛选器对象&#xff0c;它接收发送到此QObject对象&#xff08;安装事件过滤器的部件对象&#xff09;的所有事件。筛选器可以停止事件…

SpringCloud-Gateway解决跨域问题

Spring Cloud Gateway是一个基于Spring Framework的微服务网关&#xff0c;用于构建可扩展的分布式系统。在处理跨域问题时&#xff0c;可以通过配置网关来实现跨域资源共享&#xff08;CORS&#xff09;。要解决跨域问题&#xff0c;首先需要在网关的配置文件中添加相关的跨域…

(六)激光线扫描-三维重建

本篇文章是《激光线扫描-三维重建》系列的最后一篇。 1. 基础理论 1.1 光平面 在之前光平面标定的文章中,已经提到过了,是指 激光发射器投射出一条线,形成的一个扇形区域平面就是光平面。 三维空间中平面的公式是: A X + B Y + C Z + D = 0 A X+B Y+C Z+D=0

矿产达人小程序修复前端

应用介绍 本文来自&#xff1a;矿产达人小程序修复前端 - 源码1688 矿产达人小程序&#xff1a; 矿产小游戏小程序是一款以矿产资源为主题的休闲娱乐游戏。以下是该小程序的主要功能特点&#xff1a; 游戏画面精美&#xff1a;小程序采用卡通化的设计风格&#xff0c;画面色…

程序媛的mac修炼手册-- 小白入门Java篇

最近因为要用CiteSpace做文献综述&#xff0c;间接接触Java了。所以&#xff0c;继Python、C之后&#xff0c;又要涉猎Java了。刺激&#xff01;&#xff01; 由于CiteSpace与Java要求版本高度匹配&#xff0c;有个匹配详情明天为大家讲解。总之&#xff0c;我的Java之旅开始于…