一、背景

前一节我们学习了 Spring Authorization Server的使用,此处我们简单的记录下 Spring 资源服务器的使用。

二、需求

资源服务器提供2个资源 ,userInfohello
userInfo:资源是受保护的资源,需要user.userInfo权限才可以访问。
hello:资源是公开资源,不要权限即可访问。

三、分析

1、如何验证资源服务器中访问的令牌是有效的?

此处只考虑JWT的令牌。

令牌是授权服务器颁发的,且进行了签名操作,因此资源服务器对令牌的验证,就需要授权服务器的JWK信息,此处可以配置JwtDecoder来实现,并且填写好jwk set uri

2、令牌是从请求中那个地方来的?
令牌可以从请求的 request header或者request query param中获取,因此就需要配置 BearerTokenResolver来实现。

3、令牌中的权限字段,默认会加上SCOPE_前缀,想去掉如何操作。
配置JwtAuthenticationConverter对象。

4、如果像向JWT的claim中增加值如何操作?
通过 JwtDecoder#setClaimSetConverter来操作。此处也可以实现删除claim的中内容。

5、如何验证JWT是否合法?
通过 JwtDecoder#setJwtValidator方法来操作。

6、如何设置从授权服务器获取JWK的超时时间?
通过 JwkSetUriJwtDecoderBuilder#restOperations来操作。

四、资源服务器认证流程


1、请求会被 BearerTokenAuthenticationFilter 拦截器拦截,并从中解析出token出来,如果没有解析出来,则由下一个过滤器处理。解析出来则构建一个BearerTokenAuthenticationToken对象。
2、下一步将HttpServletRequest传递给AuthenticationManagerResolver对象,由它选择出AuthenticationManager对象,然后将 BearerTokenAuthenticationToken传递给AuthenticationManager对象进行认证。AuthenticationManager对象的实现,取决于我们的token对象是JWT还是opaque token
3、验证失败

  1. 清空 SecurityContextHolder 对象。
  2. 交由AuthenticationFailureHandler对象处理。

4、验证成功

  1. Authentication对象设置到SecurityContextHolder中。
  2. 交由余下的过滤器继续处理。

五、实现资源服务器

1、引入jar包

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

2、资源服务器配置

package com.huan.study.resource.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.MappedJwtClaimSetConverter;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Collections; /**
* 资源服务器配置
*
* @author huan.fu 2021/7/16 - 下午5:00
*/
@EnableWebSecurity
public class ResourceServerConfig extends WebSecurityConfigurerAdapter { private static final Logger log = LoggerFactory.getLogger(ResourceServerConfig.class); @Autowired
private RestTemplateBuilder restTemplateBuilder; @Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 对于 userInfo 这个api 需要 s
.antMatchers("/userInfo").access("hasAuthority('user.userInfo')")
.and()
.oauth2ResourceServer()
.jwt()
// 解码jwt信息
.decoder(jwtDecoder(restTemplateBuilder))
// 将jwt信息转换成JwtAuthenticationToken对象
.jwtAuthenticationConverter(jwtAuthenticationConverter())
.and()
// 从request请求那个地方中获取 token
.bearerTokenResolver(bearerTokenResolver())
// 此时是认证失败
.authenticationEntryPoint((request, response, exception) -> {
// oauth2 认证失败导致的,还有一种可能是非oauth2认证失败导致的,比如没有传递token,但是访问受权限保护的方法
if (exception instanceof OAuth2AuthenticationException) {
OAuth2AuthenticationException oAuth2AuthenticationException = (OAuth2AuthenticationException) exception;
OAuth2Error error = oAuth2AuthenticationException.getError();
log.info("认证失败,异常类型:[{}],异常:[{}]", exception.getClass().getName(), error);
}
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_JSON.toString());
response.getWriter().write("{\"code\":-3,\"message\":\"您无权限访问\"}");
})
// 认证成功后,无权限访问
.accessDeniedHandler((request, response, exception) -> {
log.info("您无权限访问,异常类型:[{}]", exception.getClass().getName());
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_JSON.toString());
response.getWriter().write("{\"code\":-4,\"message\":\"您无权限访问\"}");
})
;
} /**
* 从request请求中那个地方获取到token
*/
private BearerTokenResolver bearerTokenResolver() {
DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
// 设置请求头的参数,即从这个请求头中获取到token
bearerTokenResolver.setBearerTokenHeaderName(HttpHeaders.AUTHORIZATION);
bearerTokenResolver.setAllowFormEncodedBodyParameter(false);
// 是否可以从uri请求参数中获取token
bearerTokenResolver.setAllowUriQueryParameter(false);
return bearerTokenResolver;
} /**
* 从 JWT 的 scope 中获取的权限 取消 SCOPE_ 的前缀
* 设置从 jwt claim 中那个字段获取权限
* 如果需要同多个字段中获取权限或者是通过url请求获取的权限,则需要自己提供jwtAuthenticationConverter()这个方法的实现
*
* @return JwtAuthenticationConverter
*/
private JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
// 去掉 SCOPE_ 的前缀
authoritiesConverter.setAuthorityPrefix("");
// 从jwt claim 中那个字段获取权限,模式是从 scope 或 scp 字段中获取
authoritiesConverter.setAuthoritiesClaimName("scope");
converter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
return converter;
} /**
* jwt 的解码器
*
* @return JwtDecoder
*/
public JwtDecoder jwtDecoder(RestTemplateBuilder builder) {
// 授权服务器 jwk 的信息
NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri("http://qq.com:8080/oauth2/jwks")
// 设置获取 jwk 信息的超时时间
.restOperations(
builder.setReadTimeout(Duration.ofSeconds(3))
.setConnectTimeout(Duration.ofSeconds(3))
.build()
)
.build();
// 对jwt进行校验
decoder.setJwtValidator(JwtValidators.createDefault());
// 对 jwt 的 claim 中增加值
decoder.setClaimSetConverter(
MappedJwtClaimSetConverter.withDefaults(Collections.singletonMap("为claim中增加key", custom -> "值"))
);
return decoder;
}
}

