历史文章

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模块有两个功能

  1. 充当web容器,该服务并没有使用模板化技术,使用的是纯html、css实现前端
  2. 转发前端登录、更换token请求

可能会有人对第二条有疑问,为什么要这么做?之前测试的时候基本上都是使用postman发起的请求,请求的方式是这样的http://127.0.0.1:30000/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=zhangsan&password=123可以看到这里传递了很重要的参数client_idclient_secret,这两个参数无论如何也不应当泄露给前端,通常都是中间的真正的客户端服务拼接这两个参数再将请求转发给认证服务

四、前后端分离

设计上想要实现以下功能

  1. 首页未登录则提示用户登录,已经登录则展示用户个人信息
  2. 用户登录之后将令牌保存到localStorage
  3. token过期之后用户可以选择使用refresh_token更换已经过期的令牌(access_token)
  4. 已经过期的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-servergateway-serverauth-serverresource-serverbusiness-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认证授权六:前后端分离下的登录授权的更多相关文章

  1. Spring Security OAuth2.0认证授权四:分布式系统认证授权

    Spring Security OAuth2.0认证授权系列文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授 ...

  2. Spring Security OAuth2.0认证授权三:使用JWT令牌

    Spring Security OAuth2.0系列文章: Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二: ...

  3. Spring Security OAuth2.0认证授权二:搭建资源服务

    在上一篇文章[Spring Security OAuth2.0认证授权一:框架搭建和认证测试](https://www.cnblogs.com/kuangdaoyizhimei/p/14250374. ...

  4. Spring Security OAuth2.0认证授权五:用户信息扩展到jwt

    历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...

  5. spring security简单教程以及实现完全前后端分离

    spring security是spring家族的一个安全框架,入门简单.对比shiro,它自带登录页面,自动完成登录操作.权限过滤时支持http方法过滤. 在新手入门使用时,只需要简单的配置,即可实 ...

  6. Spring Security OAuth2.0认证授权一:框架搭建和认证测试

    一.OAuth2.0介绍 OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不 需要将用户名和密码提供给第三方应用或分享他们数据的所有内容. 1.s ...

  7. Session与Token认证机制 前后端分离下如何登录

     字号 1 Web登录涉及到知识点 1.1 HTTP无状态性 HTTP是无状态的,一次请求结束,连接断开,下次服务器再收到请求,它就不知道这个请求是哪个用户发过来的.当然它知道是哪个客户端地址发过来的 ...

  8. Spring security OAuth2.0认证授权学习第四天(SpringBoot集成)

    基础的授权其实只有两行代码就不单独写一个篇章了; 这两行就是上一章demo的权限判断; 集成SpringBoot SpringBoot介绍 这个篇章主要是讲SpringSecurity的,Spring ...

  9. Spring security OAuth2.0认证授权学习第三天(认证流程)

    本来之前打算把第三天写基于Session认证授权的,但是后来视屏看完后感觉意义不大,而且内容简单,就不单独写成文章了; 简单说一下吧,就是通过Servlet的SessionApi 通过实现拦截器的前置 ...

随机推荐

  1. OC 大数组分割成由小数组重新组合的新数组

    NSLog(@"++++%@",[self seprateBigArrBySize:3 BigArr:@[@"1",@"2",@" ...

  2. MVC中Bundle的使用

    BundleConfig配置 (1)StyleBundle中的参数,即为cshtml中需要调用的虚拟路径名称. (2)Include包含路径,可以包含一个或多个css或js文件.即包含一组文件. pu ...

  3. python序列(十)字典

    字典是无序可变序列. 定义字典是,每个元素的键和值用冒号分隔,元素之间用逗号分隔,所有的元素放在一对大括号"{ }"中. 字典中的键可以为任意不可变数据,比如.整数.实数.复数.字 ...

  4. 两千星 .NET5 框架 Furion 亮点分析(一)

    让 .NET 开发更简单,更通用,更流行. Furion 介绍 Furion 是基于 .NET5 平台下打造的现代化 Web 框架.旨在 让 .NET 开发更简单,更通用,更流行.. Furion 非 ...

  5. CentOS7下常用安装软件服务rpm方式的介绍

    简介:介绍rpm软件包的管理 rpm安装:安装别人编译好的软件包,rpm即Redhat Package Manager,是Redhat的软件包管理方式   rpm安装优点: 软件已经编译打包,所以传输 ...

  6. 其实吧,LRU也就那么回事。

    这是why哥的第 81 篇原创文章 你面试的时候遇见过LRU吗? LRU 算法,全称是Least Recently Used. 翻译过来就是最近最少使用算法. 这个算法的思想就是:如果一个数据在最近一 ...

  7. char*,const char*和string 互转

    1. string转const char* 1 string s = "abc"; 2 const char* c_s = s.c_str(); 2. const char*转st ...

  8. spring传播机制注意点

    在同一个类里面spring的传播机制是不起作用的比如说在执行saveA方法的时候调用C方法插入C设置的传播属性是不使用事物 但是执行的效果是saveA方法抛出异常后导致C的记录回滚了也就是说明C方法设 ...

  9. 讲两个int 数组找出重复的数字 用最少的循环

    int a[] = {1,3}; int b[] = {1,3,5}; int size = a.length>b.length ?a.length:b.length; int valueA = ...

  10. [leetcode]725. Split Linked List in Parts链表分块

    思路很简单  按时链表的题做起来很容易犯小错误,思维要缜密 还要多练习啊 做之前最好画算法框图 public ListNode[] splitListToParts(ListNode root, in ...