如今第三方登陸的例子數見不鮮。其實在這種示例當中,oauth2.0是使用比較多的一種受權登陸的標準。oauth2.0也是從oauth1.0升級過來的。那麼關於oauth2.0相關的概念及其原理,你們能夠參考這篇文章,這篇文章中會有更詳細的解釋,下來咱們直接進入正題。html
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
在這裏我着重強調一下這個註解:@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); } } }
這個接口是認證受權配置的核心接口,不過既然是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的用戶,能夠也能夠參考上述的配置方法,自行配置服務器
根據上述代碼咱們能夠知道,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-id
,client-secret
,scope
。特別注意oauth2.0必定要先通過springsecurity的auth認證,所以須要在這裏配置一個內存用戶名與密碼爲root與rootapp
經過資源服務器來保護咱們指定的資源,必須在獲取受權認證的時候才能訪問。在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的增刪改查操做,用於持久化tokenResourceServerTokenServices
資源服務的service(服務層),這裏主要仍是根據token來拿到OAuth2Authentication
與OAuth2AccessToken
@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
才能訪問。
@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(); } }
根據上文所述,AuthorizationServerEndpoint
與TokenEndpoint
會開放/oauth/authorize與/oauth/token
端點,所以咱們必須保證訪問端點進行受權認證前,經過springsecurity
的用戶認證,所以在這裏配置了/oauth/*
@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); } }
首先咱們經過postman 訪問http://localhost:8080/order
會獲得以下界面:
此時咱們明顯能夠看到對應的資源須要攜帶有效的token才能夠訪問,那麼咱們此時要在postman的Authorization進行oauth2.0配置認證。截圖以下:
在這裏點擊Get New Access Token 來從認證服務器獲取token,點擊後配置以下:
scope
配置對應application.yml中的配置信息,這裏面能夠放置用戶的屬性信息,好比說暱稱 頭像 電話等等State
表明狀態碼,設置一個State標誌當通過一連串認證後,咱們便可拿到token:
當咱們獲取到最新的token之後,咱們便可訪問到對應的請求資源: