Spring Boot Security OAuth2 實現支援JWT令牌的授權伺服器

程式設計師果果發表於2019-05-31

概要

之前的兩篇文章,講述了Spring Security 結合 OAuth2 、JWT 的使用,這一節要求對 OAuth2、JWT 有了解,若不清楚,先移步到下面兩篇提前瞭解下。

Spring Boot Security 整合 OAuth2 設計安全API介面服務

Spring Boot Security 整合 JWT 實現 無狀態的分散式API介面

這一篇我們來實現 支援 JWT令牌 的授權伺服器。

優點

使用 OAuth2 是向認證伺服器申請令牌,客戶端拿這令牌訪問資源服務伺服器,資源伺服器校驗了令牌無誤後,如果資源的訪問用到使用者的相關資訊,那麼資源伺服器還需要根據令牌關聯查詢使用者的資訊。

使用 JWT 是客戶端通過使用者名稱、密碼 請求伺服器獲取 JWT,伺服器判斷使用者名稱和密碼無誤之後,可以將使用者資訊和許可權資訊經過加密成 JWT 的形式返回給客戶端。在之後的請求中,客戶端攜帶 JWT 請求需要訪問的資源,如果資源的訪問用到使用者的相關資訊,那麼就直接從JWT中獲取到。

所以,如果我們在使用 OAuth2 時結合JWT ,就能節省集中式令牌校驗開銷,實現無狀態授權認證。

快速上手

專案說明

工程名 作用
jwt-authserver 8080 授權伺服器
jwt-resourceserver 8081 資源伺服器

授權伺服器

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.10.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
複製程式碼

WebSecurityConfig

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.
             authorizeRequests().antMatchers("/**").permitAll();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
            .withUser("user").password("123456").roles("USER");
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return Objects.equals(charSequence.toString(),s);
            }
        };
    }

}
複製程式碼

為了方便,使用記憶體模式,在記憶體中建立一個使用者 user 密碼 123456。

OAuth2AuthorizationServer

/**
 * 授權伺服器
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {

    /**
     * 注入AuthenticationManager ,密碼模式用到
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 對Jwt簽名時,增加一個金鑰
     * JwtAccessTokenConverter:對Jwt來進行編碼以及解碼的類
     */
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("test-secret");
        return converter;
    }

    /**
     * 設定token 由Jwt產生,不使用預設的透明令牌
     */
    @Bean
    public JwtTokenStore jwtTokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)
                .tokenStore(jwtTokenStore())
                .accessTokenConverter(accessTokenConverter());
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("clientapp")
                .secret("123")
                .scopes("read")
                //設定支援[密碼模式、授權碼模式、token重新整理]
                .authorizedGrantTypes(
                        "password",
                        "authorization_code",
                        "refresh_token");
    }


}
複製程式碼

資源伺服器

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.10.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
複製程式碼

HelloController

@RestController("/api")
public class HelloController {

    @PostMapping("/api/hi")
    public String say(String name) {
        return "hi , " + name;
    }

}
複製程式碼

OAuth2ResourceServer

/**
 * 資源伺服器
 */
@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .anyRequest().authenticated().and()
            .requestMatchers().antMatchers("/api/**");
    }
}
複製程式碼

application.yml

server:
  port: 8081

security:
  oauth2:
    resource:
      jwt:
        key-value: test-secret
複製程式碼

引數說明:

  • security.oauth2.resource.jwt.key-value:設定簽名key 保持和授權伺服器一致。
  • security.oauth2.resource.jwt:專案啟動過程中,檢查到配置檔案中有 security.oauth2.resource.jwt 的配置,就會生成 jwtTokenStore 的 bean,對令牌的校驗就會使用 jwtTokenStore 。

驗證

請求令牌

curl -X POST --user 'clientapp:123' -d 'grant_type=password&username=user&password=123456' http://localhost:8080/oauth/token
複製程式碼

返回JWT令牌

{
	"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTQ0MzExMDgsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiOGM0YWMyOTYtMDQwYS00Y2UzLTg5MTAtMWJmNjZkYTQwOTk3IiwiY2xpZW50X2lkIjoiY2xpZW50YXBwIiwic2NvcGUiOlsicmVhZCJdfQ.YAaSRN0iftmlR6Khz9UxNNEpHHn8zhZwlQrCUCPUmsU",
	"token_type": "bearer",
	"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ1c2VyIiwic2NvcGUiOlsicmVhZCJdLCJhdGkiOiI4YzRhYzI5Ni0wNDBhLTRjZTMtODkxMC0xYmY2NmRhNDA5OTciLCJleHAiOjE1NTY5Nzk5MDgsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiI0ZjA5M2ZjYS04NmM0LTQxZWUtODcxZS1kZTY2ZjFhOTI0NTAiLCJjbGllbnRfaWQiOiJjbGllbnRhcHAifQ.vvAE2LcqggBv8pxuqU6RKPX65bl7Zl9dfcoIbIQBLf4",
	"expires_in": 43199,
	"scope": "read",
	"jti": "8c4ac296-040a-4ce3-8910-1bf66da40997"
}
複製程式碼

攜帶JWT令牌請求資源

curl -X POST -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTQ0MzExMDgsInVzZXJfbmFtZSI6InVzZXIiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiOGM0YWMyOTYtMDQwYS00Y2UzLTg5MTAtMWJmNjZkYTQwOTk3IiwiY2xpZW50X2lkIjoiY2xpZW50YXBwIiwic2NvcGUiOlsicmVhZCJdfQ.YAaSRN0iftmlR6Khz9UxNNEpHHn8zhZwlQrCUCPUmsU" -d 'name=zhangsan' http://localhost:8081/api/hi
複製程式碼

返回

hi , zhangsan
複製程式碼

原始碼

github.com/gf-huanchup…

Spring Boot Security OAuth2 實現支援JWT令牌的授權伺服器

相關文章