此处对资源服务器进行了很多的自定义操作,因此配置比较长。

3、资源

1个受保护的资源和一个非受保护的资源。

@RestController
public class UserController { /**
* 这个是受保护的资源,需要 user.userInfo 权限才可以访问。
*/
@GetMapping("userInfo")
public Map<String, Object> userInfo(@AuthenticationPrincipal Jwt principal) {
return new HashMap<String, Object>(4) {{
put("principal", principal);
put("userInfo", "获取用户信息");
}};
} /**
* 非受权限保护的资源
*/
@GetMapping("hello")
public String hello() {
return "hello 不要需要受保护的资源";
}
}

六、测试

1、访问非受保护的资源


可以看到不需要token即可以访问。

2、访问受保护的资源


1、先不用token访问,可以看到是拒绝的。
2、然后通过授权服务器生成一个token,授权服务器为上一篇文章使用的授权服务器。
3、通过token访问后,可以返现可以访问资源了。
4、演示可以向token的claim中增加值。
5、演示 userInfo 是需要user.userInfo权限的。

七、完整代码

1、授权服务器,为上篇文章中的授权服务器
https://gitee.com/huan1993/spring-cloud-parent/tree/master/security/authorization-server
2、资源服务器
https://gitee.com/huan1993/spring-cloud-parent/tree/master/security/resource-server

八、参考文档

1、https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2resourceserver

