一、背景

在前一节咱们学习了 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 */@EnableWebSecuritypublic 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个受爱护的资源和一个非受爱护的资源。

@RestControllerpublic 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