父模塊:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> </parent> <properties> <spring-cloud.version>Greenwich.RELEASE</spring-cloud.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
創建資源模塊
創建工程並導入jar包
<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.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>2.1.0.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> </dependencies>
提供配置文件
server:
port: 9002
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///security_authority
username: root
password: 123456
main:
allow-bean-definition-overriding: true #允許我們自己覆蓋spring放入到IOC容器的對象
mybatis:
type-aliases-package: com.topcheer.domain
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.topcheer: debug
編寫資源管理配置類
@Configuration @EnableResourceServer public class OauthSourceConfig extends ResourceServerConfigurerAdapter { @Autowired private DataSource dataSource; /** * 指定token的持久化策略 * InMemoryTokenStore表示將token存儲在內存 * Redis表示將token存儲在redis中 * JdbcTokenStore存儲在數據庫中 * @return */ @Bean public TokenStore jdbcTokenStore(){ return new JdbcTokenStore(dataSource); } /** * 指定當前資源的id和存儲方案 * @param resources * @throws Exception */ @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId("product_api").tokenStore(jdbcTokenStore()); } @Override public void configure(HttpSecurity http) throws Exception{ http.authorizeRequests() //指定不同請求方式訪問資源所需要的權限,一般查詢是read,其余是write。 .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')") .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')") .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')") .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')") .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')") .and() .headers().addHeaderWriter((request, response) -> { response.addHeader("Access-Control-Allow-Origin", "*");//允許跨域 if (request.getMethod().equals("OPTIONS")) {//如果是跨域的預檢請求,則原封不動向下傳達請求頭信息 response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method")); response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers")); } }); } }
創建授權模塊
創建工程並導入jar包
<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.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> <version>2.1.0.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> </dependencies>
配置類
server:
port: 9001
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///security_authority
username: root
password: 123456
main:
allow-bean-definition-overriding: true
mybatis:
type-aliases-package: com.topcheer.domain
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.topcheer: debug
提供 SpringSecurity配置類
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserService userService; @Bean public BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginProcessingUrl("/login") .permitAll() .and() .csrf() .disable(); } //AuthenticationManager對象在OAuth2認證服務中要使用,提前放入IOC容器中 @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
提供OAuth2授權配置類
@Configuration @EnableAuthorizationServer public class OauthServerConfig extends AuthorizationServerConfigurerAdapter { //數據庫連接池對象 @Autowired private DataSource dataSource; //認證業務對象 @Autowired private UserService userService; //授權模式專用對象 @Autowired private AuthenticationManager authenticationManager; //客戶端信息來源 @Bean public JdbcClientDetailsService jdbcClientDetailsService(){ return new JdbcClientDetailsService(dataSource); } //token保存策略 @Bean public TokenStore tokenStore(){ return new JdbcTokenStore(dataSource); } //授權信息保存策略 @Bean public ApprovalStore approvalStore(){ return new JdbcApprovalStore(dataSource); } //授權碼模式數據來源 @Bean public AuthorizationCodeServices authorizationCodeServices(){ return new JdbcAuthorizationCodeServices(dataSource); } //指定客戶端信息的數據庫來源 @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(jdbcClientDetailsService()); } //檢查token的策略 @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.allowFormAuthenticationForClients(); } //OAuth2的主配置信息 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .approvalStore(approvalStore()) .authenticationManager(authenticationManager) .authorizationCodeServices(authorizationCodeServices()) .tokenStore(tokenStore()); } }
測試
在數據庫中手動添加客戶端信息
所有要使用當前項目資源的項目,都是我們的客戶端。比如我們之前舉的例子,A服務打印照片,B服務存儲照
片。A服務要使用B服務的資源,那么A服務就是B服務的客戶端。
這里要區分用戶的信息和客戶端信息,用戶信息是用戶在B服務上注冊的用戶信息,在sys_user表中。客戶端信息
是A服務在B服務中注冊的賬號,在OAuth2的oauth_client_details表中。
測試數據sql語句如下:
授權碼模式測試
在地址欄訪問地址
http://localhost:9001/oauth/authorize?response_type=code&client_id=topcheer_one
跳轉到SpringSecurity默認認證頁面,提示用戶登錄個人賬戶【這里是sys_user表中的數據】
點擊 Authorize后跳轉到回調地址並獲取授權碼
使用授權碼到服務器申請通行令牌 token
重啟資源服務器,然后攜帶通行令牌再次去訪問資源服務器,大功告成!
簡化模式測試
在地址欄訪問地址
http://localhost:9001/oauth/authorize?response_type=token&client_id=topcheer_one
由於上面用戶已經登錄過了,所以無需再次登錄,其實和上面是有登錄步驟的,這時,瀏覽器直接返回了token
密碼模式
客戶端模式測試
申請token