Spring Boot Security OAuth2 實(shí)現(xiàn)支持JWT令牌的授權(quán)服務(wù)器

文章首發(fā)于公眾號(hào)《程序員果果》
地址:https://mp.weixin.qq.com/s/UvRfEF_WW1TGQqsuMxgoLg
本篇源碼:https://github.com/gf-huanchupk/SpringBootLearning

概要

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

Spring Boot Security 整合 OAuth2 設(shè)計(jì)安全API接口服務(wù)

Spring Boot Security 整合 JWT 實(shí)現(xiàn) 無(wú)狀態(tài)的分布式API接口

這一篇我們來(lái)實(shí)現(xiàn) 支持 JWT令牌 的授權(quán)服務(wù)器。

優(yōu)點(diǎn)

使用 OAuth2 是向認(rèn)證服務(wù)器申請(qǐng)令牌,客戶端拿這令牌訪問(wèn)資源服務(wù)服務(wù)器,資源服務(wù)器校驗(yàn)了令牌無(wú)誤后,如果資源的訪問(wèn)用到用戶的相關(guān)信息,那么資源服務(wù)器還需要根據(jù)令牌關(guān)聯(lián)查詢用戶的信息。

使用 JWT 是客戶端通過(guò)用戶名、密碼 請(qǐng)求服務(wù)器獲取 JWT,服務(wù)器判斷用戶名和密碼無(wú)誤之后,可以將用戶信息和權(quán)限信息經(jīng)過(guò)加密成 JWT 的形式返回給客戶端。在之后的請(qǐng)求中,客戶端攜帶 JWT 請(qǐng)求需要訪問(wèn)的資源,如果資源的訪問(wèn)用到用戶的相關(guān)信息,那么就直接從JWT中獲取到。

所以,如果我們?cè)谑褂?OAuth2 時(shí)結(jié)合JWT ,就能節(jié)省集中式令牌校驗(yàn)開(kāi)銷(xiāo),實(shí)現(xiàn)無(wú)狀態(tài)授權(quán)認(rèn)證。

快速上手

項(xiàng)目說(shuō)明

工程名 端口 作用
jwt-authserver 8080 授權(quán)服務(wù)器
jwt-resourceserver 8081 資源服務(wù)器

授權(quán)服務(wù)器

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);
            }
        };
    }

}

為了方便,使用內(nèi)存模式,在內(nèi)存中創(chuàng)建一個(gè)用戶 user 密碼 123456。

OAuth2AuthorizationServer

/**
 * 授權(quán)服務(wù)器
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {

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

    /**
     * 對(duì)Jwt簽名時(shí),增加一個(gè)密鑰
     * JwtAccessTokenConverter:對(duì)Jwt來(lái)進(jìn)行編碼以及解碼的類(lèi)
     */
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("test-secret");
        return converter;
    }

    /**
     * 設(shè)置token 由Jwt產(chǎn)生,不使用默認(rèn)的透明令牌
     */
    @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")
                //設(shè)置支持[密碼模式、授權(quán)碼模式、token刷新]
                .authorizedGrantTypes(
                        "password",
                        "authorization_code",
                        "refresh_token");
    }


}

資源服務(wù)器

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

/**
 * 資源服務(wù)器
 */
@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

參數(shù)說(shuō)明:

  • security.oauth2.resource.jwt.key-value:設(shè)置簽名key 保持和授權(quán)服務(wù)器一致。
  • security.oauth2.resource.jwt:項(xiàng)目啟動(dòng)過(guò)程中,檢查到配置文件中有
    security.oauth2.resource.jwt 的配置,就會(huì)生成 jwtTokenStore 的 bean,對(duì)令牌的校驗(yàn)就會(huì)使用 jwtTokenStore 。

驗(yàn)證

請(qǐng)求令牌

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令牌請(qǐng)求資源

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

返回

hi , zhangsan

源碼

https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-security-oauth2-jwt

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容