02-OAuth2实战

新建springcloud-web和springcloud-oauth和springcloud-resource三个工程
springcloud-web是前端工程
springcloud-oauth是授权服务器
springcloud-resource是资源服务器
启动顺序,先启动oauth,再启动resource,再启动web
demo地址

如果选用JdbcClientDetailsService,我们需要建表oauth_client_details

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(255) NOT NULL,
  `resource_ids` varchar(255) DEFAULT NULL,
  `client_secret` varchar(255) DEFAULT NULL,
  `scope` varchar(255) DEFAULT NULL,
  `authorized_grant_types` varchar(255) DEFAULT NULL,
  `web_server_redirect_uri` varchar(255) DEFAULT NULL,
  `authorities` varchar(255) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` text,
  `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `archived` tinyint(1) DEFAULT '0',
  `trusted` tinyint(1) DEFAULT '0',
  `autoapprove` varchar(255) DEFAULT 'false',
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `create_time`, `archived`, `trusted`, `autoapprove`) VALUES ('app', NULL, '{bcrypt}$2a$10$VU8mjPDb5xEdlhB391Y1nOUB7Vn7V42sa3vDO7rhddAxVFF/c9MGS', 'app', 'authorization_code,refresh_token', 'http://127.0.0.1:8080/callback.html', NULL, NULL, NULL, NULL, '2020-04-24 11:36:14', 0, 0, 'true');

为了省事这里先采用内存存储用户信息和客户端信息。

oauth配置见示例代码

启动oauth工程时debug进入.formLogin() -> HttpSecurity#formLogin

public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
    return getOrApply(new FormLoginConfigurer<>());
}

public FormLoginConfigurer() {
    super(new UsernamePasswordAuthenticationFilter(), null);
    usernameParameter("username");
    passwordParameter("password");
}

public UsernamePasswordAuthenticationFilter() {
    super(new AntPathRequestMatcher("/login", "POST"));
}

启动完oauth工程后,用postman调http://127.0.0.1:8080/oauth/token_key 进行debug

截屏2021-01-27上午12.47.06.png

流程:
BasicAuthenticationFilter--->
ProviderManager#authenticate--->
DaoAuthenticationProvider--->
AbstractUserDetailsAuthenticationProvider.createSuccessAuthentication--->
TokenKeyEndpoint


resource工程配置

security:
  oauth2:
    resource:
      jwt:
        key-uri: http://127.0.0.1:8080/oauth/token_key   
      token-info-uri: http://127.0.0.1:9900/api-uaa/oauth/check_token
    client:
      client-id: app
      client-secret: 123

查看ResourceServerTokenServicesConfiguration代码,我们配置了security.oauth2.resource.jwt.key-uri就会加载JwtTokenServicesConfiguration配置,如果没有配置的keyValue话,resource工程在启动时会调用getKeyFromServer()方法会请求oauth授权系统的/oauth/token_key获取到keyValue

@Configuration
@ConditionalOnMissingBean({AuthorizationServerEndpointsConfiguration.class})
public class ResourceServerTokenServicesConfiguration {

