Spring Cloud 微服務中搭建 OAuth2.0 認證授權服務

来源:https://www.cnblogs.com/Irving/archive/2018/07/20/9343377.html
-Advertisement-
Play Games

在使用 Spring Cloud 體系來構建微服務的過程中,用戶請求是通過網關(ZUUL 或 Spring APIGateway)以 HTTP 協議來傳輸信息,API 網關將自己註冊為 Eureka 服務治理下的應用,同時也從 Eureka 服務中獲取所有其他微服務的實例信息。搭建 OAuth2 認... ...


149676948363514113

在使用 Spring Cloud 體系來構建微服務的過程中,用戶請求是通過網關(ZUUL 或 Spring APIGateway)以 HTTP 協議來傳輸信息,API 網關將自己註冊為 Eureka 服務治理下的應用,同時也從 Eureka 服務中獲取所有其他微服務的實例信息。搭建 OAuth2 認證授權服務,並不是給每個微服務調用,而是通過 API 網關進行統一調用來對網關後的微服務做前置過濾,所有的請求都必須先通過 API 網關,API 網關在進行路由轉發之前對該請求進行前置校驗,實現對微服務系統中的其他的服務介面的安全與許可權校驗。一般解決用戶認證與授權的方法,目前主流的解決方案有 OAuth2.0OIDC(OpenID Connect)HMACJWT 等。

備註關於 RESTFUL API 安全認證方式的一些總結

OAuth2.0 授權模式

OAuth2.0 協議根據使用不同的適用場景,定義了用於四種授權模式。

Authorization code(授權碼模式)
標準的 Server 授權模式,非常適合 Server 端的 Web 應用。一旦資源的擁有者授權訪問他們的數據之後,他們將會被重定向到 Web 應用併在 URL 的查詢參數中附帶一個授權碼(code)。在客戶端里,該 code 用於請求訪問令牌(access_token)。並且該令牌交換的過程是兩個服務端之前完成的,防止其他人甚至是資源擁有者本人得到該令牌。另外,在該授權模式下可以通過 refresh_token 來刷新令牌以延長訪問授權時間,也是最為複雜的一種方式。

Implicit Grant(隱式模式)
該模式是所有授權模式中最簡單的一種,併為運行於瀏覽器中的腳本應用做了優化。當用戶訪問該應用時,服務端會立即生成一個新的訪問令牌(access_token)並通過URL的#hash段傳回客戶端。這時,客戶端就可以利用JavaScript等將其取出然後請求API介面。該模式不需要授權碼(code),當然也不會提供refresh token以獲得長期訪問的入口。

Resource Owner Password Credentials(密碼模式)
自己有一套用戶體系,這種模式要求用戶提供用戶名和密碼來交換訪問令牌(access_token)。該模式僅用於非常值得信任的用戶,例如API提供者本人所寫的移動應用。雖然用戶也要求提供密碼,但並不需要存儲在設備上。因為初始驗證之後,只需將 OAuth 的令牌記錄下來即可。如果用戶希望取消授權,因為其真實密碼並沒有被記錄,因此無需修改密碼就可以立即取消授權。token本身也只是得到有限的授權,因此相比最傳統的 username/password 授權,該模式依然更為安全。

Client Credentials(客戶端模式)
沒有用戶的概念,一種基於 APP 的密鑰直接進行授權,因此 APP 的許可權非常大。它適合像資料庫或存儲伺服器這種對 API 的訪問需求。

備註:理解 OAuth 2.0

Spring Security OAuth2 框架

Spring Security OAuth2 是建立在 Spring Security 的基礎之上 OAuth2.0 協議實現的一個類庫,它提供了構建 Authorization Server、Resource Server 和 Client 三種 Spring 應用程式角色所需要的功能,能夠更好的集成到 Spring Cloud 體系中。

Keycloak 官方語言來解釋,“為現代應用系統和服務提供開源的鑒權和授權訪問控制管理”。Keycloak 實現了OpenID,Auth2.0,SAML單點登錄協議,同時提供LDAP和Active Directory,以及OpenID Connect, SAML2.0 IdPs,Github,Google 等第三方登錄適配功能,能夠做到非常簡單的開箱即用。

備註:從 4.1 版開始,Spring Boot starter 將基於 Spring Boot 2 adapter。如果您使用的是較舊的 Spring Boot 版本,則可以使用 keycloak-legacy-spring-boot-starter。

