当前位置:   article > 正文

SpringBoot中oauth2.0学习之服务端配置快速上手

springboot resourceservertokenservices

现在第三方登录的例子数见不鲜。其实在这种示例当中,oauth2.0是使用比较多的一种授权登录的标准。oauth2.0也是从oauth1.0升级过来的。那么关于oauth2.0相关的概念及其原理,大家可以参考这篇文章,这篇文章中会有更详细的解释,下来我们直接进入正题。

1.1、gradle依赖

  1. compile('org.springframework.cloud:spring-cloud-starter-oauth2')
  2. compile('org.springframework.cloud:spring-cloud-starter-security')

在这里我直接引入的是spring-cloud的依赖项,这种依赖的jar包更全面一些,这里面的核心基础还是spring-security。这里SpringBoot的版本为2.0.6.REALEASE

1.2、@EnableAuthorizationServer

在这里我着重强调一下这个注解:@EnableAuthorizationServer,这个注解源代码如下:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
  5. public @interface EnableAuthorizationServer {
  6. }

这个注解主要是导入两个配置类,分别是:

  • AuthorizationServerEndpointsConfiguration,这个配置类主要配置授权端点,获取token的端点。大家就把对应的端点想象成controller即可,在这个controller下开放了若干个@RequestMapping,比如常见的有:/oauth/authorize(授权路径)/oauth/token(获取token)
  • AuthorizationServerSecurityConfiguration,主要是做spring-security的安全配置,我们可以看一下相关代码:
  1. public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
  2. @Autowired
  3. private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
  4. @Autowired
  5. private ClientDetailsService clientDetailsService;
  6. @Autowired
  7. private AuthorizationServerEndpointsConfiguration endpoints;
  8. @Autowired
  9. public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception {
  10. for (AuthorizationServerConfigurer configurer : configurers) {
  11. configurer.configure(clientDetails);
  12. }
  13. }
  14. @Override
  15. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  16. // Over-riding to make sure this.disableLocalConfigureAuthenticationBldr = false
  17. // This will ensure that when this configurer builds the AuthenticationManager it will not attempt
  18. // to find another 'Global' AuthenticationManager in the ApplicationContext (if available),
  19. // and set that as the parent of this 'Local' AuthenticationManager.
  20. // This AuthenticationManager should only be wired up with an AuthenticationProvider
  21. // composed of the ClientDetailsService (wired in this configuration) for authenticating 'clients' only.
  22. }
  23. @Override
  24. protected void configure(HttpSecurity http) throws Exception {
  25. //....省略部分代码
  26. String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token");
  27. String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key");
  28. String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token");
  29. if (!endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) {
  30. UserDetailsService userDetailsService = http.getSharedObject(UserDetailsService.class);
  31. endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService);
  32. }
  33. // @formatter:off
  34. //上述节点的请求需要授权验证
  35. http
  36. .authorizeRequests()
  37. .antMatchers(tokenEndpointPath).fullyAuthenticated()
  38. .antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess())
  39. .antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess())
  40. .and()
  41. .requestMatchers()
  42. .antMatchers(tokenEndpointPath, tokenKeyPath, checkTokenPath)
  43. .and()
  44. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
  45. // @formatter:on
  46. http.setSharedObject(ClientDetailsService.class, clientDetailsService);
  47. }
  48. protected void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
  49. for (AuthorizationServerConfigurer configurer : configurers) {
  50. configurer.configure(oauthServer);
  51. }
  52. }
  53. }

1.2.1、AuthorizationServerConfigurer