  private static class JwtTokenCondition extends SpringBootCondition {
        private JwtTokenCondition() {
        }

        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Builder message = ConditionMessage.forCondition("OAuth JWT Condition", new Object[0]);
            Environment environment = context.getEnvironment();
            String keyValue = environment.getProperty("security.oauth2.resource.jwt.key-value");
            String keyUri = environment.getProperty("security.oauth2.resource.jwt.key-uri");
            return !StringUtils.hasText(keyValue) && !StringUtils.hasText(keyUri) ? ConditionOutcome.noMatch(message.didNotFind("provided public key").atAll()) : ConditionOutcome.match(message.foundExactly("provided public key"));
        }
    }

    @Configuration
    @Conditional({ResourceServerTokenServicesConfiguration.JwtTokenCondition.class})
    protected static class JwtTokenServicesConfiguration {
        private final ResourceServerProperties resource;
        private final List<JwtAccessTokenConverterConfigurer> configurers;
        private final List<JwtAccessTokenConverterRestTemplateCustomizer> customizers;

        public JwtTokenServicesConfiguration(ResourceServerProperties resource, ObjectProvider<List<JwtAccessTokenConverterConfigurer>> configurers, ObjectProvider<List<JwtAccessTokenConverterRestTemplateCustomizer>> customizers) {
            this.resource = resource;
            this.configurers = (List)configurers.getIfAvailable();
            this.customizers = (List)customizers.getIfAvailable();
        }

        @Bean
        @ConditionalOnMissingBean({ResourceServerTokenServices.class})
        public DefaultTokenServices jwtTokenServices(TokenStore jwtTokenStore) {
            DefaultTokenServices services = new DefaultTokenServices();
            services.setTokenStore(jwtTokenStore);
            return services;
        }

        @Bean
        @ConditionalOnMissingBean({TokenStore.class})
        public TokenStore jwtTokenStore() {
            return new JwtTokenStore(this.jwtTokenEnhancer());
        }

        @Bean
        @ConditionalOnMissingBean({JwtAccessTokenConverter.class})
        public JwtAccessTokenConverter jwtTokenEnhancer() {
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            String keyValue = this.resource.getJwt().getKeyValue();
            if (!StringUtils.hasText(keyValue)) {
                keyValue = this.getKeyFromServer();//从uaa获取keyValue
            }

            if (StringUtils.hasText(keyValue) && !keyValue.startsWith("-----BEGIN")) {
                converter.setSigningKey(keyValue);
            }

            if (keyValue != null) {
                converter.setVerifierKey(keyValue);
            }

            if (!CollectionUtils.isEmpty(this.configurers)) {
                AnnotationAwareOrderComparator.sort(this.configurers);
                Iterator var3 = this.configurers.iterator();

                while(var3.hasNext()) {
                    JwtAccessTokenConverterConfigurer configurer = (JwtAccessTokenConverterConfigurer)var3.next();
                    configurer.configure(converter);
                }
            }

            return converter;
        }

        private String getKeyFromServer() {
            RestTemplate keyUriRestTemplate = new RestTemplate();
            if (!CollectionUtils.isEmpty(this.customizers)) {
                Iterator var2 = this.customizers.iterator();

                while(var2.hasNext()) {
                    JwtAccessTokenConverterRestTemplateCustomizer customizer = (JwtAccessTokenConverterRestTemplateCustomizer)var2.next();
                    customizer.customize(keyUriRestTemplate);
                }
            }

            HttpHeaders headers = new HttpHeaders();
            String username = this.resource.getClientId();
            String password = this.resource.getClientSecret();
            if (username != null && password != null) {//配置文件里配置的client-id和client-secret
                byte[] token = Base64.getEncoder().encode((username + ":" + password).getBytes());
                headers.add("Authorization", "Basic " + new String(token));
            }

            HttpEntity<Void> request = new HttpEntity(headers);
            String url = this.resource.getJwt().getKeyUri();
            return (String)((Map)keyUriRestTemplate.exchange(url, HttpMethod.GET, request, Map.class, new Object[0]).getBody()).get("value");
        }
    }
}

请求uaa的/oauth/token_key会被org.springframework.security.web.FilterChainProxy过滤器过滤,VirtualFilterChain这里面记录了原有的originalChain,额外添加了additionalFilters,过滤完additionalFilters后,再继续执行原来的originalChain。

断点打在OncePerRequestFilter类的doFilter方法那,经过CharacterEncodingFilter、WebMvcMetricsFilter、HiddenHttpMethodFilter、FormContentFilter、等过滤器过滤。
重点在BasicAuthenticationFilter类调用
this.authenticationManager.authenticate(authRequest);校验请求合法性,然后进入ProviderManager#authenticate(Authentication authentication)方法,然后进入DaoAuthenticationProvider#retrieveUser()方法
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);这里从数据库中获取配置的client信息,校验完数据库里的client信息后,再经过一系列过滤器过滤后,进入TokenKeyEndpoint#getKey方法

TokenKeyEndpoint代码
@RequestMapping(value = "/oauth/token_key", method = RequestMethod.GET)
@ResponseBody
public Map<String, String> getKey(Principal principal) {
      if ((principal == null || principal instanceof AnonymousAuthenticationToken) && !converter.isPublic()) {
            throw new AccessDeniedException("You need to authenticate to see a shared key");
      }
      Map<String, String> result = converter.getKey();
      return result;
}

