搭建SpringCloud微服務框架:四、Spring-Security-OAuth 服務接口鑒權


搭建微服務框架(服務接口鑒權)

前面已經可以通過SpringCloud可以來構建對外的接口,現在來介紹一下怎么通過使用OAuth2來進行接口的鑒權。

本文源地址:搭建微服務框架(服務接口鑒權)

Github地址:SQuid


介紹

OAuth2網上介紹的例子太多太多,簡單點介紹它就是一個授權的標准。

  • OAuth2目前擁有四種授權機制:

    授權碼模式(authorization code)

    授權碼模式大多數用於互聯網登錄的場景,比如在京東商城網站中,使用QQ號進行授權登錄:

    oauth-qqlogin.png

    簡化模式(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的代碼,為了方便,我先將我的工程截圖:

squid-oatuh2.png

緊接着,貼下代碼:

  • 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

oauth-header.png

Authorization 中的 Basic 為固定值,后續加密字符串為 scope+password 的Base64解密值。

oauth-body.png


總結

使用OAuth2可以幫助我們進行鑒權,也可以解決CSRF跨域,在選型到OAuth的時候,還擔心它會很復雜,畢竟之前是深深的體會使用 SpringSecurity 的無奈。

參考資料:

OAuth-example

Spring-Security-OAuth

OAuth 2.0 的一個簡單解釋
BAELDUNG.com (OAUTH)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM