SpringSecurity配置 2
SpringSecurity配置 2
目前的现状,虽然是有了登录认证的接口,但是登录完成后,当我们访问受保护的接口时,即使将 Token 令牌携带与请求一起发送,依然是无法请求成功。另外,提示信息如下图所示,非常不友好,和之前定义好的统一返参格式也不一致。
1. 新建 Token 校验过滤器
首先,我们在 weblog-module-jwt 模块下的 /filter 包下,创建 TokenAuthenticationFilter 过滤器,用于专门校验 Token 令牌,代码如下:
@Slf4j
public class TokenAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenHelper jwtTokenHelper;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 从请求头中获取 key 为 Authorization 的值
String header = request.getHeader("Authorization");
// 判断 value 值是否以 Bearer 开头
if (StringUtils.startsWith(header, "Bearer")) {
// 截取 Token 令牌
String token = StringUtils.substring(header, 7);
log.info("Token: {}", token);
// 判空 Token
if (StringUtils.isNotBlank(token)) {
try {
// 校验 Token 是否可用, 若解析异常,针对不同异常做出不同的响应参数
jwtTokenHelper.validateToken(token);
} catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
// 抛出异常,统一让 AuthenticationEntryPoint 处理响应参数
authenticationEntryPoint.commence(request, response, new AuthenticationServiceException("Token 不可用"));
return;
} catch (ExpiredJwtException e) {
authenticationEntryPoint.commence(request, response, new AuthenticationServiceException("Token 已失效"));
return;
}
// 从 Token 中解析出用户名
String username = jwtTokenHelper.getUsernameByToken(token);
if (StringUtils.isNotBlank(username)
&& Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) {
// 根据用户名获取用户详情信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 将用户信息存入 authentication,方便后续校验
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, //用户详细信息,凭证,用户权限列表
userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 将 authentication 存入 ThreadLocal,方便后续获取用户信息
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
// 继续执行写一个过滤器
filterChain.doFilter(request, response);
}
}
上述代码中,我们自定义了一个 Spring Security 过滤器 TokenAuthenticationFilter,它继承了 OncePerRequestFilter,确保每个请求只被过滤一次。在重写的 doFilterInternal() 方法中来定义过滤器处理逻辑,首先,从请求头中获取 key 为 Authorization 的值,判断是否以 Bearer 开头,若是,截取出 Token, 对其进行解析,并对可能抛出的异常做出不同的返参。最后,我们获取了用户详情信息,将用户信息存入 authentication,方便后续进行校验,同时将 authentication 存入 ThreadLocal 中,方便后面方便的获取用户信息。
2. JwtTokenHelper 新增方法
上述过滤器中,需要在 JwtTokenHelper 工具类中添加两个方法:
校验 Token 是否可用;
解析 Token 获取用户名;
/**
* 校验 Token 是否可用
* @param token
* @return
*/
public void validateToken(String token) {
jwtParser.parseClaimsJws(token);
}
/**
* 解析 Token 获取用户名
* @param token
* @return
*/
public String getUsernameByToken(String token) {
try {
Claims claims = jwtParser.parseClaimsJws(token).getBody();
String username = claims.getSubject();
return username;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
3. 添加处理器
在 /handler 包下,新增 RestAuthenticationEntryPoint 处理器,它专门用来处理当用户未登录时,访问受保护的资源的情况:
/**
* @author: 犬小哈
* @url: www.quanxiaoha.com
* @date: 2023-08-27 17:27
* @description: 用户未登录访问受保护的资源
**/
@Slf4j
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
log.warn("用户未登录访问受保护的资源: ", authException);
if (authException instanceof InsufficientAuthenticationException) {
ResultUtil.fail(response, HttpStatus.UNAUTHORIZED.value(), Response.fail(ResponseCodeEnum.UNAUTHORIZED));
return;
}
ResultUtil.fail(response, HttpStatus.UNAUTHORIZED.value(), Response.fail(authException.getMessage()));
}
}
当请求相关受保护的接口时,请求头中未携带 Token 令牌,通过日志打印异常信息,你会发现对应的异常类型
对应的,你需要在 ResponseCodeEnum 枚举类中添加 UNAUTHORIZED 枚举值,代码如下:
UNAUTHORIZED("20002", "无访问权限,请先登录!"),
4. RestAccessDeniedHandler
然后,在 /handler 包下,另外新增 RestAccessDeniedHandler 处理器,它主要用于处理当用户登录成功时,访问受保护的资源,但是权限不够的情况:
/**
* @author: 犬小哈
* @url: www.quanxiaoha.com
* @date: 2023-08-27 17:32
* @description: 登录成功访问收保护的资源,但是权限不够
**/
@Slf4j
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
log.warn("登录成功访问收保护的资源,但是权限不够: ", accessDeniedException);
// 预留,后面引入多角色时会用到
}
}
5. 修改 Spring Security 配置
完成以上 Token 校验过滤器相关的开发后,接下来,需要将它们添加到 Spring Security 中,编辑 WebSecurityConfig 配置类,修改内容如下:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationSecurityConfig jwtAuthenticationSecurityConfig;
@Autowired
private RestAuthenticationEntryPoint authEntryPoint;
@Autowired
private RestAccessDeniedHandler deniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(). // 禁用 csrf
formLogin().disable() // 禁用表单登录
.apply(jwtAuthenticationSecurityConfig) // 设置用户登录认证相关配置
.and()
.authorizeHttpRequests()
.mvcMatchers("/admin/**").authenticated() // 认证所有以 /admin 为前缀的 URL 资源
.anyRequest().permitAll() // 其他都需要放行,无需认证
.and()
.httpBasic().authenticationEntryPoint(authEntryPoint) // 处理用户未登录访问受保护的资源的情况
.and()
.exceptionHandling().accessDeniedHandler(deniedHandler) // 处理登录成功后访问受保护的资源,但是权限不够的情况
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 前后端分离,无需创建会话
.and()
.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) // 将 Token 校验过滤器添加到用户认证过滤器之前
;
}
/**
* Token 校验过滤器
* @return
*/
@Bean
public TokenAuthenticationFilter tokenAuthenticationFilter() {
return new TokenAuthenticationFilter();
}
}
上述代码中,我们注入了前面写好的相关类,如过滤器、处理器等,主要改动代码如下:
.httpBasic().authenticationEntryPoint(authEntryPoint): 处理用户未登录访问受保护的资源的情况;.exceptionHandling().accessDeniedHandler(deniedHandler): 处理登录成功后访问受保护的资源,但是权限不够的情况;.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class): 将 Token 校验过滤器添加到用户认证过滤器之前;
6. 相关变量提取到 application.yml 中
鉴权功能正常开发完毕后,你会发现已经开发好的代码中,还有一些写死的变量,如请求头的 key, 令牌的失效时间等,其实都是可以提取到 applicaiton.yml 配置文件中,方便后续统一维护的:
jwt:
# token 过期时间(单位:分钟) 24*60
tokenExpireTime: 1440
# token 请求头中的 key 值
tokenHeaderKey: Authorization
# token 请求头中的 value 值前缀
tokenPrefix: Bearer
SpringSecurity配置 2的更多相关文章
- SpringSecurity02 表单登录、SpringSecurity配置类
1 功能需求 springSecuriy默认的登录窗口是一个弹出窗口,而且会默认对所有的请求都进行拦截:要求更改登录页面(使用表单登录).排除掉一些请求的拦截 2 编写一个springSecurity ...
- SpringSecurity配置,简单梳理
生活加油:摘一句子: “我希望自己能写这样的诗.我希望自己也是一颗星星.如果我会发光,就不必害怕黑暗.如果我自己是那么美好,那么一切恐惧就可以烟消云散.于是我开始存下了一点希望—如果我能做到,那么我就 ...
- spring security4.2.2的maven配置+spring-security配置详解+java源码+数据库设计
最近项目需要添加权限拦截,经讨论决定采用spring security4.2.2!废话少说直接上干货! 若有不正之处,请谅解和批评指正,不胜感激!!!!! spring security 4.2.2文 ...
- spring-security 配置简介
1.Spring Security 简介 Spring Security 是一个能够基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在 Spring 应用 ...
- springsecurity 配置swagger
最近在学习springsecurity 安全框架,具体是什么概念在这里不一一赘述了.下面呢,咱们一起搭建一下简单的springsecurity swagger 项目感受一下. 首先初始化spring ...
- spring-security配置和原理简介
SpringSecurity3的核心类有三种 1.URL过滤器或方法拦截器:用来拦截URL或者方法资源对其进行验证,其抽象基类为AbstractSecurityInterceptor 2.资源权限获取 ...
- SpringSecurity 配置
SpringSecurity+JWT https://www.jianshu.com/p/5b9f1f4de88d https://blog.csdn.net/qq_35494808/article/ ...
- Spring-security配置代码
@Configuration public static class WebSecurityConfigurer extends WebSecurityConfigurerAdapter{ @Over ...
- SpringSecurity项目中如何在多个模块中配置认证信息
⒈在SpringSecurity项目中创建AuthorizeConfigProvider接口用于配置认证信息 package cn.coreqi.ssoserver.authorize; import ...
- Spring Boot 5 SpringSecurity身份验证
对于没有访问权限的用户需要转到登录表单页面.要实现访问控制的方法多种多样,可以通过Aop.拦截器实现,也可以通过框架实现(如:Apache Shiro.Spring Security). pom.xm ...
随机推荐
- 大数据之路Week08_day06 (Zookeeper搭建)
Zookeeper集群搭建 在本文中Zookeeper节点个数(奇数)为3个.Zookeeper默认对外提供服务的端口号2181 .Zookeeper集群内部3个节点之间通信默认使用2888:3888 ...
- BloomFilter详解
目录 BloomFilter 原理: 问题引入:黑名单管理程序 哈希.哈希函数 BloomFilter : 3.4 BloomFilter 的缺陷.改进: 代码实现 黑名单blacklist.py: ...
- ABC391E题解
大概评级:绿. 题目传送门. 显然动态规划,设 \(f_{i,k}\) 表示经过 \(i\) 次变换后能将 \(a_k\) 取反的最大值,显然答案为 \(f_{n,1}\),状态转移很简单,枚举 \( ...
- PHP中类和对象相关的函数
1.class_exists 用于判断一个类是否存在,参数为类名: 2.interface_exists 判断一个接口是否存在,参数为接口名: 3.method_exists 判断一个方法是否存在,参 ...
- ORACLE SQL中执行先后次序的问题
分享一个经验 需求:Oracle中,根据COST优先级取最优先的一条记录脚本: select ... from ... where ... and rownum=1 order by cost 实际不 ...
- golang单机锁实现
1.锁的概念引入 首先,为什么需要锁? 在并发编程中,多个线程或进程可能同时访问和修改同一个共享资源(例如变量.数据结构.文件)等,若不引入合适的同步机制,会引发以下问题: 数据竞争:多个线程同时修改 ...
- 关于Primavera P6版本选择上的一些看法
从开始接触P6到目前也有近6年的时间,从最开始用的V7 (除P6.2.1)到现在用的18.8.0 ,除去一些小版本,中间自己跨越了8个不同版本 7.0, (2013) 8.2 ...
- 《机器人SLAM导航核心技术与实战》先导课:如何安装Ubuntu系统
<机器人SLAM导航核心技术与实战>先导课:如何安装Ubuntu系统 视频讲解 [先导课]2.如何安装Ubuntu系统-视频讲解 [先导课]2.1.如何安装Ubuntu系统-操作系统概念- ...
- C#多线程编程(二)线程池与TPL
一.直接使用线程的问题 每次都要创建Thread对象,并向操作系统申请创建一个线程,这是需要耗费CPU时间和内存资源的. 无法直接获取线程函数返回值 无法直接捕捉线程函数内发生的异常 使用线程池可以解 ...
- DNS介绍与实现方法
简介:域名系统(Domain Name System,缩写:DNS)是互联网的一项服务.是一种可以将域名和IP地址相互映射的以层次结构分布的数据库系,允许终端用户设备将给定的人类可读URL转换为网络可 ...