文章首發(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