web配置

在springcloud-web工程下添加index.html文件。

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta charset="utf-8"/>
    <title>zlt</title>
    <script type="text/javascript" src="js/jquery-3.2.1.min.js"></script>
</head>
<body>
<div>
    <p>用户名:<span id="userName"></span></p>
    <p>应用id:<span id="clientId"></span></p>
    <p>token:<span id="accessToken"></span></p>
    <p><input type="button" value="登出" onclick="logout()"/></p>
</div>
<script>
    //应用id
    let clientId = 'app';

    window.onload = function () {
        let accessToken = sessionStorage.getItem('access_token');
        if (accessToken) {//已登录
            let username = sessionStorage.getItem('username');
            $('#accessToken').html(accessToken);
            $('#userName').html(username);
            $('#clientId').html(clientId);
        } else {//未登录
            sessionStorage.setItem("visitUri", window.location.href);
            //redirect_uri 可以写后端某个地址,后端拿到 code 后去或取token, 然后再重定向到前端首页。
            window.location = 'http://127.0.0.1:9900/api-uaa/oauth/authorize?client_id=' + clientId + '&response_type=code&redirect_uri=http://127.0.0.1:8080/callback.html';
        }
    };

    function logout() {
        let accessToken = sessionStorage.getItem('access_token');
        sessionStorage.removeItem('access_token');
        sessionStorage.removeItem('username');
        window.location = uaaUri + 'remove/token?redirect_uri=http://127.0.0.1:8080/index.html&access_token=' + accessToken;
    }
</script>
</body>
</html>

当我们想要访问springcloud-web前端界面时,发现未登录,此时会向springcloud-uaa工程请求code。
uaa工程debug跟踪FilterChainProxy -> ExceptionTranslationFilter -> FilterSecurityInterceptor 抛出异常 -> ExceptionTranslationFilter#sendStartAuthentication -> LoginUrlAuthenticationEntryPoint#commence -> DefaultRedirectStrategy#sendRedirect,重定向到http://127.0.0.1:9900/api-uaa/login,未自定义登录界面的话会进入DefaultLoginPageGeneratingFilter#generateLoginPageHtml,response.getWriter().write(loginPageHtml);这里生成并跳转到登录页面。
如果我们自定义登录界面,配置.loginPage("/login.html"),

输入账号密码点击登录 -> UsernamePasswordAuthenticationFilter#attemptAuthentication -> ProviderManager#authenticate -> DaoAuthenticationProvider#authenticate
登录校验通过进入AuthorizationEndpoint#authorize生成code,并重定向到springcloud-web的http://127.0.0.1:8080/callback.html,callback.html获取到code,调springcloud-web的/token/{code}接口,这个接口再去请求uaa的/api-uaa/oauth/token接口,经过BasicAuthenticationFilter校验clientId和clientSecret,然后SecurityContextHolder.getContext().setAuthentication(authResult);
然后进入TokenEndpoint#getAccessToken返回OAuth2AccessToken。
springcloud-web获取到accessToken后返回前端界面,前端界面存储accessToken,并进入首页。

后续springcloud-web携带accessToken请求resource的http://127.0.0.1:8081/users/current?access_token=79156972-3878-4063-b49e-1a43b83d1688

Check Token Endpoint
如果我们需要一个 JWT 可以被回收,由于 JWT 自身的完整性,那么唯一的方法就是每次想查看 Access Token 持有的信息的时候,不是直接解密 Access Token 。而是使用 Authorization Server 提供的Check Token Endpoint。

resource获取到access_token后会请求uaa的http://127.0.0.1:9900/api-uaa/oauth/check_token进行token校验

用RemoteTokenServices替代DefaultTokenServices
在 Spring Security OAuth2 的 RemoteTokenServices 类中可以看到 active 信息的判断,只要 active 不是 true,则抛出 InvalidTokenException 异常。


项目搭建