Spring Security Resource Server的使用的更多相关文章

  1. Spring Cloud(6.3):搭建OAuth2 Resource Server

    配置web.xml 添加spring-cloud-starter-security,spring-security-oauth2-autoconfigure2个依赖. <!-- Spring c ...

  2. 【OAuth2.0】Spring Security OAuth2.0篇之初识

    不吐不快 因为项目需求开始接触OAuth2.0授权协议.断断续续接触了有两周左右的时间.不得不吐槽的,依然是自己的学习习惯问题,总是着急想了解一切,习惯性地钻牛角尖去理解小的细节,而不是从宏观上去掌握 ...

  3. REST Security with JWT using Java and Spring Security

    Security Security is the enemy of convenience, and vice versa. This statement is true for any system ...

  4. Cross Site Request Forgery (CSRF)--spring security -转

    http://docs.spring.io/spring-security/site/docs/3.2.0.CI-SNAPSHOT/reference/html/csrf.html 13. Cross ...

  5. Spring security oauth2最简单入门环境搭建

    关于OAuth2的一些简介,见我的上篇blog:http://wwwcomy.iteye.com/blog/2229889 PS:貌似内容太水直接被鹳狸猿干沉.. 友情提示 学习曲线:spring+s ...

  6. Spring Security Oauth2系列(一)

    前言: 关于oauth2,其实是一个规范,本文重点讲解spring对他进行的实现,如果你还不清楚授权服务器,资源服务器,认证授权等基础概念,可以移步理解OAuth 2.0 - 阮一峰,这是一篇对于oa ...

  7. spring security oauth2

    https://connect.qq.com/manage.html#/ http://wiki.connect.qq.com/%E7%BD%91%E7%AB%99%E5%BA%94%E7%94%A8 ...

  8. Spring Security OAuth2 SSO

    通常公司肯定不止一个系统,每个系统都需要进行认证和权限控制,不可能每个每个系统都自己去写,这个时候需要把登录单独提出来 登录和授权是统一的 业务系统该怎么写还怎么写 最近学习了一下Spring Sec ...

  9. Spring Security OAuth 2.0

    续·前一篇<OAuth 2.0> OAuth 2.0 Provider 实现 在OAuth 2.0中,provider角色事实上是把授权服务和资源服务分开,有时候它们也可能在同一个应用中, ...

随机推荐

  1. 运输层协议:TCP和UDP

    运输层简介 运输层的通信实体不再是主机,而是主机中的进程.运输层的通信是一台主机的进程和另一台主机的进程进行数据交换. 运输层作用 运输层向上层的应用层提供通信服务 运输层为进程提供端到端的通信 运输 ...

  2. github上使用C语言实现的线程池

    网上介绍线程池的知识很多,但是在代码实现上介绍的又不是那么多.而且给人的一种感觉就是:你的这种实现是正规的方式还是你自己的实现? 如果有这么个疑问,且想找一个靠谱的代码拿来使用,那么这个项目是个不错的 ...

  3. Stream流方法引用

    一.对象存在,方法也存在,双冒号引用 1.方法引用的概念: 使用实例: 1.1先定义i一个函数式接口: 1.2定义一个入参参数列表有函数式接口的方法: 1.3调用这个入参有函数式接口的方法: lamb ...

  4. python模块--calendar

    方法 返回值类型 说明 .calendar(theyear, w=2, l=1, c=6, m=3) str 返回指定年份的年历, w: 每个日期的宽度, l: 每一行的纵向宽度, c: 月与月之间的 ...

  5. 【第六篇】- Maven 仓库之Spring Cloud直播商城 b2b2c电子商务技术总结

    Maven 仓库 在 Maven 的术语中,仓库是一个位置(place). Maven 仓库是项目中依赖的第三方库,这个库所在的位置叫做仓库. 在 Maven 中,任何一个依赖.插件或者项目构建的输出 ...

  6. 定时器及PWM

    1 定时器 1.1 定时器分类 对于STM32来说,定时器可分为基本定时器.通用定时器.高级定时器三类,后者包括前者的全部功能.以stm32f1系列为例,TIM6和TIM7为基本定时器,TIM2~TI ...

  7. C++吃金币小游戏

    上图: 游戏规则:按A,D键向左和向右移动小棍子,$表示金币,0表示炸弹,吃到金币+10分,吃到炸弹就GAME OVER. 大体思路和打字游戏相同,都是使用数组,refresh和run函数进行,做了一 ...

  8. mongodb linux基本启动 基础增删改 mysql语法的对比

    一.主流数据源类型 还存在自定义数据源以及REST接口数据,共6中数据源. 二.linux下启动连接数据库 进去mongodb的目录启动服务:mongo --host 192.168.320.826 ...

  9. [转载]linux环境变量设置方法总结(PATH/LD_LIBRARY_PATH)

    http://blog.chinaunix.net/uid-354915-id-3568853.html PATH:  可执行程序的查找路径查看当前环境变量:echo $PATH设置: 方法一:exp ...

  10. 卧槽,redis分布式如果用不好,坑真多

    前言 在分布式系统中,由于redis分布式锁相对于更简单和高效,成为了分布式锁的首先,被我们用到了很多实际业务场景当中. 但不是说用了redis分布式锁,就可以高枕无忧了,如果没有用好或者用对,也会引 ...