授权服务器
Introspection Endpoint:校验 Access Token 的合法性
Spring Security OAuth2 架构
Authorization flow
Authorization Code Flow
Maven
1 2 3 4 5 6 7 8
| <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>
|
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
| @GetMapping("/api/user") public ResponseEntity<UserInfo> getUser() { User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return ResponseEntity.ok( UserInfo.builder() .name(user.getUsername()) .email(user.getUsername() + "@gmail.com") .build()); }
|
Authorization Server
Authorization Server + Client App
authorization_code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Configuration @EnableAuthorizationServer public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients .inMemory() .withClient("wechat") .secret("654321") .redirectUris("http://localhost:9000/callback") .authorizedGrantTypes("authorization_code") .scopes("read_userinfo", "read_contacts"); } }
|
Resource Server
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Configuration @EnableResourceServer public class ResourceServer extends ResourceServerConfigurerAdapter {
@Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest() .authenticated() .and() .requestMatchers() .antMatchers("/api/**"); } }
|
Resource owner
1 2 3 4 5 6
| server: port: 8000 security: user: name: zhongmingmao password: 123456
|
Flow
未授权
1 2 3 4 5
| $ curl -s --location --request GET 'http://localhost:8000/api/user' | jq { "error": "unauthorized", "error_description": "Full authentication is required to access this resource" }
|
获取授权码,response_type=code
浏览器:http://localhost:8000/oauth/authorize?client_id=wechat&redirect_uri=http://localhost:9000/callback&response_type=code&scope=read_userinfo
1 2
| $ echo -n 'zhongmingmao:123456' | base64 emhvbmdtaW5nbWFvOjEyMzQ1Ng==
|
1 2 3 4 5 6
| $ curl --location --request POST 'http://localhost:8000/oauth/authorize' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Authorization: Basic emhvbmdtaW5nbWFvOjEyMzQ1Ng==' \ --data-urlencode 'user_oauth_approval=true' \ --data-urlencode 'scope.read_userinfo=true' \ --data-urlencode 'authorize=Authorize'
|
兑换授权码
1 2
| $ echo -n 'wechat:654321' | base64 d2VjaGF0OjY1NDMyMQ==
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| $ curl -s --location --request POST 'http://localhost:8000/oauth/token' \ --header 'content-type: application/x-www-form-urlencoded' \ --header 'Authorization: Basic d2VjaGF0OjY1NDMyMQ==' \ --data-urlencode 'code=6qhVz7' \ --data-urlencode 'grant_type=authorization_code' \ --data-urlencode 'redirect_uri=http://localhost:9000/callback' \ --data-urlencode 'scope=read_userinfo' | jq { "access_token": "1aac6db3-c304-45c1-ab9d-9c7d63da842d", "token_type": "bearer", "expires_in": 43199, "scope": "read_userinfo" }
|
访问资源
1 2 3 4 5 6
| $ curl -s --location --request GET 'http://localhost:8000/api/user' \ --header 'authorization: Bearer 1aac6db3-c304-45c1-ab9d-9c7d63da842d' | jq { "name": "zhongmingmao", "email": "[email protected]" }
|
Implicit Grant Flow
相对于授权码模式,减少了授权码兑换的过程,直接获得 Access Token,非常不安全!
代码结构与授权码类似
Authorization Server
implicit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Configuration @EnableAuthorizationServer public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients .inMemory() .withClient("wechat") .secret("654321") .redirectUris("http://localhost:9000/callback") .authorizedGrantTypes("implicit") .accessTokenValiditySeconds(1 << 8) .scopes("read_userinfo", "read_contacts"); } }
|
Flow
直接获取 Access Token:response_type=token
浏览器:http://localhost:8000/oauth/authorize?client_id=wechat&redirect_uri=http://localhost:9000/callback&response_type=token&scope=read_userinfo
访问资源
1 2 3 4 5 6
| $ curl -s --location --request GET 'http://localhost:8000/api/user' \ --header 'authorization: Bearer a5cf3dc2-d3e6-4915-aee3-155e95d9764e' | jq { "name": "zhongmingmao", "email": "[email protected]" }
|
Resource Owner Password Credentials Flow
Authorization Server
password
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Configuration @EnableAuthorizationServer @AllArgsConstructor @FieldDefaults(level = AccessLevel.PRIVATE) public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
AuthenticationManager authenticationManager;
@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager); }
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients .inMemory() .withClient("wechat") .secret("654321") .authorizedGrantTypes("password") .scopes("read_userinfo", "read_contacts"); } }
|
Flow
1 2
| $ echo -n 'wechat:654321' | base64 d2VjaGF0OjY1NDMyMQ==
|
客户应用直接使用用户账密去授权服务器申请 Access Token
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| $ 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": "e2849e93-125d-444a-862e-acc2c3a0bad9", "token_type": "bearer", "expires_in": 43145, "scope": "read_userinfo" }
|
1 2 3 4 5 6
| $ curl -s --location --request GET 'http://localhost:8000/api/user' \ --header 'authorization: Bearer e2849e93-125d-444a-862e-acc2c3a0bad9' | jq { "name": "zhongmingmao", "email": "[email protected]" }
|
Client Credentials Flow
API
1 2 3 4 5 6 7
| @GetMapping("/api/users") public ResponseEntity<List<UserInfo>> getUsers() { List<UserInfo> users = new ArrayList<>(); users.add(UserInfo.builder().name("zhongmingmao").email("[email protected]").build()); users.add(UserInfo.builder().name("zhongmingwu").email("[email protected]").build()); return ResponseEntity.ok(users); }
|
Authorization Server
client_credentials
1 2 3 4 5 6
| clients .inMemory() .withClient("wechat") .secret("654321") .authorizedGrantTypes("client_credentials") .scopes("read_userinfo", "read_contacts");
|
Flow
1 2
| $ echo -n 'wechat:654321' | base64 d2VjaGF0OjY1NDMyMQ==
|
1 2 3 4 5 6 7 8 9 10 11
| $ curl -s --location --request POST 'http://localhost:8000/oauth/token' \ --header 'Authorization: Basic d2VjaGF0OjY1NDMyMQ==' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data-urlencode 'grant_type=client_credentials' \ --data-urlencode 'scope=read_userinfo' | jq { "access_token": "5954cfbc-5b1b-4699-9478-8233774c05c6", "token_type": "bearer", "expires_in": 43199, "scope": "read_userinfo" }
|
1 2 3 4 5 6 7 8 9 10 11 12
| $ curl -s --location --request GET 'http://localhost:8000/api/users' \ --header 'authorization: Bearer 5954cfbc-5b1b-4699-9478-8233774c05c6' | jq [ { "name": "zhongmingmao", "email": "[email protected]" }, { "name": "zhongmingwu", "email": "[email protected]" } ]
|
技术选型
参考
- 微服务架构实战 160 讲