热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringBoot整合SpringSecurity+JWT实现单点登录

SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社

SpringBoot整合SpringSecurity+JWT实现单点登录

    • 一、JSON Web Token(JWT)
    • 二、项目背景
    • 三、实现方式及效果
      • 1.登录拦截全局配置
      • 2.JwtToken解析并生成authentication身份信息过滤器
      • 3.自定义登录成功处理器并生成token:响应状态码200及token
      • 4.其他核心处理器没有变化,参考之前的博文:前后端分离 SpringBoot整合SpringSecurity权限控制(动态拦截url)
    • 四、测试
    • 附:GitHub


一、JSON Web Token(JWT)


官方解释:JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为JSON对象安全地传输信息。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对对JWT进行签名。



二、项目背景

公司的业务越来越复杂,随着业务的扩展需要将现有单个后端web系统进行拆分,并在同为顶级域名的多个web系统之间实现单点登录及权限控制,同时也为以后的服务集群部署做准备。


三、实现方式及效果

实现的方式:基于现有的完整权限控制项目,之前整理过的一篇博文 前后端分离 SpringBoot整合SpringSecurity权限控制(动态拦截url),在此基础之上引入JWT实现单点登录。

实现的效果:


  • 用户不带token访问系统B,系统B响应状态码401(需要认证)

  • 用户登录系统A,系统A校验用户名密码成功,生成并响应token及状态码200

  • 用户没有登录系统B而是携带系统A响应的token去访问系统B

  • 系统B解析token并进行权限校验,无权限访问资源则响应403(权限不足),权限验证成功则响应正常的json数据

  • 访问系统C、系统D或分布式集群亦是如此

GitHub: link. 欢迎star

maven依赖:

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

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

1.登录拦截全局配置

@Configuration
public class WebSecurityConfigure extends WebSecurityConfigurerAdapter {
@Resource
private UrlAuthenticationEntryPoint authenticationEntryPoint; //自定义未登录时:返回状态码401
@Resource
private UrlAuthenticationSuccessHandler authenticationSuccessHandler; //自定义登录成功处理器并生成token:响应状态码200及token
@Resource
private UrlAuthenticationFailureHandler authenticationFailureHandler; //自定义登录失败处理器:返回状态码402
@Resource
private UrlAccessDeniedHandler accessDeniedHandler; //自定义权限不足处理器:返回状态码403
@Resource
private UrlLogoutSuccessHandler logoutSuccessHandler; //自定义注销成功处理器:返回状态码200
@Resource
private SelfAuthenticationProvider authenticationProvider; //自定义登录认证
@Resource
private SelfFilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource; //动态获取url权限配置
@Resource
private SelfAccessDecisionManager accessDecisionManager; //自定义权限判断管理器
@Resource
private AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource; //身份验证详细信息源
@Resource
private JwtAuthorizationTokenFilter authorizationTokenFilter; //JwtToken解析并生成authentication身份信息过滤器
@Override
public void configure(WebSecurity web) {
// web.ignoring().antMatchers("/connect/**"); //无条件允许访问
web.ignoring().antMatchers("/common/**"); //无条件允许访问
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider); //自定义登录认证
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 关闭csrf验证(防止跨站请求伪造攻击)
http.csrf().disable();
// JwtToken解析并生成authentication身份信息过滤器
http.addFilterBefore(authorizationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 未登录时:返回状态码401
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
// 无权访问时:返回状态码403
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
// url权限认证处理
http.antMatcher("/**").authorizeRequests()
// .antMatchers("/security/user/**").hasRole("ADMIN") //需要ADMIN角色才可以访问
// .antMatchers("/connect").hasIpAddress("127.0.0.1") //只有ip[127.0.0.1]可以访问'/connect'接口
.anyRequest() //其他任何请求
.authenticated() //都需要身份认证
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(filterInvocationSecurityMetadataSource); //动态获取url权限配置
o.setAccessDecisionManager(accessDecisionManager); //权限判断
return o;
}
});
// 将session策略设置为无状态的,通过token进行登录认证
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 开启自动配置的登录功能
http.formLogin() //开启登录
// .loginPage("/login") //登录页面(前后端不分离)
.loginProcessingUrl("/nonceLogin") //自定义登录请求路径(post)
.usernameParameter("username").passwordParameter("password") //自定义登录用户名密码属性名,默认为username和password
// .successForwardUrl("/index") //登录成功后的url(post,前后端不分离)
// .failureForwardUrl("/error") //登录失败后的url(post,前后端不分离)
.successHandler(authenticationSuccessHandler) //验证成功处理器(前后端分离):生成token及响应状态码200
.failureHandler(authenticationFailureHandler) //验证失败处理器(前后端分离):返回状态码402
.authenticationDetailsSource(authenticationDetailsSource); //身份验证详细信息源(登录验证中增加额外字段)
// 开启自动配置的注销功能
http.logout() //用户注销, 清空session
.logoutUrl("/nonceLogout") //自定义注销请求路径
// .logoutSuccessUrl("/bye") //注销成功后的url(前后端不分离)
.logoutSuccessHandler(logoutSuccessHandler); //注销成功处理器(前后端分离):返回状态码200
}
}

2.JwtToken解析并生成authentication身份信息过滤器

