SpringBoot之oauth2.0學習之服務端配置快速上手

如今第三方登陸的例子數見不鮮。其實在這種示例當中,oauth2.0是使用比較多的一種受權登陸的標準。oauth2.0也是從oauth1.0升級過來的。那麼關於oauth2.0相關的概念及其原理,你們能夠參考這篇文章,這篇文章中會有更詳細的解釋,下來咱們直接進入正題。html

1.一、gradle依賴

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

在這裏我直接引入的是spring-cloud的依賴項,這種依賴的jar包更全面一些,這裏面的核心基礎仍是spring-security。這裏SpringBoot的版本爲2.0.6.REALEASEjava

1.二、@EnableAuthorizationServer

在這裏我着重強調一下這個註解:@EnableAuthorizationServer,這個註解源代碼以下:spring

@Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
    public @interface EnableAuthorizationServer {
    
    }

這個註解主要是導入兩個配置類,分別是:安全

  • AuthorizationServerEndpointsConfiguration,這個配置類主要配置受權端點,獲取token的端點。你們就把對應的端點想象成controller便可,在這個controller下開放了若干個@RequestMapping,好比常見的有:/oauth/authorize(受權路徑)/oauth/token(獲取token)
  • AuthorizationServerSecurityConfiguration,主要是作spring-security的安全配置,咱們能夠看一下相關代碼:
public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
         @Autowired
        private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
      
        @Autowired
        private ClientDetailsService clientDetailsService;
      
        @Autowired
        private AuthorizationServerEndpointsConfiguration endpoints;
      
        @Autowired
        public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception {
            for (AuthorizationServerConfigurer configurer : configurers) {
                configurer.configure(clientDetails);
            }
        }
      
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            // Over-riding to make sure this.disableLocalConfigureAuthenticationBldr = false
            // This will ensure that when this configurer builds the AuthenticationManager it will not attempt
            // to find another 'Global' AuthenticationManager in the ApplicationContext (if available),
            // and set that as the parent of this 'Local' AuthenticationManager.
            // This AuthenticationManager should only be wired up with an AuthenticationProvider
            // composed of the ClientDetailsService (wired in this configuration) for authenticating 'clients' only.
        }
      
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //....省略部分代碼
            String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token");
            String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key");
            String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token");
            if (!endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) {
                UserDetailsService userDetailsService = http.getSharedObject(UserDetailsService.class);
                endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService);
            }
            // @formatter:off
              //上述節點的請求須要受權驗證
            http
                .authorizeRequests()
                    .antMatchers(tokenEndpointPath).fullyAuthenticated()
                    .antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess())
                    .antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess())
              .and()
                .requestMatchers()
                    .antMatchers(tokenEndpointPath, tokenKeyPath, checkTokenPath)
              .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
            // @formatter:on
            http.setSharedObject(ClientDetailsService.class, clientDetailsService);
        }
      
        protected void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            for (AuthorizationServerConfigurer configurer : configurers) {
                configurer.configure(oauthServer);
            }
        }
      }

1.2.一、AuthorizationServerConfigurer

這個接口是認證受權配置的核心接口,不過既然是SpringBoot咱們就先來看看它怎麼幫咱們裝配的,咱們能夠在org.springframework.boot.autoconfigure.security.oauth2.authserver這個包下面找到對應配置的Bean:springboot

