Spring Security OAuth2.0认证授权六:前后端分离下的登录授权
历史文章
Spring Security OAuth2.0认证授权一:框架搭建和认证测试
Spring Security OAuth2.0认证授权二:搭建资源服务
Spring Security OAuth2.0认证授权三:使用JWT令牌
Spring Security OAuth2.0认证授权四:分布式系统认证授权
Spring Security OAuth2.0认证授权五:用户信息扩展到jwt
本篇文章将会解决上一篇文章《Spring Security OAuth2.0认证授权五:用户信息扩展到jwt 》中遗留的问题,并在原有的项目中新增模块business-server用来充当前端页面的web容器并转发登录请求和更换token的请求等,以模拟前后端分离下的登录以及更换token操作。
一、jwt令牌在网关处的过期时间校验
上一篇文章中讲了在网关处解析token并转发到目标服务的操作,因为使用了jwt令牌的原因,所以省了一步到认证服务器认证的操作,只要验签成功,就认为令牌有效。这实际上留下了一个bug:服务端无法主动取消jwt令牌,所以这个令牌只要客户端保存下来,如果不调用认证服务器的令牌验证接口,这个jwt令牌将永远有效。因此需要在网关处加上对过期时间的校验。
在TokenFilter中添加以下代码逻辑
//取出exp字段,判断token是否已经过期
try {
Map<String, Object> map = objectMapper.readValue(payLoad, new TypeReference<Map<String, Object>>() {
});
long expiration = ((Integer) map.get("exp")) * 1000L;
if (expiration < new Date().getTime()) {
return unAuthorized(exchange, "未认证的请求:token存在,但是已经失效",WrapperResult.TOKEN_EXPIRE);
}
} catch (IOException e) {
log.error("", e);
return unAuthorized(exchange, "未认证的请求:错误的token",null);
}
二、refresh-token接口缺少用户信息
refresh-token在access_token过期,但是refresh-token未过期的时候使用,目的是使用refresh_token更新已经过期的access_token,这样理论上来说,客户端只要能在refresh_token过期之前进行任意操作,就可以避免重新登录了。
上一篇文章中将用户信息放到了jwt token中并返回给客户端,但是如果使用refresh_token更新token,后端会报错,前端取到的token中则缺少了用户信息。究其原因,和JwtAccessTokenConverter有关系,关于这个类的实例,当初创建的方法如下
@Bean
public JwtAccessTokenConverter accessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//对称秘钥,资源服务器使用该秘钥来验证
return jwtAccessTokenConverter;
}
这里的new操作省了很多默认参数的指定,且先看下为啥会缺少用户信息,扩展用户信息的关键在于方法com.kdyzm.spring.security.auth.center.service.MyUserDetailsServiceImpl#loadUserByUsername,这里扩展了用户信息,使其从单纯的username字符串变成了UserDetailsExpand对象,然后在增强方法com.kdyzm.spring.security.auth.center.enhancer.CustomTokenEnhancer#enhance中将扩展信息取出来放到Token中。
经过debug,发现

最终发现是如下代码的问题org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter#extractAuthentication
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME)) {
Object principal = map.get(USERNAME);
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
//运行到这里的时候userDetailsService为空,所以并没有执行自定义的loadUserByUsername方法
if (userDetailsService != null) {
UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
authorities = user.getAuthorities();
principal = user;
}
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
}
return null;
}
层层网上追寻调用链,竟然是JwtAccessTokenConverter创建的时候省略参数导致的,只需要如此做就可以解决问题了
@Bean
public JwtAccessTokenConverter accessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
DefaultAccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
userTokenConverter.setUserDetailsService(userDetailsService);
tokenConverter.setUserTokenConverter(userTokenConverter);
jwtAccessTokenConverter.setAccessTokenConverter(tokenConverter);
jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);//对称秘钥,资源服务器使用该秘钥来验证
return jwtAccessTokenConverter;
}
JwtAccessTokenConverter对象创建的时候指定DefaultUserAuthenticationConverter使用的userDetailsService即可。
三、新建business-server模块作为web容器
这里新建的business-server模块有两个功能
- 充当web容器,该服务并没有使用模板化技术,使用的是纯html、css实现前端
- 转发前端登录、更换token请求
可能会有人对第二条有疑问,为什么要这么做?之前测试的时候基本上都是使用postman发起的请求,请求的方式是这样的http://127.0.0.1:30000/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=zhangsan&password=123可以看到这里传递了很重要的参数client_id和client_secret,这两个参数无论如何也不应当泄露给前端,通常都是中间的真正的客户端服务拼接这两个参数再将请求转发给认证服务
四、前后端分离
设计上想要实现以下功能
- 首页未登录则提示用户登录,已经登录则展示用户个人信息
- 用户登录之后将令牌保存到localStorage
- token过期之后用户可以选择使用refresh_token更换已经过期的令牌(access_token)
- 已经过期的refresh_token不能用于更换新的令牌
1、关闭认证服务表单登录
以前请求认证服务的任意接口,如果没有认证,则都会跳转到系统自带的登录页面,现在我们想要实现前后端分离了,原来系统自带的登录页面就有些碍眼了,直接关闭就好。关闭方法如下,spring security的配置更改为如下:
.formLogin()
.disable();
2、前后端代码
前端代码在business-server/src/main/resources/static目录下,只有两个页面,一个首页,一个登陆页面
后端只有两个接口
- 登录接口:com.kdyzm.spring.security.oauth.study.business.server.controller.LoginController#login
- 更新token接口:com.kdyzm.spring.security.oauth.study.business.server.controller.TokenController#refreshToken
其它不做赘述,不过前端页面写起来挺麻烦的。。难是不难的
五、测试
源代码:
测试前首先需要重新执行初始化sql(auth-server/docs/sql/init.sql),然后依次启动 register-server、gateway-server、auth-server、resource-server、business-server 五个服务
启动成功后打开浏览器,输入http://127.0.0.1:30002/地址,就会看到以下页面