@SuppressWarnings("unchecked")
@Slf4j
@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
@Value("${jwt.token-header-key}")
private String tokenHeaderKey; //token请求头Key
@Value("${jwt.token-prefix}")
private String tokenPrefix; //token前缀
@Value("${jwt.token-secret}")
private String tokenSecret; //token秘钥
/**
* 解析token并生成authentication身份信息
*/

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String token = request.getHeader(tokenHeaderKey);
log.info("JwtAuthorizationTokenFilter >> token:{}", token);
if (null == token || !token.startsWith(tokenPrefix + " ")) {
chain.doFilter(request, response);
return;
}
Claims claims;
try {
// 解析token
claims = Jwts.parser().setSigningKey(tokenSecret).parseClaimsJws(token.replace(tokenPrefix + " ", "")).getBody();
} catch (Exception e) {
log.error("JwtToken validity!! error={}", e.getMessage());
chain.doFilter(request, response);
return;
}
String username = claims.getSubject();
List<String> roles = claims.get("role", List.class);
List<SimpleGrantedAuthority> authorities = roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
if (null != username) {
// 生成authentication身份信息
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}

3.自定义登录成功处理器并生成token:响应状态码200及token

@Component
public class UrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Value("${jwt.token-header-key}")
private String tokenHeaderKey; //token响应头Key
@Value("${jwt.token-prefix}")
private String tokenPrefix; //token前缀
@Value("${jwt.token-secret}")
private String tokenSecret; //token秘钥
@Value("${jwt.token-expiration}")
private Long tokenExpiration; //token过期时间
@Resource
private UserService userService;
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
UrlResponse response = new UrlResponse();
response.setSuccess(true);
response.setCode("200");
response.setMessage("Login Success!");
String username = (String) authentication.getPrincipal(); //表单输入的用户名
Map<String, Object> userInfo = userService.findMenuInfoByUsername(username, response); //用户可访问的菜单信息
response.setData(userInfo);
// 生成token并设置响应头
Claims claims = Jwts.claims();
claims.put("role", authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()));
String token = Jwts.builder()
.setClaims(claims)
.setSubject(username) //设置用户名
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)) //设置token过期时间
.signWith(SignatureAlgorithm.HS512, tokenSecret).compact(); //设置token签名算法及秘钥
httpServletResponse.addHeader(tokenHeaderKey, tokenPrefix + " " + token); //设置token响应头
httpServletResponse.getWriter().write(GsonUtil.GSON.toJson(response));
}
}

4.其他核心处理器没有变化,参考之前的博文:前后端分离 SpringBoot整合SpringSecurity权限控制(动态拦截url)

参考之前的博文或者github项目有详细的记录:前后端分离 SpringBoot整合SpringSecurity权限控制(动态拦截url)


四、测试

1.模拟多系统(或分布式集群)启动三个端口801880288038
在这里插入图片描述

2.没有登录也没有携带token访问系统A,响应401(需要认证)
在这里插入图片描述

3.在系统B的登录接口,响应头中得到了签名token
在这里插入图片描述

4.携带token访问系统A的获取所有用户信息的接口,此token没有该接口访问权限,响应403(权限不足)
在这里插入图片描述

5.在系统A登录具有获取所有用户信息权限的用户,并在响应头中获得签名token
在这里插入图片描述

6.此时携带具有权限的token,访问系统C的获取所用用户信息接口,系统C解析并校验成功,响应json数据
在这里插入图片描述


附:GitHub

GitHub: link. 欢迎star




推荐阅读
  • 深度学习中的Vision Transformer (ViT)详解
    本文详细介绍了深度学习中的Vision Transformer (ViT)方法。首先介绍了相关工作和ViT的基本原理,包括图像块嵌入、可学习的嵌入、位置嵌入和Transformer编码器等。接着讨论了ViT的张量维度变化、归纳偏置与混合架构、微调及更高分辨率等方面。最后给出了实验结果和相关代码的链接。本文的研究表明,对于CV任务,直接应用纯Transformer架构于图像块序列是可行的,无需依赖于卷积网络。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 阿里Treebased Deep Match(TDM) 学习笔记及技术发展回顾
    本文介绍了阿里Treebased Deep Match(TDM)的学习笔记,同时回顾了工业界技术发展的几代演进。从基于统计的启发式规则方法到基于内积模型的向量检索方法,再到引入复杂深度学习模型的下一代匹配技术。文章详细解释了基于统计的启发式规则方法和基于内积模型的向量检索方法的原理和应用,并介绍了TDM的背景和优势。最后,文章提到了向量距离和基于向量聚类的索引结构对于加速匹配效率的作用。本文对于理解TDM的学习过程和了解匹配技术的发展具有重要意义。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • iOS Swift中如何实现自动登录?
    本文介绍了在iOS Swift中如何实现自动登录的方法,包括使用故事板、SWRevealViewController等技术,以及解决用户注销后重新登录自动跳转到主页的问题。 ... [详细]
  • 本文介绍了OpenStack的逻辑概念以及其构成简介,包括了软件开源项目、基础设施资源管理平台、三大核心组件等内容。同时还介绍了Horizon(UI模块)等相关信息。 ... [详细]
  • python限制递归次数(python最大公约数递归)
    本文目录一览:1、python为什么要进行递归限制 ... [详细]
  • 图像因存在错误而无法显示 ... [详细]
  • 本文介绍了一个React Native新手在尝试将数据发布到服务器时遇到的问题,以及他的React Native代码和服务器端代码。他使用fetch方法将数据发送到服务器,但无法在服务器端读取/获取发布的数据。 ... [详细]
  • 概述H.323是由ITU制定的通信控制协议,用于在分组交换网中提供多媒体业务。呼叫控制是其中的重要组成部分,它可用来建立点到点的媒体会话和多点间媒体会议 ... [详细]
author-avatar
浪漫的白狼族
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有