Loading

SpringSecurityOauth2系列学习(三):资源服务

系列导航

SpringSecurity系列

SpringSecurityOauth2系列

资源服务

代码参考

资源服务器就比较简单了,因为认证和授权已经在授权服务器做了,所以我们这里只需要完成鉴权就行了。

这里我们实现一个简单的服务就行,主要是演示一下调取接口

引入依赖和配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.3</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>resource-server</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

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

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

        <!-- Spring Security OAuth2 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
    </dependencies>

</project>

主要需要spring-boot-starter-securityspring-boot-starter-oauth2-resource-server

配置项目:application.yml

server:
  port: 8090
  servlet:
    context-path: /${spring.application.name}

spring:
  application:
    #服务名称
    name: resource_server
  servlet:
    multipart:
      max-file-size: 1024MB
      max-request-size: 1024MB
  security:
    oauth2:
      resourceserver:
        jwt:
          # 配置jwk set
          jwk-set-uri: http://localhost:8080/authorization_server/.well-known/jwks.json

这里将jwk set 设定进去,接下来只需要在安全配置中配置好使用jwt验证token,SpringSecurityOauth2就会使用非对称的方式解析token的签名,会自动从这个接口中读取公钥,不需要我们去实现验签逻辑

如果这个配置错误,可能会出现token是正确的,并且也有权限访问,但是最后会报401错误的情况,资源服务器无法获取公钥,不能正确解密验签!

安全配置

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author 硝酸铜
 * @date 2021/9/23
 */

@Configuration
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .formLogin(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                .logout(AbstractHttpConfigurer::disable)
                .sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeRequests(req -> req
                        .antMatchers("/auth/**").permitAll()
                        .anyRequest().authenticated()
                )
                //oauth2使用jwt模式,会走接口拿取公钥解密验签
                .oauth2ResourceServer(resourceServer -> resourceServer.jwt()
                //读取权限默认读取scopes,这里配置一个转换器,使其也读取Authorities
                .jwtAuthenticationConverter(customJwtAuthenticationTokenConverter())
        );
    }

    private Converter<Jwt, AbstractAuthenticationToken> customJwtAuthenticationTokenConverter() {
        return jwt -> {
            List<String> userAuthorities = jwt.getClaimAsStringList("authorities");
            List<String> scopes = jwt.getClaimAsStringList("scope");
            List<GrantedAuthority> combinedAuthorities = Stream.concat(
                    userAuthorities.stream(),
                    //加上 SCOPE_前缀
                    scopes.stream().map(scope -> "SCOPE_" + scope))
                    .map(SimpleGrantedAuthority::new)
                    .collect(Collectors.toList());
            String username = jwt.getClaimAsString("user_name");
            return new UsernamePasswordAuthenticationToken(username, null, combinedAuthorities);
        };
    }

    @Override
    public void configure(WebSecurity web) {
        web
                .ignoring()
                .antMatchers("/error",
                        "/resources/**",
                        "/static/**",
                        "/public/**",
                        "/h2-console/**",
                        "/swagger-ui.html",
                        "/swagger-ui/**",
                        "/v3/api-docs/**",
                        "/v2/api-docs/**",
                        "/doc.html",
                        "/swagger-resources/**")
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }
}

customJwtAuthenticationTokenConverter()这个方法主要是将claims中的scopes和authorities放在一起,鉴权的时候即判断scopes + authorities 有没有需要的权限

定义接口测试

在安全配置类里面启用了方法级注解,这里写个简单的接口,方便后面调取

@RestController
@RequestMapping(value = "/test")
public class TestController {


    @PreAuthorize("hasAnyAuthority('api:hello')")
    @GetMapping(value = "/hello")
    public String hello(){
        return "hello world!";
    }
}

启动授权服务和资源服务

直接进行访问,401错误

登陆后,在Headers里面加上Authorization,token为从授权服务器中获取的token,可以调取成功

posted @ 2021-09-27 17:00  硝酸铜  阅读(699)  评论(1编辑  收藏  举报