spring cloud oauth2微服务认证授权

springcloud oauth 官方页面 https://spring.io/projects/spring-security-oauth#learn

oauth2官网 https://oauth.net/2/

OAuth 2.0 is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices. This specification and its extensions are being developed within the IETF OAuth Working Group.

OAuth 2.0是用于授权的行业标准协议。 OAuth 2.0着眼于简化客户端开发人员,同时为Web应用程序,桌面应用程序,移动电话和客厅设备提供特定的授权流程。 该规范及其扩展名正在IETF OAuth工作组内开发。

首先来了解下Oatuh2中的几个名字,方便下文的阐述。

  1. Third-party application: 第三方应用
  2. Resource Owner: 资源持有者,一般就是用户自身
  3. Authorization server: 认证服务器
  4. Resource server: 资源服务器,即具体资源的存储方。与认证服务器是不同的逻辑节点,但是在物理上,双方是可以在一起的
  5. User Agent: 用户代理,一般就是指的浏览器
  6. Http Service: 服务提供者,也就是持有Resource Server的存在方。可以理解为类似QQ,或者微信这样具备用户信息的服务者。

Oauth2的作用就是让第三方应用在用户(资源持有者)授权的情况下,通过认证服务器的认证,从而安全的在资源服务器上获得对应的用户资源的流程指导。

oauth2常用的四种模式

 1.密码模式(Resource Owner Password Credentials Grant)

 

  •  第一步:用户访问用页面时,输入第三方认证所需要的信息(QQ/微信账号密码)
  •  第二步:应用页面那种这个信息去认证服务器授权
  •  第三步:认证服务器授权通过,拿到token,访问真正的资源页面

优点:不需要多次请求转发,额外开销,同时可以获取更多的用户信息。(都拿到账号密码了)

缺点:局限性,认证服务器和应用方必须有超高的信赖

应用场景:自家公司搭建的认证服务器

2.客户端凭证模式(Client Credentials Grant)

  •  第一步:用户访问应用客户端
  •  第二步:通过客户端定义的验证方法,拿到token,无需授权
  •  第三步:访问资源服务器A
  •  第四步:拿到一次token就可以畅通无阻的访问其他的资源页面。

这是一种最简单的模式,只要client请求,我们就将AccessToken发送给它。这种模式是最方便但最不安全的模式。因此这就要求我们对client完全的信任,而client本身也是安全的。

因此这种模式一般用来提供给我们完全信任的服务器端服务。在这个过程中不需要用户的参与。

3.授权码授权模式(Authorization code Grant)

  • 第一步:用户携带client_id redirect_uri response_type 访问应用客户端
  • 第二步:客户端返回登录认证页面 
  • 第三步:输入用户名密码登录  
  • 第四步:客户端验证码密码正确 账号密码准确性 和权限 验证通过 重定向到请求参数配置的重定向地址 并且携带code
  • 第五步:用户携带code grant_type redirect_uri client_id client_secret (这里client ID和secret 使用表单认证)
  • 第六步:客户端验证成功  通过返回accss_token refresh 信息
  • 第七步:拿到token 且token 没失效 就可以畅通无阻的访问其他的资源页面。

4.隐式授权模式(Implicit Grant)

 授权码默认简化版本

  • 第一步:用户携带client_id redirect_uri response_type 访问应用客户端
  • 第二步:客户端返回登录认证页面 
  • 第三步:输入用户名密码登录  
  • 第四步:客户端验证码密码正确 账号密码准确性 和权限 验证通过 重定向到请求参数配置的重定向地址 并且携带refresh_token和access_token等信息
  • 第五步:拿到token 且token 没失效 就可以畅通无阻的访问其他的资源页面。

理论到此结束

这里创建3个服务

第一个 eureka 注册中心 

第二个 auth-service  认证服务

第三个 resource-service 资源服务器

使用的springcloud 版本 为 Hoxton.SR4 

auth-service服务开始 

认证服务整体如图

pom依赖配置
        <!-- Eureka 服务发现与注册客户端依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <!-- actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