这个接口是认证授权配置的核心接口,不过既然是SpringBoot我们就先来看看它怎么帮我们装配的,我们可以在org.springframework.boot.autoconfigure.security.oauth2.authserver这个包下面找到对应配置的Bean:

  1. @Configuration
  2. @ConditionalOnClass(EnableAuthorizationServer.class)
  3. @ConditionalOnMissingBean(AuthorizationServerConfigurer.class)
  4. @ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class)
  5. @EnableConfigurationProperties(AuthorizationServerProperties.class)
  6. public class OAuth2AuthorizationServerConfiguration
  7. extends AuthorizationServerConfigurerAdapter {
  8. //....
  9. @Override
  10. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
  11. //默认基于内存创建ClientDetails
  12. ClientDetailsServiceBuilder<InMemoryClientDetailsServiceBuilder>.ClientBuilder builder = clients
  13. .inMemory().withClient(this.details.getClientId());
  14. builder.secret(this.details.getClientSecret())
  15. .resourceIds(this.details.getResourceIds().toArray(new String[0]))
  16. .authorizedGrantTypes(
  17. this.details.getAuthorizedGrantTypes().toArray(new String[0]))
  18. .authorities(
  19. AuthorityUtils.authorityListToSet(this.details.getAuthorities())
  20. .toArray(new String[0]))
  21. .scopes(this.details.getScope().toArray(new String[0]));
  22. if (this.details.getAutoApproveScopes() != null) {
  23. builder.autoApprove(
  24. this.details.getAutoApproveScopes().toArray(new String[0]));
  25. }
  26. if (this.details.getAccessTokenValiditySeconds() != null) {
  27. builder.accessTokenValiditySeconds(
  28. this.details.getAccessTokenValiditySeconds());
  29. }
  30. if (this.details.getRefreshTokenValiditySeconds() != null) {
  31. builder.refreshTokenValiditySeconds(
  32. this.details.getRefreshTokenValiditySeconds());
  33. }
  34. if (this.details.getRegisteredRedirectUri() != null) {
  35. builder.redirectUris(
  36. this.details.getRegisteredRedirectUri().toArray(new String[0]));
  37. }
  38. }
  39. @Override
  40. public void configure(AuthorizationServerEndpointsConfigurer endpoints)
  41. throws Exception {
  42. if (this.tokenConverter != null) {
  43. endpoints.accessTokenConverter(this.tokenConverter);
  44. }
  45. if (this.tokenStore != null) {
  46. endpoints.tokenStore(this.tokenStore);
  47. }
  48. if (this.details.getAuthorizedGrantTypes().contains("password")) {
  49. endpoints.authenticationManager(this.authenticationManager);
  50. }
  51. }
  52. @Override
  53. public void configure(AuthorizationServerSecurityConfigurer security)
  54. throws Exception {
  55. security.passwordEncoder(NoOpPasswordEncoder.getInstance());
  56. if (this.properties.getCheckTokenAccess() != null) {
  57. security.checkTokenAccess(this.properties.getCheckTokenAccess());
  58. }
  59. if (this.properties.getTokenKeyAccess() != null) {
  60. security.tokenKeyAccess(this.properties.getTokenKeyAccess());
  61. }
  62. if (this.properties.getRealm() != null) {
  63. security.realm(this.properties.getRealm());
  64. }
  65. }
  66. @Configuration
  67. @ConditionalOnMissingBean(BaseClientDetails.class)
  68. protected static class BaseClientDetailsConfiguration {
  69. private final OAuth2ClientProperties client;
  70. protected BaseClientDetailsConfiguration(OAuth2ClientProperties client) {
  71. this.client = client;
  72. }
  73. /**
  74. 由此可知它会寻找security.oauth2.client的配置
  75. */
  76. @Bean
  77. @ConfigurationProperties(prefix = "security.oauth2.client")
  78. public BaseClientDetails oauth2ClientDetails() {
  79. BaseClientDetails details = new BaseClientDetails();
  80. if (this.client.getClientId() == null) {
  81. this.client.setClientId(UUID.randomUUID().toString());
  82. }
  83. details.setClientId(this.client.getClientId());
  84. details.setClientSecret(this.client.getClientSecret());
  85. details.setAuthorizedGrantTypes(Arrays.asList("authorization_code",
  86. "password", "client_credentials", "implicit", "refresh_token"));
  87. details.setAuthorities(
  88. AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
  89. details.setRegisteredRedirectUri(Collections.<String>emptySet());
  90. return details;
  91. }
  92. }
  93. }

如果没有用spring-boot的用户,可以也可以参考上述的配置方法,自行配置

1.3、application.yml的配置

根据上述代码我们可以知道,springboot通过外部化配置的security.oauth2.client的前缀来配置客户端。那么因此我们不妨在外部化配置文件里做如下配置:

  1. server:
  2. port: 8080
  3. security:
  4. oauth2:
  5. client:
  6. client-id: root
  7. client-secret: root
  8. scope:
  9. - email
  10. - username
  11. - face
  12. spring:
  13. security:
  14. user:
  15. name: root
  16. password: root
  17. roles: ADMIN

这里先做最基本的配置,配置client-idclient-secretscope特别注意oauth2.0一定要先经过springsecurity的auth认证,因此需要在这里配置一个内存用户名与密码为root与root

1.4、配置资源服务器

通过资源服务器来保护我们指定的资源,必须在获取授权认证的时候才能访问。在SpringBoot当中,我们可以通过@EnableResourceServer注解来开启此功能。该注解定义如下:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Import(ResourceServerConfiguration.class)
  5. public @interface EnableResourceServer {
  6. }

