1. 概述

OAuth2.0是一个标准的授权协议,实际上它是用户资源和第三方应用之间的一个中间层,把资源和第三方应用隔开,使得第三方应用无法直接访问资源,第三方应用要访问资源需要通过提供凭证获得OAuth2.0授权,从而起到保护资源的作用

1.1. OAuth2.0角色

OAuth2.0在认证和授权过程中,主要有四种角色
授权服务(Authorization Server):进行访问的认证和授权
资源服务(Resource Server):存放用户资源的服务
资源所有者(Resource Owner):用户
客户端(Client):与授权和资源服务提供无关的任何第三方应用

1.2. OAuth2.0运行流程

OAuth2.0运行流程图

  1. 用户打开客户端,客户端要求用户给予授权
  2. 用户同意给客户端授权
  3. 客户端使用上一步获得的授权向授权服务器申请令牌
  4. 授权服务器对客户端进行认证,在确认无误后发放令牌
  5. 客户端使用令牌向资源服务器申请获取资源
  6. 资源服务器确认令牌正确后,向客户端开放资源

1.3. 客户端授权模式

OAuth2.0定义了以下4中授权模式

1.3.1. 密码模式

用户向客户端提供自己的用户名和密码,客户端使用这些信息向授权服务器申请授权
客户端发出HTTP请求参数如下:
grant_type:授权类型
username:用户名
password:用户密码
scope:权限范围

1.3.2. 客户端模式

客户端直接向授权服务器进行认证
HTTP请求参数如下:
grant_type:授权类型
scope:权限范围

1.3.3. 授权码模式

客户端通过后台服务器与授权服务器进行交互
客户端申请认证参数如下:
response_type:授权类型
client_id:客户端ID
redirect_uri:重定向URI
scope:权限范围
state:客户端当前状态
响应参数如下
code:授权码,客户端只能使用一次
state:当前状态
客户端向授权服务器申请令牌参数如下:
grant_type:授权模式authorization_code
code:授权码
redirect_uri:重定向URI
client_id:客户端ID
授权服务器响应参数如下:
access_token:访问令牌
token_type:令牌类型
expires_in:过期时间,单位秒
refresh_token:更新令牌
scope:权限范围
更新令牌请求参数如下:
grant_type:授权模式
refresh_token:刷新令牌
scope:权限范围

1.3.4. 简化模式

客户端直接在浏览器中向授权服务器申请令牌
HTTP请求参数如下:
response_type:授权
client_id:客户端ID
redirect_uri:重定向URI
scope:权限范围
state:客户端当前状态
HTTP响应参数如下:
access_token:访问令牌
token_type:令牌类型
expires_in:过期时间,单位秒
scope:权限范围
state:当前状态

2. 搭建授权服务

2.1. 引入核心依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
</dependency>

2.2. 编写application.yml

server:
  port: 8815
spring:
  application:
    name: cloud-oauth2-authorization-server
  main:
    allow-bean-definition-overriding: true
  mvc:
    throw-exception-if-no-handler-found: true
  resources:
    add-mappings: false
management:
  endpoints:
    web:
      exposure:
        include: refresh,health,info,env

logging:
  level:
    root: WARN
    org.springframework.web: INFO
    org.springframework.security: INFO
    org.springframework.security.oauth2: INFO

2.3. 编写AuthorizationServerConfig

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private ClientDetailsService clientDetailsService;
    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    /**
     * 配置令牌端点安全策略
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //oauth/token_key公开
        security.tokenKeyAccess("permitAll()")
                //oauth/check_token公开
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();
    }

    /**
     * 配置客户端
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                //设置clientId
                .withClient("client1")
                //设置clientSecret
                .secret(new BCryptPasswordEncoder().encode("123456"))
                //设置资源ID
                .resourceIds("resource1")
                //设置授权模式
                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
                //设置权限
                .scopes("all")
                //取消自动授权
                .autoApprove(false)
                //重定向地址
                .redirectUris("http://localhost:8816/authorized");
    }

    /**
     * 配置令牌服务
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .authorizationCodeServices(authorizationCodeServices)
                .tokenServices(tokenServices())
                .allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.GET);
    }

    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        //客户端详情服务
        services.setClientDetailsService(clientDetailsService);
        //允许令牌自动刷新
        services.setSupportRefreshToken(true);
        //令牌存储策略-内存
        services.setTokenStore(tokenStore);
        //使用jwt令牌
        services.setTokenEnhancer(accessTokenConverter);
        //令牌默认有效期2小时
        services.setAccessTokenValiditySeconds(7200);
        //刷新令牌默认有效期3天
        services.setRefreshTokenValiditySeconds(259200);
        return services;
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new InMemoryAuthorizationCodeServices();
    }

2.4. 编写TokenConfig

@Configuration
public class TokenConfig {

    private static final String SIGN_KEY = "oauth2";

    @Bean
    public TokenStore tokenStore() {
        //return new InMemoryTokenStore();
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey(SIGN_KEY);
        return accessTokenConverter;
    }
}

2.5. 编写WebSecurityConfig

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(proxyTargetClass = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

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

    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager(
                User.withUsername("admin").password(passwordEncoder().encode("123456")).authorities("USER").build(),
                User.withUsername("manager").password(passwordEncoder().encode("123456")).authorities("USER").build(),
                User.withUsername("worker").password(passwordEncoder().encode("123456")).authorities("USER").build());
        return userDetailsManager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()//关闭csrf跨域检查
                .authorizeRequests()
                .anyRequest()
                .authenticated()//其他请求需要登录
                .and()
                .formLogin();//默认login页面登录
    }
}

3. 搭建资源服务

3.1. 引入核心依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

3.2. 编写application.yml

server:
  port: 8816
spring:
  application:
    name: cloud-oauth2-resource-server
  main:
    allow-bean-definition-overriding: true
  mvc:
    throw-exception-if-no-handler-found: true
  resources:
    add-mappings: false
management:
  endpoints:
    web:
      exposure:
        include: refresh,health,info,env

3.3. 编写ResourceServerConfig

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    private static final String RESOURCE_ID = "resource1";

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId(RESOURCE_ID)
                //使用远程服务验证令牌服务
                .tokenServices(tokenServices())
                //无状态模式
                .stateless(true);
    }

    /**
     * 配置安全策略
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //路径匹配规则
                .antMatchers("/oauth2/**")
                //匹配scope
                .access("#oauth2.hasScope('all')")
                .and()
                .csrf().disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    /**
     * 配置access_token远程验证策略
     * @return
     */
    private ResourceServerTokenServices tokenServices() {
        RemoteTokenServices services = new RemoteTokenServices();
        services.setCheckTokenEndpointUrl("http://localhost:8815/oauth/check_token");
        services.setClientId("client1");
        services.setClientSecret("123456");
        return services;
    }
}

3.4. 编写WebSecurityConfig

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/oauth2/**")
                .hasAuthority("oauth2")
                .anyRequest().authenticated();
    }
}

3.5. 编写controller

@RestController
@RequestMapping("/oauth2")
public class OAuth2ResourceController {

    @GetMapping("/resources")
    public String[] getResources() {
        return new String[] {"Resource1", "Resource2", "Resource3"};
    }
}

4. 验证

依次启动cloud-oauth2-authorization-server和cloud-oauth2-resource-server微服务,在postman中发起post请求http://localhost:8815/oauth/token获取token
获取token
再次发起get请求http://localhost:8816/oauth2/resources,将上面获取的token带入请求头
获取资源

后记

spring-cloud-starter-oauth2已经过时,需要了解新方案的小伙伴,请参阅SpringCloud搭建微服务之OAuth2.1认证和授权

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