之前提到 Authorization Server、Resource Server 和 Client 之間的關係,下麵使用 Spring Security OAuth2 為 Spring Cloud 搭建認證授權服務

Authorization Server 

在 Authorization Server  的角色中 Spring Security OAuth2 定義了 AuthorizationServerConfigurerAdapter 配置類

public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
    public AuthorizationServerConfigurerAdapter() {
    }

    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    }

    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    }

    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    }
}
  • ClientDetailsServiceConfigurer:用來配置客戶端詳情信息,一般使用資料庫來存儲或讀取應用配置的詳情信息(client_id ,client_secret,redirect_uri 等配置信息)。
  • AuthorizationServerSecurityConfigurer:用來配置令牌端點(Token Endpoint)的安全與許可權訪問。
  • AuthorizationServerEndpointsConfigurer:用來配置授權以及令牌(Token)的訪問端點和令牌服務(比如:配置令牌的簽名與存儲方式)

Resource Server

在 Resource Server 的角色中 Spring Security OAuth2 定義了 ResourceServerConfigurerAdapter 配置類

public class ResourceServerConfigurerAdapter implements ResourceServerConfigurer {
    public ResourceServerConfigurerAdapter() {
    }

    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    }

    public void configure(HttpSecurity http) throws Exception {
        ((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated();
    }
}

ResourceServerConfigurerAdapter 用於保護 OAuth2 要開放的資源,同時主要作用於client端以及token的認證(Bearer Auth),由於後面 OAuth2 服務端後續還需要提供用戶信息,所以也是一個 Resource Server,預設攔截了所有的請求,也可以通過重新方法方式自定義自己想要攔截的資源 URL 地址。

另外根據 OAuth2.0 規範,獲取票據要支持 Basic 驗證與驗證用戶的賬戶信息,比如密碼模式:

     POST /token HTTP/1.1
     Host: server.example.com
     Authorization: Basic 1sZCaJks20MzpnMsPOi
     Content-Type: application/x-www-form-urlencoded
     grant_type=password&username=irving&password=123456

可以在 WebSecurityConfigurerAdapter 類中重新相應的方法來實現。

  • AuthorizationServerConfigurerAdapter
  • ResourceServerConfigurerAdapter
  • WebSecurityConfigurerAdapter

Client

根據 OAuth2.0 規範定義獲得票據需要提供 client_id 與 client_secret ,這個過程需要在服務端申請獲得,比我新浪與騰訊的聯合登錄就是採用的授權碼模式。一般還是要根據適用的場景給與不同的配置與作用域。

   /*
    * 配置客戶端詳情信息(記憶體或JDBC來實現)
    *
    * */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //初始化 Client 數據到 DB
        clients.jdbc(dataSource)
       // clients.inMemory()
                .withClient("client_1")
                .authorizedGrantTypes("client_credentials")
                .scopes("all","read", "write")
                .authorities("client_credentials")
                .accessTokenValiditySeconds(7200)
                .secret(passwordEncoder.encode("123456"))

                .and().withClient("client_2")
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("all","read", "write")
                .accessTokenValiditySeconds(7200)
                .refreshTokenValiditySeconds(10000)
                .authorities("password")
                .secret(passwordEncoder.encode("123456"))

                .and().withClient("client_3").authorities("authorization_code","refresh_token")
                .secret(passwordEncoder.encode("123456"))
                .authorizedGrantTypes("authorization_code")
                .scopes("all","read", "write")
                .accessTokenValiditySeconds(7200)
                .refreshTokenValiditySeconds(10000)
                .redirectUris("http://localhost:8080/callback","http://localhost:8080/signin")

                .and().withClient("client_test")
                .secret(passwordEncoder.encode("123456"))
                .authorizedGrantTypes("all flow")
                .authorizedGrantTypes("authorization_code", "client_credentials", "refresh_token","password", "implicit")
                .redirectUris("http://localhost:8080/callback","http://localhost:8080/signin")
                .scopes("all","read", "write")
                .accessTokenValiditySeconds(7200)
                .refreshTokenValiditySeconds(10000);

            //https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
           // clients.withClientDetails(new JdbcClientDetailsService(dataSource));
    }

理解上述說的關係後,就可以來實現 OAuth2.0 的相關服務了。

MAVEN

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <!--Spring Security 與 Security 的 OAuth2 擴展-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!-- 將 token 存儲在 redis 中 -->
        <!--<dependency>-->
        <!--<groupId>org.springframework.boot</groupId>-->
        <!--<artifactId>spring-boot-starter-data-redis</artifactId>-->
        <!--</dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
