搭建微服務框架(服務接口鑒權)
前面已經可以通過SpringCloud可以來構建對外的接口,現在來介紹一下怎么通過使用OAuth2來進行接口的鑒權。
本文源地址:搭建微服務框架(服務接口鑒權)
Github地址:SQuid
介紹
OAuth2網上介紹的例子太多太多,簡單點介紹它就是一個授權的標准。
-
OAuth2目前擁有四種授權機制:
授權碼模式(authorization code)
授權碼模式大多數用於互聯網登錄的場景,比如在京東商城網站中,使用QQ號進行授權登錄:
簡化模式(implicit)
一般很少用到,目前對這種模式自己也沒有代碼上的實現,所以還是不進行敘述,免得誤人子弟了😋
密碼模式(resource owner password credentials)
密碼模式對於一般的企業B/S架構系統用到的非常多,畢竟系統中已經存儲了用戶名和密碼,而密碼模式則就是需要提供用戶名和密碼。客戶端使用這些信息,向“服務商提供商(也就是企業開發的系統)”索要授權。
客戶端模式(client credentials)
客戶端模式多用於對第三方接口的使用,分配給對方一個secret就行,用對方來進行acess_token的獲取。
-
關鍵名詞說明:
- client_id :客戶端的Id。
- access_token : 授權成功后返回的token。
- expires_in : 過期時間。
- refresh_token : access_token即將過期,或者access_token丟失,可以使用refresh_token重新獲取。
- scope : 本次申請權限的范圍。
- grant_type : 授權的類型,填寫對應的授權機制定義的名詞。
本次集成的是
Spring Security OAuth
,集成后的項目僅支持密碼模式
和客戶端模式
兩種。
使用Spring Security OAuth
在集成OAuth時,我們需要擁有以下環境:
Mysql5.7+
MySQL用來記錄OAuth的必要信息: client_id、clien_secret、grant、scope
,SQL腳本地址:oauth2.sql
Redis
Redis則用來控制 access_token
的有效期。
新建工程
首先我們先在原有的工程下新建一個 squid-oatuh2
的工程,引入下列maven依賴:
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-config</artifactId>
</dependency>
引入依賴后,我們開始編寫使用OAuth的代碼,為了方便,我先將我的工程截圖:
緊接着,貼下代碼:
- AuthResourceConfig
@EnableResourceServer
@Configuration
public class AuthResourceConfig extends ResourceServerConfigurerAdapter {
@Autowired
RedisConnectionFactory redisConnectionFactory;
// @Autowired
// DataSource dataSource;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
super.configure(resources);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.
csrf().disable()
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
}
// @Bean
// ResourceServerTokenServices resourceServerTokenServices() {
// RemoteTokenServices tokenServices = new RemoteTokenServices();
// tokenServices.setCheckTokenEndpointUrl("http://localhost:8081/oauth/check_token");
// tokenServices.setAccessTokenConverter(accessTokenConverter());
// return tokenServices;
// }
@Bean
public AccessTokenConverter accessTokenConverter() {
return new DefaultAccessTokenConverter();
}
@Bean
RedisTokenStore redisTokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
// @Bean
// ClientDetailsService clientDetailsService() {
// return new JdbcClientDetailsService(dataSource);
// }
@Bean
@Primary
DefaultTokenServices defaultTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(redisTokenStore());
// tokenServices.setSupportRefreshToken(true);
// tokenServices.setClientDetailsService(clientDetailsService());
// tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12); // token有效期自定義設置,默認12小時
// tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//默認30天,這里修改
return tokenServices;
}
}
AuthResourceConfig,作為資源授權模塊來說,它更多的出現在業務模塊工程中,做的是資源授權的操作,每一個微服務模塊都會被當成一個資源來定義,而請求資源,則需要每次校驗 access_token
,這個類可以出現在任意一個微服務工程中。
- AuthServerConfig
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Autowired
DataSource dataSource;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsServiceImpl userServices;
@Bean
RedisTokenStore redisTokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
@Bean
ClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
@Bean
@Primary
DefaultTokenServices defaultTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(redisTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(clientDetailsService());
tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12); // token有效期自定義設置,默認12小時
tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//默認30天,這里修改
return tokenServices;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()");
security.checkTokenAccess("isAuthenticated()");
security.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(redisTokenStore()).userDetailsService(userServices).authenticationManager(authenticationManager).tokenServices(defaultTokenServices());
}
}
認證授權,顧名思義,看到實現的代碼也明白,做的是與 Mysql和Redis的交互,做到關鍵的用戶登錄信息處理,以及token的有效期定義。
- WebSecurityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userServices;
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userServices).passwordEncoder(passwordEncoder());
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/plugins/**", "/favicon.ico");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// http
// .authorizeRequests()
// .anyRequest().authenticated()
// .antMatchers("/oauth/**").permitAll()
// .and()
// .csrf().disable();
}
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
boolean matches = encoder.matches("123456", "$2a$10$cKRbR9IJktfmKmf/wShyo.5.J8IxO/7YVn8twuWFtvPgruAF8gtKq");
System.out.println(matches);
// System.out.println(encoder.encode("123456"));
}
}
- UserServiceImpl
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
// @Autowired
// private SysUserDao sysUserDao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// SysUser sysUser = sysUserDao.findByUserName(s);
//
// if (sysUser == null) {
// throw new UsernameNotFoundException("用戶名或密碼錯誤");
// }
return new User(s, "$2a$10$cKRbR9IJktfmKmf/wShyo.5.J8IxO/7YVn8twuWFtvPgruAF8gtKq", new HashSet<>());
}
}
由於目前還沒有介紹到使用 SpringDataJPA
以及 MyBatils-Plus
,這里我們先將用戶密碼寫死 123456
。
- application.yaml
server:
port: 8081
spring:
redis:
host: yanzhenyidai.com
port: 6379
password: ****
datasource:
url: jdbc:mysql://yanzhenyidai.com:3306/oauth2?useUnicode=true&characterEncoding=utf-8
username: yanzhenyidai
password: password
druid:
driver-class-name: com.mysql.jdbc.Driver
main:
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
server-addr: yanzhenyidai.com:8848
application:
name: squid-oauth2
show-sql: true
logging:
config: classpath:logback.xml
level:
org:
springframework:
web: info
- OAuthApplication
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class OAuthApplication {
public static void main(String[] args) {
SpringApplication.run(OAuthApplication.class, args);
}
@GetMapping("/test")
public String test() {
return "test";
}
@RequestMapping("/user")
public Principal principal(Principal principal) {
return principal;
}
}
一切完成后,我們啟動服務,來進行測試,這個時候需要使用到 POSTMAN
Authorization
中的 Basic
為固定值,后續加密字符串為 scope+password 的Base64解密值。
總結
使用OAuth2可以幫助我們進行鑒權,也可以解決CSRF跨域,在選型到OAuth的時候,還擔心它會很復雜,畢竟之前是深深的體會使用 SpringSecurity
的無奈。
參考資料:
OAuth-example
Spring-Security-OAuth
OAuth 2.0 的一個簡單解釋
BAELDUNG.com (OAUTH)