当前位置: 首页 > news >正文

Spring security 自定义 token 身份验证

👉 请投票支持这款 全新设计的脚手架 ,让 Java 再次伟大!

在这里插入图片描述

前言

既然 Spring securtiy 的核心是 Filter chains,那我们只需要定义一个符合 security 标准的 filter ,再把自己的 chain 加入到 VirtualFilterChain 里面,就可以自定义身份验证的认证逻辑了。

之前提到过,大部分身份认证相关的过滤器都继承了 AbstractAuthenticationProcessingFilter。但是这一次我们让接下来的自定义过滤器不选择继承它,而是继承 OncePerRequestFilter。

这个类之前我们没有见过,其实它也是 security 框架提供的一个 VirtualFilter 父类。继承一个陌生的类听起来有点吓人,不过很快你就知道 AbstractAuthenticationProcessingFilter 也好,OncePerRequestFilter 也罢,都是 filter 而已。

自定义一个 Filter

我们参照 security 的基础身份认证过滤器 UsernamePasswordAuthenticationFilter 来编写我们的过滤器。这是不是意味着我们也必须使用模板设计模式来编写自定义过滤器的父类,然后聚合 providerManager 再自定义 provider 实现一整套 security 范式的身份认证代码?

不是的,你完全可以把 provider 的逻辑全部抽离出来加入到你的自定义过滤器里面。实际上我的过滤器一共就三个逻辑。非常简单并且可以工作的很好!

  1. 通过确认 token 的有效性来验证请求的身份。类似于 DaoAuthenticationProvider.additionalAuthenticationChecks。
  2. 从数据库检索出用户的详情,方便后续取用。类似于 DaoAuthenticationProvider. retrieveUser()。
  3. 将验证通过的身份资产(principal)封装到 UsernamePasswordAuthenticationToken,并设置到 security context。类似于 AbstractAuthenticationProcessingFilter.successfulAuthentication。
 @Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain chain)throws ServletException, IOException {String header = request.getHeader(HttpHeaders.AUTHORIZATION);if (StringUtils.isEmpty(header) || !header.startsWith(BEARER_SPACE)) {globalExceptionHandler.commence(request, response, new AuthenticationException("非法的 token 请求参数") {});return;}String token = header.split(" ")[1].trim();try {TokenUtil.verifyToken(token, base64Secret);} catch (FoundationAppException e) {globalExceptionHandler.commence(request, response, new AuthenticationException("非法的 token") {});return;}// 返回 401// jwt 认证通过即代表认证成功。将 principal 资源装配到 contextUserDetails userDetails = userAppService.loadUserByUsername(TokenUtil.getUsername(token) + "@" + TokenUtil.getDomain(token));UsernamePasswordAuthenticationTokenauthentication = new UsernamePasswordAuthenticationToken(userDetails, null,userDetails.getAuthorities());if (!userDetails.isEnabled()) {globalExceptionHandler.commence(request, response, new DisabledException("当前账户已锁定"));return;}authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authentication);chain.doFilter(request, response);}

实现 loadByUsername

让我们的任何一个类——我建议是和 user 相关处理的 service 类实现 UserDetailsService 接口,就可以重写 loadByUsername 方法了。
实现这个接口很重要,因为他定义了 UserDetails 接口——这个你的用户对象应该遵守的规范和行为。

@Overridepublic UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {String[] split = name.split("@");String username = split[0];String domainCode = split[1];Domain domain = domainRepository.findByCode(domainCode).orElseThrow(() -> new UsernameNotFoundException(String.format("User with domain - %s, not found", domainCode)));AuthUser user = authUserRepository.findByDomainAndUsername(domain, username).orElseThrow(() -> new UsernameNotFoundException(String.format("User with username and domain - %s, not found", name)));return user;}

考虑一下异常处理

自定义 token 过滤器的工作这就基本完成了。不过健壮的处理程序,往往都伴随着异常逻辑的完美捕获。回忆之前的内容,filter 的异常处理都是通过端点处理程序——AuthenticationEntryPointFailureHandler 来处理的。所以只要我们自定义端点处理程序并重写 commence 方法就完全可以达到一样的效果。

AuthenticationEntryPointFailureHandler

@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,AuthenticationException exception) throws IOException, ServletException {this.authenticationEntryPoint.commence(request, response, exception);}