AuthorizationServerConfig
/**
 * 认证配置
 *
 * @author admin
 * @date 2020-06-17 17:06:30
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

   private final AuthenticationManager authenticationManager;

   private final ClientDetailsService clientDetailsService;

   private final UserDetailsService userDetailsService;

   @Autowired
   public AuthorizationServerConfig(ClientDetailsService clientDetailsService, AuthenticationManager authenticationManager, UserDetailsService userDetailsService) {
      this.clientDetailsService = clientDetailsService;
      this.authenticationManager = authenticationManager;
      this.userDetailsService = userDetailsService;
   }

   @Override
   public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
      //用来配置令牌端点(Token Endpoint)的安全与权限访问。
      security
            .tokenKeyAccess("permitAll()")
            .checkTokenAccess("permitAll()")
            .accessDeniedHandler(new AccessDeniedHandlerImpl())
            //允许表单传入 client_id client_secret进行认证
            .allowFormAuthenticationForClients()
      ;
   }

   /**
    * 认证配置
    * @param endpoints endpoints
    * @throws Exception endpoints
    */
   @Override
   public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      //用来配置授权以及令牌(Token)的访问端点和令牌服务(比如:配置令牌的签名与存储方式)
      endpoints
            .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
            .authenticationManager(this.authenticationManager)
            //token 存储
            .tokenStore(tokenStore())
            .userDetailsService(userDetailsService)
      ;
   }

   @Override
   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
      //内存中配置客户端信息
      //用来配置客户端详情信息,一般使用数据库来存储或读取应用配置的详情信息
      clients.inMemory().withClient("user")
            .accessTokenValiditySeconds(30 * 60)
            .refreshTokenValiditySeconds(30 * 60)
            .authorizedGrantTypes("authorization_code", "refresh_token", "client_credentials", "password","implicit")
            .scopes("all", "read", "write")
            // // secret密码配置从 Spring Security 5.0开始必须以 {加密方式}+加密后的密码 这种格式填写
            //        /*
            //         *   当前版本5新增支持加密方式:
            //         *   bcrypt - BCryptPasswordEncoder (Also used for encoding)
            //         *   ldap - LdapShaPasswordEncoder
            //         *   MD4 - Md4PasswordEncoder
            //         *   MD5 - new MessageDigestPasswordEncoder("MD5")
            //         *   noop - NoOpPasswordEncoder
            //         *   pbkdf2 - Pbkdf2PasswordEncoder
            //         *   scrypt - SCryptPasswordEncoder
            //         *   SHA-1 - new MessageDigestPasswordEncoder("SHA-1")
            //         *   SHA-256 - new MessageDigestPasswordEncoder("SHA-256")
            //         *   sha256 - StandardPasswordEncoder
            //         */
            .secret("{noop}style")
            .redirectUris("https://www.baidu.com")
      ;
   }

   /**
    * Persistence interface for OAuth2 tokens.
    *
    * @return TokenStore
    */
   @Bean
   public TokenStore tokenStore() {
      return new InMemoryTokenStore();
   }

}
SecurityConfig
package com.style.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * Security 配置
 *
 * @author admin
 * @date 2020-06-18 15:23:04
 */
@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Override
   protected void configure(HttpSecurity http) throws Exception {
      http
            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS).permitAll()
            .antMatchers("/oauth/**").permitAll()
            .anyRequest().authenticated()
            .and()
            //basic 弹窗登录
            //.httpBasic()
            //表单登录
            .formLogin()
            .and()
            .csrf().disable()
      ;
   }

   @Bean
   public UserDetailsService userDetails() {
      UserDetails admin = User.withUsername("admin")
            //,默认BCryptPasswordEncoder 更多实现 org.springframework.security.crypto.password.PasswordEncoder
            //可查看该接口的实现
            // password  Spring Security 5.0开始必须以 {加密方式}+加密后的密码 这种格式填写
            .password("{bcrypt}$2a$10$ZlFDDZMkZ9P7Yb4BsZ50ZueNzn7yM3GTJD97M5cJMWDu4oKr1Lsuq")
            .roles("ADMIN", "USER")
            .build();
      UserDetails user = User.withUsername("user")
            .password("{bcrypt}" + new BCryptPasswordEncoder().encode("123456"))
            .roles("USER")
            .build();
      //内存用户管理器
      InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
      userDetailsManager.createUser(admin);
      userDetailsManager.createUser(user);
      return userDetailsManager;
   }

   @Bean
   @Override
   protected AuthenticationManager authenticationManager() throws Exception {
      return super.authenticationManager();
   }
}

