在Spring Cloud
須要使用OAUTH2
來實現多個微服務的統一認證受權,經過向OAUTH服務
發送某個類型的grant type
進行集中認證和受權,從而得到access_token
,而這個token是受其餘微服務信任的,咱們在後續的訪問能夠經過access_token
來進行,從而實現了微服務的統一認證受權。android
本示例提供了四大部分:git
eureka
:服務註冊和發現的基本模塊auth
:OAUTH2認證受權中心account
:普通微服務,用來驗證認證和受權gateway
:邊界網關(全部微服務都在它以後)OAUTH2中的角色:github
Resource Server
:被受權訪問的資源Authotization Server
:OAUTH2認證受權中心Resource Owner
: 用戶Client
:使用API的客戶端(如Android 、IOS、web app)Grant Type:web
authorization_code
:implicit
:password
:refrsh_token
:使用Postgres
做爲帳戶存儲,Redis
做爲Token
存儲,使用docker-compose
在服務器上啓動Postgres
和Redis
。redis
Redis:
image: sameersbn/redis:latest
ports:
- "6379:6379"
volumes:
- /srv/docker/redis:/var/lib/redis:Z
restart: always
PostgreSQL:
restart: always
image: sameersbn/postgresql:9.6-2
ports:
- "5432:5432"
environment:
- DEBUG=false
- DB_USER=wang
- DB_PASS=yunfei
- DB_NAME=order
volumes:
- /srv/docker/postgresql:/var/lib/postgresql:Z
Redis
用來存儲token
,服務重啓後,無需從新獲取token
.spring
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private RedisConnectionFactory connectionFactory; @Bean public RedisTokenStore tokenStore() { return new RedisTokenStore(connectionFactory); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) .userDetailsService(userDetailsService)//若無,refresh_token會有UserDetailsService is required錯誤 .tokenStore(tokenStore()); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()"); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("android") .scopes("xx") //此處的scopes是無用的,能夠隨意設置 .secret("android") .authorizedGrantTypes("password", "authorization_code", "refresh_token") .and() .withClient("webapp") .scopes("xx") .authorizedGrantTypes("implicit"); } }
auth-server
提供user信息,因此auth-server
也是一個Resource Server
sql
@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http .csrf().disable() .exceptionHandling() .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED)) .and() .authorizeRequests() .anyRequest().authenticated() .and() .httpBasic(); } }
@RestController public class UserController { @GetMapping("/user") public Principal user(Principal user){ return user; } }
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public UserDetailsService userDetailsService(){ return new DomainUserDetailsService(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .userDetailsService(userDetailsService()) .passwordEncoder(passwordEncoder()); } @Bean public SecurityEvaluationContextExtension securityEvaluationContextExtension() { return new SecurityEvaluationContextExtension(); } //不定義沒有password grant_type @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
採用用戶(SysUser)
<->角色(SysRole)
<->權限(SysAuthotity)
設置,彼此之間的關係是多對多
。經過DomainUserDetailsService
加載用戶和權限。docker
spring:
profiles:
active: ${SPRING_PROFILES_ACTIVE:dev}
application:
name: auth-server
jpa:
open-in-view: true
database: POSTGRESQL
show-sql: true
hibernate:
ddl-auto: update
datasource:
platform: postgres
url: jdbc:postgresql://192.168.1.140:5432/auth
username: wang
password: yunfei
driver-class-name: org.postgresql.Driver
redis:
host: 192.168.1.140
server:
port: 9999
eureka:
client:
serviceUrl:
defaultZone: http://${eureka.host:localhost}:${eureka.port:8761}/eureka/
logging.level.org.springframework.security: DEBUG
logging.leve.org.springframework: DEBUG
data.sql
裏初始化了兩個用戶admin
->ROLE_ADMIN
->query_demo
,wyf
->ROLE_USER
api
@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter{ @Override public void configure(HttpSecurity http) throws Exception { http .csrf().disable() .exceptionHandling() .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED)) .and() .authorizeRequests() .anyRequest().authenticated() .and() .httpBasic(); } }
account是一個簡單的微服務,使用auth-server
進行認證受權,在它的配置文件指定用戶信息在auth-server
的地址便可:安全
security: oauth2: resource: id: order-service user-info-uri: http://localhost:8080/uaa/user prefer-token-info: false
具有authority
未query-demo
的才能訪問,即爲admin
用戶
@RestController public class DemoController { @GetMapping("/demo") @PreAuthorize("hasAuthority('query-demo')") public String getDemo(){ return "good"; } }
api-gateway
在本例中有2個做用:
自己做爲一個client,使用implicit
做爲外部app訪問的方向代理
@Configuration @EnableOAuth2Sso public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); } }
zuul: routes: uaa: path: /uaa/** sensitiveHeaders: serviceId: auth-server order: path: /order/** sensitiveHeaders: serviceId: order-service add-proxy-headers: true security: oauth2: client: access-token-uri: http://localhost:8080/uaa/oauth/token user-authorization-uri: http://localhost:8080/uaa/oauth/authorize client-id: webapp resource: user-info-uri: http://localhost:8080/uaa/user prefer-token-info: false
feign client沒有將access token放入請求頭裏,需定義一個OAuth2FeignRequestInterceptor
的bean:
使用Postman
向http://localhost:8080/uaa/oauth/token
發送請求得到access_token
(admin用戶的如7f9b54d4-fd25-4a2c-a848-ddf8f119230b
)
暫時沒有作測試,下次補充。
代碼已經開源、託管到個人碼雲:
https://gitee.com/senhelpa-vivo/spring-cloud.git