自定义异常处理程序

    @Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {log.error("spring security 认证发生异常。", e);HttpResponseWriter.sendError(httpServletResponse, HttpServletResponse.SC_UNAUTHORIZED, "身份认证失败");}@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {log.error("spring security 鉴权发生异常。", e);HttpResponseWriter.sendError(httpServletResponse, HttpServletResponse.SC_FORBIDDEN, "鉴权失败");}

将自定义内容装配进 security 架构

要想我们的过滤器先一步生效,需要将过滤器加入到 security 自带的认证过滤器之前;接着还需要把自定义异常配置到异常处理流程中。是的,就这些工作内容,没有更多了。

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserAppService userAppService;@Autowiredprivate JwtTokenFilter jwtTokenFilter;@Autowiredprivate GlobalExceptionHandler globalExceptionHandler;/*** 配置密码加密装置*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 配置 Dao Authenticationprovider 协调器*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// configure authentication managerauth.userDetailsService(userAppService);}@Overridepublic void configure(WebSecurity web) {web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**").antMatchers("/swagger-ui/index.html").antMatchers("/test/**");}/*** web 安全配置*/@Overrideprotected void configure(HttpSecurity http) throws Exception {// Enable CORS and disable CSRFhttp = http.cors().and().csrf().disable();// Set session management to statelesshttp = http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and();// exception handlerhttp.exceptionHandling().authenticationEntryPoint(globalExceptionHandler).accessDeniedHandler(globalExceptionHandler);// Set permissions on endpointshttp.authorizeRequests()// Our public endpoints.antMatchers("/foundation/api/example/**").permitAll().anyRequest().authenticated();// Add JWT token filterhttp.addFilterBefore(jwtTokenFilter,UsernamePasswordAuthenticationFilter.class);}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}

结语

这就是自定义 token 身份验证与配置的全部工作内容。由于 spring security 大量使用了组合与设计模式使得任何开发者都可以定义出各式各样的身份验证逻辑,所以只要你的自定义程序可以嵌合到 security 框架里并且可以很好的工作,那他就是适合你的身份验证。


http://www.mrgr.cn/news/55159.html

相关文章:

  • 2024年4个好用的录屏软件大盘点,轻松录制精彩瞬间。
  • 大模型的检索增强生成综述研究
  • 动手学深度学习63 束搜索
  • 双ll将至,你找到好用的API接口获取商品详情数据吗?
  • nginx过滤模块怎么生效的
  • OpenCV高级图形用户界面(21)暂停程序执行并等待用户按键输入函数waitKey()的使用
  • 学习docker第五弹------Docker容器数据卷
  • 【题解】—— LeetCode一周小结42
  • 一般HR面试程序员会问什么问题?
  • 如何在分布式环境中实现高可靠性分布式锁
  • Openlayers高级交互(4/20):手绘多边形,导出KML文件,可以自定义name和style
  • 分布式系统中的Dapper与Twitter Zipkin:链路追踪技术的实现与应用
  • 全面击破工程级复杂缓存难题
  • 温度传感器
  • Java网络编程 - headers
  • YOLOv11模型改进-注意力-引入简单无参数注意力模块SimAM 提升小目标和遮挡检测
  • 华为OD机试 - 贪心歌手 - 动态规划(Python/JS/C/C++ 2024 D卷 200分)
  • 探秘Python读取文件内容的奥秘:从入门到精通
  • 500强企业是如何进行数据安全建设的?看这篇就够了
  • javaspringbootmsyql银行客户管理系统91826-计算机毕业设计项目选题推荐(附源码)
  • 政府采购合同公告明细数据(1996-2024年)
  • AI没必要学
  • 使用上述Python脚本来更新系统环境变量
  • 局域网——Prim Kruskal
  • Python学习100天第14天之网络编程入门和网络应用开发
  • 什么是智能电网?