点击登录之后,出现登录框

输入账号密码之后,登录成功之后会跳转首页,就会看到个人信息

这里设置的token有效期为10秒,所以很快token就会失效,十秒钟之后刷新页面就会有新的提示

接下来可以有两种选择,一种是使用refresh-token更新失效的令牌,另外一种是重新登录,这里refresh_token的有效期也很短,只有30秒,如果超出30秒,则会更新失败,提示如下

而如果在30秒内刷新令牌,则会重新获取到令牌并刷新当前页
六、源代码地址
源代码地址:https://gitee.com/kdyzm/spring-security-oauth-study/tree/v7.0.0
我的博客地址:https://blog.kdyzm.cn/
Spring Security OAuth2.0认证授权六:前后端分离下的登录授权的更多相关文章
- Spring Security OAuth2.0认证授权四:分布式系统认证授权
Spring Security OAuth2.0认证授权系列文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授 ...
- Spring Security OAuth2.0认证授权三:使用JWT令牌
Spring Security OAuth2.0系列文章: Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二: ...
- Spring Security OAuth2.0认证授权二:搭建资源服务
在上一篇文章[Spring Security OAuth2.0认证授权一:框架搭建和认证测试](https://www.cnblogs.com/kuangdaoyizhimei/p/14250374. ...
- Spring Security OAuth2.0认证授权五:用户信息扩展到jwt
历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...
- spring security简单教程以及实现完全前后端分离
spring security是spring家族的一个安全框架,入门简单.对比shiro,它自带登录页面,自动完成登录操作.权限过滤时支持http方法过滤. 在新手入门使用时,只需要简单的配置,即可实 ...
- Spring Security OAuth2.0认证授权一:框架搭建和认证测试
一.OAuth2.0介绍 OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不 需要将用户名和密码提供给第三方应用或分享他们数据的所有内容. 1.s ...
- Session与Token认证机制 前后端分离下如何登录
字号 1 Web登录涉及到知识点 1.1 HTTP无状态性 HTTP是无状态的,一次请求结束,连接断开,下次服务器再收到请求,它就不知道这个请求是哪个用户发过来的.当然它知道是哪个客户端地址发过来的 ...
- Spring security OAuth2.0认证授权学习第四天(SpringBoot集成)
基础的授权其实只有两行代码就不单独写一个篇章了; 这两行就是上一章demo的权限判断; 集成SpringBoot SpringBoot介绍 这个篇章主要是讲SpringSecurity的,Spring ...
- Spring security OAuth2.0认证授权学习第三天(认证流程)
本来之前打算把第三天写基于Session认证授权的,但是后来视屏看完后感觉意义不大,而且内容简单,就不单独写成文章了; 简单说一下吧,就是通过Servlet的SessionApi 通过实现拦截器的前置 ...
随机推荐
- ASP.NET Core 3.1 IOC容器以及默认DI以及替换Autofac生命周期
IOC 就是我们需要一个对象 以前我们是去 new 现在我们是直接向 IOC容器 要我们需要的那个对象. 使用一个IOC容器(autofac)通过依赖注入控制各个组件的耦合.也就是说你写好了组件,不需 ...
- 企业运维案例:xxx is not in the sudoers file.This incident will be reported” 错误解决方法
CentOS6系统下,普通用户使用sudo执行命令时报错: xxx is not in the sudoers file.This incident will be reported" 解决 ...
- MacOS下解决宿主机和docker容器之间网络互通
docker在Mac下使用非常方便,官网提供了desktop版本的可视化软件,甚至还十分贴心地集成好了k8s套件.然而如果想同时部署和使用多个容器,每个容器不使用127.0.0.1地址,而是各自有ip ...
- 为了搞清楚类加载,竟然手撸JVM!
作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...
- Spring Cloud Config原码篇(十)
上篇中说到通过@Value注解获取配置中心的内容进行注入,要想了解这个就要知道spring Environment原理,关于这原理我看了下网上分析的文章:https://blog.csdn.net/t ...
- AtCoder Beginner Contest 187 F - Close Group
题目链接 点我跳转 题目大意 给你一张完全图,你可以删除任意数量的边 要求删除完后剩余的所有子图必须是完全图 问完全子图数量最少是多少 解题思路 定义 \(ok[i]\) 表示状态为 \(i\) 时所 ...
- SpringMVC的@Validated校验注解使用方法
validate会对参数进行校验,校验标准为validate后的类中的标准.本例中对User进行校验,User类中设置了校验标准. 在后台开发过程中,对参数的校验成为开发环境不可缺少的一个环节.比如参 ...
- for _ in range( ):
for _ in range( ): { //函数体 } 其中"-"只是一个占位符,可以把它理解为i或者j等等任意的字母. 上面代码相当于同下: for i in range( ) ...
- Spring(1) --入门(IoC,AOP)
说说你对spring的理解? Spring框架是一个轻量级的企业级开发的一站式解决方案,所谓一站式解决方案就是可以基于Spring解决Java EE开发的所有问题.Spring框架主要提供了IoC容器 ...
- virsh常见命令笔记
[基本命令] virsh start 启动 shutdown 关闭 destroy 强制断电 suspend 挂起 resume 恢复 undefine 删除 dominfo 查看配置信息 domif ...