我们可以看到这个注解导入了默认的资源配置信息:ResourceServerConfiguration,它的源代码如下:

  1. @Configuration
  2. public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements Ordered {
  3. //....
  4. @Override
  5. protected void configure(HttpSecurity http) throws Exception {
  6. ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer();
  7. ResourceServerTokenServices services = resolveTokenServices();
  8. if (services != null) {
  9. resources.tokenServices(services);
  10. }
  11. else {
  12. if (tokenStore != null) {
  13. resources.tokenStore(tokenStore);
  14. }
  15. else if (endpoints != null) {
  16. resources.tokenStore(endpoints.getEndpointsConfigurer().getTokenStore());
  17. }
  18. }
  19. if (eventPublisher != null) {
  20. resources.eventPublisher(eventPublisher);
  21. }
  22. //配置资源
  23. for (ResourceServerConfigurer configurer : configurers) {
  24. configurer.configure(resources);
  25. }
  26. // @formatter:off
  27. http.authenticationProvider(new AnonymousAuthenticationProvider("default"))
  28. // N.B. exceptionHandling is duplicated in resources.configure() so that
  29. // it works
  30. .exceptionHandling()
  31. .accessDeniedHandler(resources.getAccessDeniedHandler()).and()
  32. .sessionManagement()
  33. .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
  34. .csrf().disable();
  35. // @formatter:on
  36. http.apply(resources);
  37. if (endpoints != null) {
  38. // Assume we are in an Authorization Server
  39. http.requestMatcher(new NotOAuthRequestMatcher(endpoints.oauth2EndpointHandlerMapping()));
  40. }
  41. for (ResourceServerConfigurer configurer : configurers) {
  42. // Delegates can add authorizeRequests() here
  43. configurer.configure(http);
  44. }
  45. //如果没有任何配置资源,则所有请求保护
  46. if (configurers.isEmpty()) {
  47. // Add anyRequest() last as a fall back. Spring Security would
  48. // replace an existing anyRequest() matcher with this one, so to
  49. // avoid that we only add it if the user hasn't configured anything.
  50. http.authorizeRequests().anyRequest().authenticated();
  51. }
  52. }
  53. //....
  54. }

在这里主要是配置资源服务器的配置,我们可以得到如下几点信息:

  • 资源配置的核心ResourceServerConfigurer,在这里如果没有任何配置,则所有请求都要进行token认证
  • TokenStore 主要定义了对token的增删改查操作,用于持久化token
  • ResourceServerTokenServices 资源服务的service(服务层),这里主要还是根据token来拿到OAuth2AuthenticationOAuth2AccessToken

1.5、完整示例

1.5.1、资源认证配置

  1. @Configuration
  2. @EnableResourceServer
  3. public class ResourceConfigure extends ResourceServerConfigurerAdapter {
  4. @Override
  5. public void configure(HttpSecurity http) throws Exception {
  6. http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
  7. .and().authorizeRequests().antMatchers("/free/**").permitAll().and()
  8. .authorizeRequests().anyRequest().authenticated()
  9. .and().formLogin().permitAll();//必须认证过后才可以访问
  10. }
  11. }

在这里如果以/free/**请求路径的,都允许直接访问。否则,都必须携带access_token才能访问。

1.5.2 、授权认证配置

  1. @Configuration
  2. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. http.csrf().disable().requestMatchers().anyRequest().and().authorizeRequests()
  6. .antMatchers("/oauth/*").authenticated().and().formLogin().permitAll();
  7. }
  8. }

根据上文所述,AuthorizationServerEndpointTokenEndpoint会开放/oauth/authorize与/oauth/token端点,因此我们必须保证访问端点进行授权认证前,通过springsecurity的用户认证,因此在这里配置了/oauth/*

1.5.3、启动类

  1. @SpringBootApplication
  2. @EnableAuthorizationServer
  3. @Controller
  4. public class AuthorizationServer {
  5. @GetMapping("/order")
  6. public ResponseEntity<String> order() {
  7. ResponseEntity<String> responseEntity = new ResponseEntity("order", HttpStatus.OK);
  8. return responseEntity;
  9. }
  10. @GetMapping("/free/test")
  11. public ResponseEntity<String> test() {
  12. ResponseEntity<String> responseEntity = new ResponseEntity("free", HttpStatus.OK);
  13. return responseEntity;
  14. }
  15. public static void main(String[] args) {
  16. SpringApplication.run(AuthorizationServer.class, args);
  17. }
  18. }

1.5.4、访问请求

首先我们通过postman 访问http://localhost:8080/order会得到如下界面:

CC882A39_C3A8_4A51_9394_9CF812496EE9

此时我们明显可以看到对应的资源需要携带有效的token才可以访问,那么我们此时要在postman的Authorization进行oauth2.0配置认证。截图如下:

AD395591_2F6F_4D58_8D8B_9CFB9F36E69D

在这里点击Get New Access Token 来从认证服务器获取token,点击后配置如下:

`08CC20D3_B858_44F6_9A9B_3E1876AD0C8A

  • scope配置对应application.yml中的配置信息,这里面可以放置用户的属性信息,比如说昵称 头像 电话等等
  • State代表状态码,设置一个State标志
  • 回调地址这里必须配置,通过这个地址当同意授权后会返回一个认证的code给我们,我们根据这个code请求token
  • 认证地址与获取token的地址请填写,相关Endpoint生成的地址

当经过一连串认证后,我们即可拿到token:

61F4FB07_0C2E_4FF8_AB8F_CCE53F46699D
3FE37240_F36E_4CC6_935A_267CA6740C34

当我们获取到最新的token以后,我们即可访问到对应的请求资源:

16C498EC_D665_4ECA_B926_89DEFCE5E532

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/765881
推荐阅读
相关标签
  

闽ICP备14008679号