AuthServerApplication 启动类

/**
 * 认证服务
 *
 * @author admin
 */
@SpringBootApplication
@EnableDiscoveryClient
public class AuthServerApplication {

   public static void main(String[] args) {
      SpringApplication.run(AuthServerApplication.class, args);
   }

}

resource-service 

整体目录结构

pom依赖配置

        <!-- Eureka 服务发现与注册客户端依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <!-- actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
ResourceServerConfig
package com.style.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;

/**
 * 资源服务配置
 *
 * @author admin
 * @date 2020-06-18 16:59:28
 */
@EnableResourceServer
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

   private static final String RESOURCE_ID = "resource";

   @Override
   public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
      resources.resourceId(RESOURCE_ID);
      resources.tokenServices(tokenService());
   }

   @Override
   public void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests()
            .antMatchers("/oauth/**").permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .csrf()
            .disable()
            .formLogin()
      ;
   }

   @Bean
   public RemoteTokenServices tokenService() {
      //远程token服务 即为认证的服务器的check_token地址
      RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
      remoteTokenServices.setClientId("user");
      remoteTokenServices.setClientSecret("style");
      // 20110 端口为auth-service 的端口
      remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:20110/oauth/check_token");
      return remoteTokenServices;
   }

   /**
    * Persistence interface for OAuth2 tokens.
    *
    * @return TokenStore
    */
   @Bean
   public TokenStore tokenStore() {
      return new InMemoryTokenStore();
   }
}
SecurityConfig
package com.style.auth.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * Security配置
 *
 * @author admin
 * @date 2020-06-18 15:23:04
 */
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Override
   protected void configure(HttpSecurity http) throws Exception {
      http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/oauth/**").permitAll()
            .anyRequest().authenticated()
            .and()
            //表单登录
            .formLogin()
      ;
   }
}

ResourceController

@RestController
public class ResourceController {

   @Value("${server.port}")
   private Integer serverPort;

   @GetMapping("/port")
   public String getPort() {
      return "server port is :" + serverPort;
   }
}
ResourceServerApplication 启动类

启动 eureka

启动 auth-service

启动 resource-service

四个模式按个测试下

1.密码模式

postman 访问 

http://localhost:20110/oauth/token?client_id=user&client_secret=style&grant_type=password&username=user&password=123456

2.客户端凭证模式

http://localhost:20110/oauth/token?client_id=user&client_secret=style&grant_type=client_credentials&scope=read

3.授权码模式

浏览器请求

获取code 

 http://localhost:20110/oauth/authorize?response_type=code&redirect_uri=https://www.baidu.com&client_id=user&scop=all

跳转登录界面

输入账号密码进行登录

重定向 到 https://www.baidu.com/?code=Zs97m8  并且携带了code码

下一步根据 code 进行获取token

请求地址 

http://localhost:20110/oauth/token?code=Zs97m8&grant_type=authorization_code&redirect_uri=https://www.baidu.com&scope=all&client_id=user&client_secret=style

4.简化模式

http://localhost:20110/oauth/authorize?response_type=token&client_id=user&redirect_uri=https://www.baidu.com

首先重定向到登录页面 

登录成功 重定向到设置的重定向 地址中

改地址已经存在了 access_token 

https://www.baidu.com/#access_token=eb96d104-cec7-4d2e-9da6-6fd8f2851446&token_type=bearer&expires_in=1031&scope=all%20read%20write

刷新token 接口 

http://localhost:20110/oauth/token?client_id=user&client_secret=style&grant_type=refresh_token&refresh_token=819e651a-b704-4bce-9147-532c5fc06ada

验证下 resource_serivice 

该接口为resource_service中定义的一个接口 位于ResourceController类中 

第一次访问没有携带token 进行访问 提示错误 

第二次 

亦或者 

请求头里添加 Authorization value为 bearer{空格}+token

源码见 https://github.com/passliang/panda

参考知乎文章https://zhuanlan.zhihu.com/p/84670338

https://www.cnblogs.com/Innocent-of-Dabber/p/11009811.html

猜你喜欢

转载自blog.csdn.net/a15835774652/article/details/107065563