SpringApplication
@SpringCloudApplication //@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker
public class MicrosrvOauth2ServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(MicrosrvOauth2ServerApplication.class, args);
    }
}
/*
[/oauth/authorize]
[/oauth/token]
[/oauth/check_token]
[/oauth/confirm_access]
[/oauth/token_key]
[/oauth/error]
*/
@Configuration
@EnableAuthorizationServer
//@Order(2)
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

/*
    @Autowired
    private RedisConnectionFactory connectionFactory;

    @Bean
    public RedisTokenStore tokenStore() {
        return new RedisTokenStore(connectionFactory);
    }
 */

    @Autowired
    @Qualifier("dataSource")
    private DataSource dataSource;

//    @Bean(name = "dataSource")
//    @ConfigurationProperties(prefix = "spring.datasource")
//    public DataSource dataSource() {
//        return DataSourceBuilder.create().build();
//    }

    @Bean("jdbcTokenStore")
    public JdbcTokenStore getJdbcTokenStore() {
        return new JdbcTokenStore(dataSource);
    }

//    @Bean
//    public UserDetailsService userDetailsService(){
//        return new UserService();
//    }

    /*
    * 配置客戶端詳情信息(記憶體或JDBC來實現)
    *
    * */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //初始化 Client 數據到 DB
        clients.jdbc(dataSource)
       // clients.inMemory()
                .withClient("client_1")
                .authorizedGrantTypes("client_credentials")
                .scopes("all","read", "write")
                .authorities("client_credentials")
                .accessTokenValiditySeconds(7200)
                .secret(passwordEncoder.encode("123456"))

                .and().withClient("client_2")
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("all","read", "write")
                .accessTokenValiditySeconds(7200)
                .refreshTokenValiditySeconds(10000)
                .authorities("password")
                .secret(passwordEncoder.encode("123456"))

                .and().withClient("client_3").authorities("authorization_code","refresh_token")
                .secret(passwordEncoder.encode("123456"))
                .authorizedGrantTypes("authorization_code")
                .scopes("all","read", "write")
                .accessTokenValiditySeconds(7200)
                .refreshTokenValiditySeconds(10000)
                .redirectUris("http://localhost:8080/callback","http://localhost:8080/signin")

                .and().withClient("client_test")
                .secret(passwordEncoder.encode("123456"))
                .authorizedGrantTypes("all flow")
                .authorizedGrantTypes("authorization_code", "client_credentials", "refresh_token","password", "implicit")
                .redirectUris("http://localhost:8080/callback","http://localhost:8080/signin")
                .scopes("all","read", "write")
                .accessTokenValiditySeconds(7200)
                .refreshTokenValiditySeconds(10000);

            //https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
           // clients.withClientDetails(new JdbcClientDetailsService(dataSource));
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

//        endpoints
//                .tokenStore(new RedisTokenStore(redisConnectionFactory))
//                .authenticationManager(authenticationManager);

           endpoints.authenticationManager(authenticationManager)
                     //配置 JwtAccessToken 轉換器
                  //  .accessTokenConverter(jwtAccessTokenConverter())
                     //refresh_token 需要 UserDetailsService is required
                 //   .userDetailsService(userDetailsService)
                    .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                    .tokenStore(getJdbcTokenStore());
    }


    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
        //curl -i -X POST -H "Accept: application/json" -u "client_1:123456" http://localhost:5000/oauth/check_token?token=a1478d56-ebb8-4f21-b4b6-8a9602df24ec
        oauthServer.tokenKeyAccess("permitAll()")         //url:/oauth/token_key,exposes public key for token verification if using JWT tokens
                   .checkTokenAccess("isAuthenticated()") //url:/oauth/check_token allow check token
                   .allowFormAuthenticationForClients();
    }

    /**
     * 使用非對稱加密演算法來對Token進行簽名
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyPair keyPair = new KeyStoreKeyFactory(
                new ClassPathResource("keystore.jks"), "foobar".toCharArray())
                .getKeyPair("test");
        converter.setKeyPair(keyPair);
        return converter;
    }
}
/*
* 提供 user 信息,所以 oauth2-server 也是一個Resource Server
* */
@Configuration
@EnableResourceServer
//@Order(3)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter  {