AuthorizationCodeServices有两个实现类,InMemoryAuthorizationCodeServices和JdbcAuthorizationCodeServices,他俩有个公共的抽象父类RandomValueAuthorizationCodeServices,InMemoryAuthorizationCodeServices不能在多节点下使用,JdbcAuthorizationCodeServices需要经常插入和删除数据库,所以我们自定义RedisAuthorizationCodeServices继承RandomValueAuthorizationCodeServices

@Slf4j
@Service
@AllArgsConstructor
public class RedisAuthorizationCodeServices extends RandomValueAuthorizationCodeServices {

    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 替换JdbcAuthorizationCodeServices的存储策略
     * 将存储code到redis,并设置过期时间,10分钟
     */
    @Override
    protected void store(String code, OAuth2Authentication authentication) {
        redisTemplate.opsForValue().set(redisKey(code), authentication, 10, TimeUnit.MINUTES);
    }

    @Override
    protected OAuth2Authentication remove(String code) {
        String codeKey = redisKey(code);
        OAuth2Authentication token = (OAuth2Authentication) redisTemplate.opsForValue().get(codeKey);
        this.redisTemplate.delete(codeKey);
        return token;
    }

    /**
     * redis中 code key的前缀
     */
    private String redisKey(String code) {
        return "oauth:code:" + code;
    }
}

TokenStore选JwtTokenStore,通过看源码可以知道JwtTokenStore不需要存储OAuth2AccessToken

我们可以通过实现TokenGranter接口来自定义生成OAuth2AccessToken,具体可以把源码下载下来看,也可以参照我下面给出的代码地址。

1. 添加依赖

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

2. 添加注解和配置

在启动类中添加@EnableAuthorizationServer注解:

@SpringBootApplication
@EnableAuthorizationServer
public class AlanOAuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(AlanOAuthApplication.class, args);
    }
}

完成这些我们的授权服务最基本的骨架就已经搭建完成了。但是要想跑通整个流程,我们必须分配 client_id, client_secret才行。Spring Security OAuth2的配置方法是编写@Configuration类继承AuthorizationServerConfigurerAdapter,然后重写
void configure(ClientDetailsServiceConfigurer clients)方法

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory() // 使用in-memory存储
                .withClient("client") // client_id
                .secret("secret") // client_secret
                .authorizedGrantTypes("authorization_code") // 该client允许的授权类型
                .scopes("app"); // 允许的授权范围
}

3. 授权流程

访问授权页面:

localhost:8080/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com

此时浏览器会让你输入用户名密码,这是因为 Spring Security 在默认情况下会对所有URL添加Basic Auth认证。默认的用户名为user, 密码是随机生成的,在控制台日志中可以看到。

屏幕快照 2020-01-21 下午4.30.24.png

选择Approve点击Authorize后,浏览器就会重定向到百度,并带上code参数:

屏幕快照 2020-01-21 下午4.31.16.png

拿到code以后,就可以调用http://client:secret@localhost:8080/oauth/token来换取access_token

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=Li4NZo&redirect_uri=http://www.baidu.com' "http://client:secret@localhost:8080/oauth/token"

返回如下:

{
  "access_token": "32a1ca28-bc7a-4147-88a1-c95abcc30556", // 令牌
  "token_type": "bearer",
  "expires_in": 2591999,
  "scope": "app"
}

到此我们最最基本的授权服务就搭建完成了。然而,这仅仅是个demo,如果要在生产环境中使用,还需要做更多的工作。

4. 使用MySQL存储client信息,redis存储access_token

在上面的例子中,所有的token信息都是保存在内存中的,这显然无法在生产环境中使用(进程结束后所有token丢失, 用户需要重新授权),因此我们需要将这些信息进行持久化操作。
把授权服务器中的数据存储到数据库中并不难,因为 Spring Cloud Security OAuth 已经为我们设计好了一套Schema和对应的DAO对象。但在使用之前,我们需要先对相关的类有一定的了解。

4.1 相关接口

Spring Cloud Security OAuth2通过DefaultTokenServices类来完成token生成、过期等 OAuth2 标准规定的业务逻辑,而DefaultTokenServices又是通过TokenStore接口完成对生成数据的持久化。TokenStore的默认实现为InMemoryTokenStore,即内存存储。

