Token Type
JWT
资源服务器自解释和自校验,无需再跟授权服务器交互
结构
验签
issue + audience
issue:令牌签发人
audience:目标接收人
Code
Authorization Server
Maven
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> </dependency> </dependencies>
|
application.yml
1 2 3 4 5 6
| server: port: 8000 security: user: name: zhongmingmao password: 123456
|
Config
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| @Configuration @EnableAuthorizationServer public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired private AuthenticationManager authenticationManager;
@Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("macos-secret"); return converter; }
@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("wechat") .secret("654321") .scopes("read_userinfo", "read_contacts") .authorizedGrantTypes("authorization_code", "password", "refresh_token"); } }
|
Resource Server
Maven
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> </dependency>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
|
application.yml
对称加密,密钥与授权服务器一致
1 2 3 4 5 6 7
| server: port: 9000 security: oauth2: resource: jwt: key-value: macos-secret
|
API
1 2 3 4 5 6 7
| @Data @Builder @FieldDefaults(level = AccessLevel.PRIVATE) public class UserInfo { String name; String email; }
|
1 2 3 4 5 6 7 8 9 10 11
| @Controller public class UserController {
@GetMapping("/api/userinfo") public ResponseEntity<UserInfo> getUserInfo() { String username = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return ResponseEntity.ok( UserInfo.builder().name(username).email(username + "@gmail.com").build()); } }
|
Config
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Configuration @EnableResourceServer public class OAuth2Config extends ResourceServerConfigurerAdapter {
@Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest() .authenticated() .and() .requestMatchers() .antMatchers("/api/**"); } }
|
Flow
Authorization Server
1 2
| $ echo -n 'wechat:654321' | base64 d2VjaGF0OjY1NDMyMQ==
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| $ curl -s --location --request POST 'http://localhost:8000/oauth/token' \ --header 'accept: application/json' \ --header 'content-type: application/x-www-form-urlencoded' \ --header 'Authorization: Basic d2VjaGF0OjY1NDMyMQ==' \ --data-urlencode 'grant_type=password' \ --data-urlencode 'username=zhongmingmao' \ --data-urlencode 'password=123456' \ --data-urlencode 'scope=read_userinfo' | jq { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2Njg0ODI2ODMsInVzZXJfbmFtZSI6Inpob25nbWluZ21hbyIsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiI2ZTQ4ZmZjNy1kODViLTRkN2ItOTY2MC00YjU3ZWJhN2M0YzciLCJjbGllbnRfaWQiOiJ3ZWNoYXQiLCJzY29wZSI6WyJyZWFkX3VzZXJpbmZvIl19.oM5xeY8aVauChmVXubu7KNXS1VT1MK3e-IadLAuy4CM", "token_type": "bearer", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ6aG9uZ21pbmdtYW8iLCJzY29wZSI6WyJyZWFkX3VzZXJpbmZvIl0sImF0aSI6IjZlNDhmZmM3LWQ4NWItNGQ3Yi05NjYwLTRiNTdlYmE3YzRjNyIsImV4cCI6MTY3MTAzMTQ4MywiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6IjRjOTAzYmU0LWU5NTMtNDM3OC04YTg3LTAyYzU3MzdlMjA4MSIsImNsaWVudF9pZCI6IndlY2hhdCJ9.-nB67OKfaucimBAVOwGGnS2dGj8yGGefxbPLwfimfvI", "expires_in": 43199, "scope": "read_userinfo", "jti": "6e48ffc7-d85b-4d7b-9660-4b57eba7c4c7" }
|
Resource Server
解析 JWT
1 2 3 4 5 6
| $ curl -s --location --request GET 'http://localhost:9000/api/userinfo' \ --header 'authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2Njg0ODI2ODMsInVzZXJfbmFtZSI6Inpob25nbWluZ21hbyIsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiJdLCJqdGkiOiI2ZTQ4ZmZjNy1kODViLTRkN2ItOTY2MC00YjU3ZWJhN2M0YzciLCJjbGllbnRfaWQiOiJ3ZWNoYXQiLCJzY29wZSI6WyJyZWFkX3VzZXJpbmZvIl19.oM5xeY8aVauChmVXubu7KNXS1VT1MK3e-IadLAuy4CM' | jq { "name": "zhongmingmao", "email": "[email protected]" }
|
Reference
- 微服务架构实战 160 讲