@Configuration
    @ConditionalOnClass(EnableAuthorizationServer.class)
    @ConditionalOnMissingBean(AuthorizationServerConfigurer.class)
    @ConditionalOnBean(AuthorizationServerEndpointsConfiguration.class)
    @EnableConfigurationProperties(AuthorizationServerProperties.class)
    public class OAuth2AuthorizationServerConfiguration
            extends AuthorizationServerConfigurerAdapter {
        //....
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            //默認基於內存建立ClientDetails
            ClientDetailsServiceBuilder<InMemoryClientDetailsServiceBuilder>.ClientBuilder builder = clients
                    .inMemory().withClient(this.details.getClientId());
            builder.secret(this.details.getClientSecret())
                    .resourceIds(this.details.getResourceIds().toArray(new String[0]))
                    .authorizedGrantTypes(
                            this.details.getAuthorizedGrantTypes().toArray(new String[0]))
                    .authorities(
                            AuthorityUtils.authorityListToSet(this.details.getAuthorities())
                                    .toArray(new String[0]))
                    .scopes(this.details.getScope().toArray(new String[0]));
    
            if (this.details.getAutoApproveScopes() != null) {
                builder.autoApprove(
                        this.details.getAutoApproveScopes().toArray(new String[0]));
            }
            if (this.details.getAccessTokenValiditySeconds() != null) {
                builder.accessTokenValiditySeconds(
                        this.details.getAccessTokenValiditySeconds());
            }
            if (this.details.getRefreshTokenValiditySeconds() != null) {
                builder.refreshTokenValiditySeconds(
                        this.details.getRefreshTokenValiditySeconds());
            }
            if (this.details.getRegisteredRedirectUri() != null) {
                builder.redirectUris(
                        this.details.getRegisteredRedirectUri().toArray(new String[0]));
            }
        }
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints)
                throws Exception {
            if (this.tokenConverter != null) {
                endpoints.accessTokenConverter(this.tokenConverter);
            }
            if (this.tokenStore != null) {
                endpoints.tokenStore(this.tokenStore);
            }
            if (this.details.getAuthorizedGrantTypes().contains("password")) {
                endpoints.authenticationManager(this.authenticationManager);
            }
        }
    
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security)
                throws Exception {
            security.passwordEncoder(NoOpPasswordEncoder.getInstance());
            if (this.properties.getCheckTokenAccess() != null) {
                security.checkTokenAccess(this.properties.getCheckTokenAccess());
            }
            if (this.properties.getTokenKeyAccess() != null) {
                security.tokenKeyAccess(this.properties.getTokenKeyAccess());
            }
            if (this.properties.getRealm() != null) {
                security.realm(this.properties.getRealm());
            }
        }
        
        
        
        @Configuration
        @ConditionalOnMissingBean(BaseClientDetails.class)
        protected static class BaseClientDetailsConfiguration {
    
            private final OAuth2ClientProperties client;
    
            protected BaseClientDetailsConfiguration(OAuth2ClientProperties client) {
                this.client = client;
            }
    
            /**
                由此可知它會尋找security.oauth2.client的配置
            */
            @Bean
            @ConfigurationProperties(prefix = "security.oauth2.client")
            public BaseClientDetails oauth2ClientDetails() {
                BaseClientDetails details = new BaseClientDetails();
                if (this.client.getClientId() == null) {
                    this.client.setClientId(UUID.randomUUID().toString());
                }
                details.setClientId(this.client.getClientId());
                details.setClientSecret(this.client.getClientSecret());
                details.setAuthorizedGrantTypes(Arrays.asList("authorization_code",
                        "password", "client_credentials", "implicit", "refresh_token"));
                details.setAuthorities(
                        AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
                details.setRegisteredRedirectUri(Collections.<String>emptySet());
                return details;
            }
    
        }
    
    
    }

若是沒有用spring-boot的用戶,能夠也能夠參考上述的配置方法,自行配置服務器

1.三、application.yml的配置

根據上述代碼咱們能夠知道,springboot經過外部化配置的security.oauth2.client的前綴來配置客戶端。那麼所以咱們不妨在外部化配置文件裏作以下配置:session

server:
      port: 8080
    security:
      oauth2:
        client:
          client-id: root
          client-secret: root
          scope:
            - email
            - username
            - face
    spring:
      security:
        user:
          name: root
          password: root
          roles: ADMIN

這裏先作最基本的配置,配置client-idclient-secretscope特別注意oauth2.0必定要先通過springsecurity的auth認證,所以須要在這裏配置一個內存用戶名與密碼爲root與rootapp

1.四、配置資源服務器

經過資源服務器來保護咱們指定的資源,必須在獲取受權認證的時候才能訪問。在SpringBoot當中,咱們能夠經過@EnableResourceServer註解來開啓此功能。該註解定義以下:dom

@Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(ResourceServerConfiguration.class)
    public @interface EnableResourceServer {
    
    }

咱們能夠看到這個註解導入了默認的資源配置信息:ResourceServerConfiguration,它的源代碼以下:ide

@Configuration
    public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements Ordered {
        //....
            @Override
        protected void configure(HttpSecurity http) throws Exception {
            ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer();
            ResourceServerTokenServices services = resolveTokenServices();
            if (services != null) {
                resources.tokenServices(services);
            }
            else {
                if (tokenStore != null) {
                    resources.tokenStore(tokenStore);
                }
                else if (endpoints != null) {
                    resources.tokenStore(endpoints.getEndpointsConfigurer().getTokenStore());
                }
            }
            if (eventPublisher != null) {
                resources.eventPublisher(eventPublisher);
            }
            //配置資源
            for (ResourceServerConfigurer configurer : configurers) {
                configurer.configure(resources);
            }
            // @formatter:off
            http.authenticationProvider(new AnonymousAuthenticationProvider("default"))
            // N.B. exceptionHandling is duplicated in resources.configure() so that
            // it works
            .exceptionHandling()
                    .accessDeniedHandler(resources.getAccessDeniedHandler()).and()
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    .csrf().disable();
            // @formatter:on
            http.apply(resources);
            if (endpoints != null) {
                // Assume we are in an Authorization Server
                http.requestMatcher(new NotOAuthRequestMatcher(endpoints.oauth2EndpointHandlerMapping()));
            }
            for (ResourceServerConfigurer configurer : configurers) {
                // Delegates can add authorizeRequests() here
                configurer.configure(http);
            }
            //若是沒有任何配置資源,則全部請求保護
            if (configurers.isEmpty()) {
                // Add anyRequest() last as a fall back. Spring Security would
                // replace an existing anyRequest() matcher with this one, so to
                // avoid that we only add it if the user hasn't configured anything.
                http.authorizeRequests().anyRequest().authenticated();
            }
        }
        //....
        
    }

在這裏主要是配置資源服務器的配置,咱們能夠獲得以下幾點信息:

  • 資源配置的核心ResourceServerConfigurer,在這裏若是沒有任何配置,則全部請求都要進行token認證
  • TokenStore 主要定義了對token的增刪改查操做,用於持久化token
  • ResourceServerTokenServices 資源服務的service(服務層),這裏主要仍是根據token來拿到OAuth2AuthenticationOAuth2AccessToken

1.五、完整示例

1.5.一、資源認證配置

@Configuration
    @EnableResourceServer
    public class ResourceConfigure extends ResourceServerConfigurerAdapter {
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                    .and().authorizeRequests().antMatchers("/free/**").permitAll().and()
                    .authorizeRequests().anyRequest().authenticated()
                    .and().formLogin().permitAll();//必須認證事後才能夠訪問
        }
    }

在這裏若是以/free/**請求路徑的,都容許直接訪問。不然,都必須攜帶access_token才能訪問。

1.5.2 、受權認證配置

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

根據上文所述,AuthorizationServerEndpointTokenEndpoint會開放/oauth/authorize與/oauth/token端點,所以咱們必須保證訪問端點進行受權認證前,經過springsecurity的用戶認證,所以在這裏配置了/oauth/*

1.5.三、啓動類

@SpringBootApplication
    @EnableAuthorizationServer
    @Controller
    public class AuthorizationServer {
        
       
       @GetMapping("/order")
        public ResponseEntity<String> order() {
            ResponseEntity<String> responseEntity = new ResponseEntity("order", HttpStatus.OK);
            return responseEntity;
        }
    
        @GetMapping("/free/test")
        public ResponseEntity<String> test() {
            ResponseEntity<String> responseEntity = new ResponseEntity("free", HttpStatus.OK);
            return responseEntity;
        }
        
         public static void main(String[] args) {
            SpringApplication.run(AuthorizationServer.class, args);
        }
    }

1.5.四、訪問請求

首先咱們經過postman 訪問http://localhost:8080/order會獲得以下界面:

此時咱們明顯能夠看到對應的資源須要攜帶有效的token才能夠訪問,那麼咱們此時要在postman的Authorization進行oauth2.0配置認證。截圖以下:

在這裏點擊Get New Access Token 來從認證服務器獲取token,點擊後配置以下:

  • scope配置對應application.yml中的配置信息,這裏面能夠放置用戶的屬性信息,好比說暱稱 頭像 電話等等
  • State表明狀態碼,設置一個State標誌
  • 回調地址這裏必須配置,經過這個地址當贊成受權後會返回一個認證的code給咱們,咱們根據這個code請求token
  • 認證地址與獲取token的地址請填寫,相關Endpoint生成的地址

當通過一連串認證後,咱們便可拿到token:


當咱們獲取到最新的token之後,咱們便可訪問到對應的請求資源:

相關文章
相關標籤/搜索