//    @Override
//    public void configure(HttpSecurity http) throws Exception {
//        http
//                // Since we want the protected resources to be accessible in the UI as well we need
//                // session creation to be allowed (it's disabled by default in 2.0.6)
//                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
//                .and()
//                .requestMatchers().anyRequest()
//                .and()
//                .anonymous()
//                .and()
//                .authorizeRequests()
////              .antMatchers("/product/**").access("#oauth2.hasScope('select') and hasRole('ROLE_USER')")
//                .antMatchers("/user/**").authenticated();//必須認證過後才可以訪問
//    }


//    @Override
//    public void configure(HttpSecurity http) throws Exception {
//        http.requestMatchers().anyRequest()
//                .and()
//                .authorizeRequests()
//                .antMatchers("/api/**").authenticated();
//    }
}
@Configuration
@EnableWebSecurity
//@Order(1)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    public UserDetailsService userDetailsService(){
        return new UserService();
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("irving")
                .password(passwordEncoder().encode("123456"))
                .roles("read");
        // auth.userDetailsService(userDetailsService())
        //   .passwordEncoder(passwordEncoder());
    }

//    @Bean
//    public static NoOpPasswordEncoder passwordEncoder() {
//        return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
//    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        http
//                .formLogin().loginPage("/login").permitAll()
//                .and()
//                .requestMatchers()
//                .antMatchers("/", "/login", "/oauth/authorize", "/oauth/confirm_access")
//                .and()
//                .authorizeRequests()
//                .anyRequest().authenticated();


//        http.requestMatchers()
//                .antMatchers("/login", "/oauth/authorize")
//                .and()
//                .authorizeRequests()
//                .anyRequest().authenticated()
//                .and()
//                .formLogin().permitAll();

   //     http.csrf().disable();
        //不攔截 oauth 開放的資源
        http.requestMatchers()
                .anyRequest()
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/**").permitAll();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
@RestController
@RequestMapping("/api/user")
public class UserController {

    @GetMapping("/me")
    public Principal user(Principal principal) {
        return principal;
    }

    @GetMapping("/{name}")
    public String getUserName(@PathVariable String name) {
        return "hello,"+ name;
    }
}

application.yml

#logging:
#  level:
#    root: DEBUG
logging:
  level:
      org.springframework: INFO #INFO
      org.springframework.security: DEBUG
spring:
  application:
    name: microsrv-oauth2-server
  datasource:
    url: jdbc:mysql://XXX.XXX.XXX.XXX:3306/oauth2?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
    username: root
    password: "!TEST"
    driver: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
        minIdle: 10
        idle-timeout: 10000
        maximumPoolSize: 30
server:
  port: 5000
config:
    oauth2:
        # openssl genrsa -out jwt.pem 2048
        # openssl rsa -in jwt.pem
        privateKey: |
            -----BEGIN RSA PRIVATE KEY-----
            MIICXQIBAAKBgQDNQZKqTlO/+2b4ZdhqGJzGBDltb5PZmBz1ALN2YLvt341pH6i5
            mO1V9cX5Ty1LM70fKfnIoYUP4KCE33dPnC7LkUwE/myh1zM6m8cbL5cYFPyP099t
            hbVxzJkjHWqywvQih/qOOjliomKbM9pxG8Z1dB26hL9dSAZuA8xExjlPmQIDAQAB
            AoGAImnYGU3ApPOVtBf/TOqLfne+2SZX96eVU06myDY3zA4rO3DfbR7CzCLE6qPn
            yDAIiW0UQBs0oBDdWOnOqz5YaePZu/yrLyj6KM6Q2e9ywRDtDh3ywrSfGpjdSvvo
            aeL1WesBWsgWv1vFKKvES7ILFLUxKwyCRC2Lgh7aI9GGZfECQQD84m98Yrehhin3
            fZuRaBNIu348Ci7ZFZmrvyxAIxrV4jBjpACW0RM2BvF5oYM2gOJqIfBOVjmPwUro
            bYEFcHRvAkEAz8jsfmxsZVwh3Y/Y47BzhKIC5FLaads541jNjVWfrPirljyCy1n4
            sg3WQH2IEyap3WTP84+csCtsfNfyK7fQdwJBAJNRyobY74cupJYkW5OK4OkXKQQL
            Hp2iosJV/Y5jpQeC3JO/gARcSmfIBbbI66q9zKjtmpPYUXI4tc3PtUEY8QsCQQCc
            xySyC0sKe6bNzyC+Q8AVvkxiTKWiI5idEr8duhJd589H72Zc2wkMB+a2CEGo+Y5H
            jy5cvuph/pG/7Qw7sljnAkAy/feClt1mUEiAcWrHRwcQ71AoA0+21yC9VkqPNrn3
            w7OEg8gBqPjRlXBNb00QieNeGGSkXOoU6gFschR22Dzy
            -----END RSA PRIVATE KEY-----
        # openssl rsa -in jwt.pem -pubout
        publicKey: |
            -----BEGIN PUBLIC KEY-----
            MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNQZKqTlO/+2b4ZdhqGJzGBDlt
            b5PZmBz1ALN2YLvt341pH6i5mO1V9cX5Ty1LM70fKfnIoYUP4KCE33dPnC7LkUwE
            /myh1zM6m8cbL5cYFPyP099thbVxzJkjHWqywvQih/qOOjliomKbM9pxG8Z1dB26
            hL9dSAZuA8xExjlPmQIDAQAB
            -----END PUBLIC KEY-----
eureka:
  instance:
    preferIpAddress: true
#    instanceId: ${spring.cloud.client.ipAddress}:${server.port}
  client:
    serviceUrl:
      defaultZone: http://10.255.131.162:8000/eureka/,http://10.255.131.163:8000/eureka/,http://10.255.131.164:8000/eureka/

運行測試

客戶端模式

POST http://localhost:5000/oauth/token HTTP/1.1
Authorization: Basic Y2xpZW50XzE6MTIzNDU2
cache-control: no-cache
Postman-Token: 86fd25cd-406d-4db1-a67a-eda3cf760ba5
User-Agent: PostmanRuntime/7.1.1
Accept: */*
Host: localhost:5000
content-type: application/x-www-form-urlencoded
accept-encoding: gzip, deflate
content-length: 29
Connection: keep-alive
grant_type=client_credentials
HTTP/1.1 200
{"access_token":"a1478d56-ebb8-4f21-b4b6-8a9602df24ec","token_type":"bearer","expires_in":1014,"scope":"all read write"}