ClientDetailsService接口负责从存储仓库中读取Client信息,他有两个实现类InMemoryClientDetailsServiceJdbcClientDetailsService,默认使用的是InMemoryClientDetialsService实现类,我们选用JdbcClientDetailsService实现类。

TokenStore有五个实现类,我们选RedisTokenStore
InMemoryTokenStore,JdbcTokenStore,JwkTokenStore,JwtTokenStore,RedisTokenStore

4.2 建表

要想使用这些JDBC实现,首先要建表。框架为我们提前设计好了schema, 在github上:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
在使用这套表结构之前要注意的是,对于MySQL来说,默认建表语句中主键是varchar(255)类型,在mysql中执行会报错,原因是mysql对varchar主键长度有限制。所以这里改成128即可。其次,语句中会有某些字段为LONGVARBINARY类型,它对应mysql的blob类型,也需要修改一下。


OAuth2 协议中 Resource Server 是要保护的对象,也就是说当 Resource Server 上的某个资源被请求时,Resource Server 要有能力去验证 Access Token 拥有需要的权限(scope)

要查看 Access Token 持有的权限,有两种办法

  • 一种是利用加密算法(对称加密和非对称加密均可),Authorization Server 使用Private Key 加密 Access Token,在 Resource Server 使用 Public Key 解密 Access Token,即可查看到其中的权限(Scope)。
  • 第二种方法是使用 OAuth2 中规定 Authorization Server 提供的两个 Endpoint,Check Token 和 UserInfo Endpoint。

查看 ResourceServerTokenServices 接口及其实现。在 Spring Cloud OAuth2 中 ,ResourceServerTokenServices 接口有 DefaultTokenServices、SpringSocialTokenServices、RemoteTokenServices 和 UserInfoTokenServices 四个实现。

  • DefaultTokenServices 对应非对称加密的方式
  • RemoteTokenServices 对应 Check Token 的方式,调用配置的 Check Token Endpoint URI(checkTokenEndpointUrl),解析返回的信息。
  • UserInfoTokenServices 对应 UserInfo 的方式,调用配置的 UserInfo Endpoint URI(userInfoEndpointUrl),解析返回的信息。

RemoteTokenCondition提供了TokenInfo,UserInfo两种方式
JwtTokenCondition,JwKCondition,JwtKeyStoreCondition

一、利用加密算法

Jwk

这种方式只需要配置让 Spring Security OAuth2 知道用来解密的 Public Key 即可。

security: 
  oauth2:
    resource:
      jwk:
        key-set-uri: http://localhost:9999/oauth/token_keys

如上这是最简单的 Resource Server 的配置方式,Spring Security OAuth2 通过 key-set-uri 配置的 URI 地址提供的 Public Key ,构建 JwkVerifyingJwtAccessTokenConverter 对象,用于将 Access Token 解密为明文 JWT Claims。
这种使用方式是最简单的,同时也是 JWT 最开始的初衷,Authorization Server 发放 JWT ,但是鉴权过程由 Resource Server 自己完成,大大减轻了 Authorization Server 的压力。这种方式的唯一缺陷就是无法验证 JWT 是否被 Revoke。

Jwt

通上边的方式是一个原理,只不过配置公钥的方式有变化。可以使用如下的方式配置一个URL地址,其返回的 JSON 只要含有 value 字段,值为对应的公钥即可。

security: 
  oauth2:
    resource:
      jwt:
        key-uri: http://localhost:9999/auth/oauth/token_key

当然也可以使用如下的方式直接配置公钥的值。

security: 
  oauth2:
    resource:
      jwt:
        key-value: |
          -----BEGIN CERTIFICATE-----
          MIIDSTCCArKgAwIBAgIBADANBgkqhkiG9w0BAQQFADB8MQswCQYDVQQGEwJhdzEO
          MAwGA1UECBMFYXJ1YmExDjAMBgNVBAoTBWFydWJhMQ4wDAYDVQQHEwVhcnViYTEO
          MAwGA1UECxMFYXJ1YmExDjAMBgNVBAMTBWFydWJhMR0wGwYJKoZIhvcNAQkBFg5h
          cnViYUBhcnViYS5hcjAeFw0xNTExMjAyMjI2MjdaFw0xNjExMTkyMjI2MjdaMHwx
          CzAJBgNVBAYTAmF3MQ4wDAYDVQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAM
          BgNVBAcTBWFydWJhMQ4wDAYDVQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAb
          BgkqhkiG9w0BCQEWDmFydWJhQGFydWJhLmFyMIGfMA0GCSqGSIb3DQEBAQUAA4GN
          ADCBiQKBgQDHtC5gUXxBKpEqZTLkNvFwNGnNIkggNOwOQVNbpO0WVHIivig5L39W
          qS9u0hnA+O7MCA/KlrAR4bXaeVVhwfUPYBKIpaaTWFQR5cTR1UFZJL/OF9vAfpOw
          znoD66DDCnQVpbCjtDYWX+x6imxn8HCYxhMol6ZnTbSsFW6VZjFMjQIDAQABo4Ha
          MIHXMB0GA1UdDgQWBBTx0lDzjH/iOBnOSQaSEWQLx1syGDCBpwYDVR0jBIGfMIGc
          gBTx0lDzjH/iOBnOSQaSEWQLx1syGKGBgKR+MHwxCzAJBgNVBAYTAmF3MQ4wDAYD
          VQQIEwVhcnViYTEOMAwGA1UEChMFYXJ1YmExDjAMBgNVBAcTBWFydWJhMQ4wDAYD
          VQQLEwVhcnViYTEOMAwGA1UEAxMFYXJ1YmExHTAbBgkqhkiG9w0BCQEWDmFydWJh
          QGFydWJhLmFyggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAYvBJ
          0HOZbbHClXmGUjGs+GS+xC1FO/am2suCSYqNB9dyMXfOWiJ1+TLJk+o/YZt8vuxC
          KdcZYgl4l/L6PxJ982SRhc83ZW2dkAZI4M0/Ud3oePe84k8jm3A7EvH5wi5hvCkK
          RpuRBwn3Ei+jCRouxTbzKPsuCVB+1sNyxMTXzf0=
          -----END CERTIFICATE-----

Jwt KeyStore

如果你的密钥对存储到了一个 keystore 中,也可以将此 keystore 直接配置给Spring Cloud OAuth2。由于 keystroe 是有用户名和密码的,keystore 也就是安全的,虽然此时的 keystore 中同时存储了公钥和私钥,也是没有问题的。

security: 
 oauth2:
   resource:
     jwt:
       key-store: test.jks
       key-store-password: test 
       key-alias: test
       key-password: test

关于 Resource Server 的配置, Spring Cloud OAuth2 提供了以上五种配置方式;分为两大类,本地解密和远程调用解析。本地解密有三种配置公钥的方式,远程调用解析可以调用 Authorization Server 提供的两个标准接口;
上面的配置,是为了构建 ResourceServerTokenServices 这个ResourceServer端的核心类。

添加配置类

@Configuration
@EnableAuthorizationServer  //开启授权服务
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private ClientDetailsService clientDetailsService;
    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //数据库的方式
        clients.withClientDetails(clientDetailsService);
        //在内存中配置,这种方式不够灵活,学习倒是没有问题
        //配置两个客户端,一个用于password认证一个用于client认证
        // clients.inMemory()
        //         .withClient("client_1")
        //         .resourceIds("rid")
        //         .authorizedGrantTypes("client_credentials", "refresh_token")
        //         .accessTokenValiditySeconds(1800)
        //         .scopes("select")
        //         .authorities("client")
        //         .secret("$2a$10$VU8mjPDb5xEdlhB391Y1nOUB7Vn7V42sa3vDO7rhddAxVFF/c9MGS")
        //         .and()
        //         .withClient("client_2")
        //         .resourceIds("rid")
        //         .authorizedGrantTypes("password", "refresh_token")
        //         .accessTokenValiditySeconds(1800)
        //         .scopes("select")
        //         .authorities("client")
        //         .secret("$2a$10$VU8mjPDb5xEdlhB391Y1nOUB7Vn7V42sa3vDO7rhddAxVFF/c9MGS");
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容