密碼模式

POST http://localhost:5000/oauth/token HTTP/1.1
Authorization: Basic Y2xpZW50X3Rlc3Q6MTIzNDU2
cache-control: no-cache
Postman-Token: f97aca16-e2ea-4dda-b51f-eb95caa57560
User-Agent: PostmanRuntime/7.1.1
Accept: */*
Host: localhost:5000
content-type: application/x-www-form-urlencoded
grant_type=password&scope=all&username=irving&password=123456
HTTP/1.1 200
{"access_token":"dfe36394-8592-472f-b52b-24739811f6ee","token_type":"bearer","refresh_token":"c150594f-7d00-44cc-bbce-49e1a6e83552","expires_in":7190,"scope":"all"}

獲取資源信息

GET http://localhost:5000/api/user/me?access_token=a1478d56-ebb8-4f21-b4b6-8a9602df24ec HTTP/1.1
Host: localhost:5000
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Date: Fri, 20 Jul 2018 09:21:32 GMT
Content-Length: 674
{"authorities":[{"authority":"client_credentials"}],"details":{"remoteAddress":"0:0:0:0:0:0:0:1","sessionId":null,"tokenValue":"a1478d56-ebb8-4f21-b4b6-8a9602df24ec","tokenType":"Bearer","decodedDetails":null},"authenticated":true,"userAuthentication":null,"credentials":"","oauth2Request":{"clientId":"client_1","scope":["all","read","write"],"requestParameters":{"grant_type":"client_credentials"},"resourceIds":[],"authorities":[{"authority":"client_credentials"}],"approved":true,"refresh":false,"redirectUri":null,"responseTypes":[],"extensions":{},"refreshTokenRequest":null,"grantType":"client_credentials"},"clientOnly":true,"principal":"client_1","name":"client_1"}

問題

There is no PasswordEncoder mapped for the id “null”問題

一般是老的項目升到 Spring Boot 2.0 依賴的是 Spring 5,相關的依賴都發生了較大的改動 Spring Security 5.0 New Features ,Spring Security 重構了 PasswordEncoder 相關的演算法 ,原先預設配置的 PlainTextPasswordEncoder(明文密碼)被移除了,替代的 BCryptPasswordEncoder ,Client 與 Resource Server 中設計密碼的相關都需要採用新的的編碼方式(上述代碼已採用)。

//相容老版本 明文存儲
@Bean
PasswordEncoder passwordEncoder(){
    return NoOpPasswordEncoder.getInstance();
}

@Bean
PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

method_not_allowed(Request method 'GET' not supported)  問題

可以配置,由於不是 OAuth2.0 規範定義的範疇,調試在密碼模式獲得票據的時候會報錯,不推薦。

@Configuration
public class OAuthSecurityConfig extends AuthorizationServerConfigurerAdapter {
...
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    ...
    endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);// add get method
    ...

    endpoints.tokenServices(tokenServices);
}
...
}

Token 存儲 DB 報錯問題

檢查資料庫 token 相關的欄位是否是二進位數據類型(預設是:token LONGVARBINARY),資料庫的腳本可以在 Spring Security OAuth2 官方的項目中找到:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

2018-07-19 22:31:29.574 DEBUG 20084 --- [nio-5000-exec-6] .s.s.o.p.c.ClientCredentialsTokenGranter : Getting access token for: client_1
2018-07-19 22:31:29.574 DEBUG 20084 --- [nio-5000-exec-6] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2018-07-19 22:31:29.574 DEBUG 20084 --- [nio-5000-exec-6] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [select token_id, token from oauth_access_token where authentication_id = ?]
2018-07-19 22:31:29.575 DEBUG 20084 --- [nio-5000-exec-6] o.s.jdbc.datasource.DataSourceUtils      : Fetching JDBC Connection from DataSource
2018-07-19 22:31:29.623 DEBUG 20084 --- [nio-5000-exec-6] o.s.jdbc.datasource.DataSourceUtils      : Returning JDBC Connecti

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這裡我用了兩個生產者和兩個消費者進行演示,如下圖(畫的不好看,湊活看看): 這裡我就只講下怎麼註冊到dashbord和相關的配置,提供者和消費者等代碼可以去下載查看: 1.hystrix的配置: 這裡我將熔斷器(或者稱為斷路器配置到了消費者端): 啟動類: pom.xml: StuConsumerA ...
  • 打開eclipse,在菜單欄上找到Window,點擊Window >Perspective >Customize Perspective...,會看到 彈出來的一個視窗,然後點擊最後一個Shortcuts,然後將Shortcut Categories下麵的所有選項都去掉,然後點擊 左邊的每一項,右邊 ...
  • 1、 集合的嵌套: 集合的用法其實和數組的用法有很多共同之處,在使用數組的時候,二維數組就是數組的嵌套; 那麼在集合之中是否也可以這樣呢? 當然也是可以的,例如對於最複雜的的map集合; map<string, map<string,student>>;這樣map中就嵌套了一個map集合; 其中對於 ...
  • “Spring”——每一個Javaer開發者都繞不開的字眼,從21世紀第一個十年國內異常活躍的SSH框架,到現在以Spring Boot作為入口粘合了各種應用。Spring現在已經完成了從web入口到微服務架構再到數據處理整個生態,看著現在https://spring.io/projects上長長的 ...
  • @Controller @Controller 註解用於標記在 Java 類上。被 @Controller 標記過的類就是一個 SpringMVC Controller對象。DispatcherServlet 會掃描使用了該註解的類的方法,並檢查對應方法是否有 @RequestMapping 註解標 ...
  • 編寫此文僅為以後可以複習。 最近在自學Java核心技術(很好的書,推薦!!),也是第一次從上面瞭解了goto,或許只是淺層瞭解。 錯誤之處希望大佬們給予批評與建議!!謝謝!!! Java核心技術中就提到過:無限制的使用goto語句確實是導致錯誤的根源,但是有些情況下,偶爾使用goto 跳出迴圈 還是 ...
  • 1、三層架構 表現層 web層(MVC是一個表現層的設計模型) 業務層 service層 持久層 dao層2、三大框架和三層架構的關係(建議學習三大框架的順序:先學習hibernate在學習struts2框架,最後學習spring 框架) hibernate框架:它是一個持久層框架 struts2框 ...
  • 前言 作為一名準備轉行數據分析的小白,我先接觸到的是網路爬蟲學習,每次爬蟲運行都有新的bug收穫,通過不斷debug,終於稍微能爬一些數據了,在此想和大家分享一下~ 私信小編007即可獲取小編精心準備的PDF十套哦! 看看最後一頁搜索結果 。 PS:小技巧,在頁面下部跳轉頁面輸入一個很大的數字,比如 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...