Spring Security Oauth2 - 基本使用【01】

一、概述

关于oauth2,其实是一个规范,对理论运行模式不清楚的可以参考下面链接

参考:
http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
https://oauth.net/2/

使用oauth2,主要就是以下三个组件

  • 配置资源服务器
  • 配置认证服务器
  • 配置spring security

前两点是oauth2的主体内容,spring security oauth2是建立在spring security基础之上的,所以有一些体系是公用的。

oauth2根据使用场景不同,分成了4种模式

  • 授权码模式(authorization code)

  • 简化模式(implicit)

  • 密码模式(resource owner password credentials)

  • 客户端模式(client credentials)

本文重点讲解接口对接中常使用的密码模式(以下简称password模式)和客户端模式(以下简称client模式)。

授权码模式使用到了回调地址,是最为复杂的方式,通常网站中经常出现的微博,qq第三方登录,都会采用这个形式。

简化模式不常用。

二、环境准备

代码:https://github.com/lexburner/oauth2-demo

项目:client-credentials

工具:maven + jdk1.8 + redis

参考资料:https://www.cnkirito.moe/categories/Spring-Security-OAuth2/

三、代码解析

1、开启Debug日志
logging:
  level:
    org.springframework.security: DEBUG
2、maven依赖
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-securityartifactId>
dependency>

<dependency>
    <groupId>org.springframework.security.oauthgroupId>
    <artifactId>spring-security-oauth2artifactId>
dependency>

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>

定义两个接口作为访问的资源

@RestController
public class TestEndpoints {

    @GetMapping("/product/{id}")
    public String getProduct(@PathVariable String id) {
        //for debug
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return "product id : " + id;
    }

    @GetMapping("/order/{id}")
    public String getOrder(@PathVariable String id) {
	    //for debug
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return "order id : " + id;
    }

}
3、配置资源服务器、授权服务器
@Configuration
public class OAuth2ServerConfig {

    private static final String DEMO_RESOURCE_ID = "order";

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(DEMO_RESOURCE_ID).stateless(true);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http
                    // Since we want the protected resources to be accessible in the UI as well we need
                    // session creation to be allowed (it's disabled by default in 2.0.6)
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                    .and()
                    .anonymous()
                    .and()
                    .authorizeRequests()
                    .antMatchers("/product/**").permitAll()		// 【1】
                    .antMatchers("/order/**").authenticated();	// 配置order访问控制,必须认证过后才可以访问
            // @formatter:on
        }
    }


    
    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        AuthenticationManager authenticationManager;
        @Autowired
        RedisConnectionFactory redisConnectionFactory;
        @Autowired
        UserDetailsService userDetailsService;

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // 配置两个客户端,一个用于password认证一个用于client认证
            clients.inMemory()
                        .withClient("client_1")
                        .resourceIds(DEMO_RESOURCE_ID)
                        .authorizedGrantTypes("client_credentials")
                        .scopes("select")
                        .authorities("oauth2")
                        .secret("123456")
                    .and()
                        .withClient("client_2")
                        .resourceIds(DEMO_RESOURCE_ID)
                        .authorizedGrantTypes("password", "refresh_token")
                        .scopes("select")
                        .authorities("oauth2")
                        .secret("123456");
        }

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints
                    .tokenStore(new RedisTokenStore(redisConnectionFactory))
                    .tokenStore(new InMemoryTokenStore())
                    .authenticationManager(authenticationManager)
                    .userDetailsService(userDetailsService)
                    // 允许 GET、POST 请求获取 token,即访问端点:oauth/token
                    .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);

            endpoints.reuseRefreshTokens(true);
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            //允许表单认证
            oauthServer.allowFormAuthenticationForClients();
        }

    }

}

【1】暴露一个商品查询接口(/product/),后续不做安全限制;一个订单查询接口(/order/),后续添加访问控制。

资源服务器配置,需要继承 ResourceServerConfigurerAdapter,通过 @EnableResourceServer 注解启用

授权服务器配置,需要继承 AuthorizationServerConfigurerAdapter,通过 @EnableAuthorizationServer 注解启用

简单说下spring security oauth2的认证思路。

  • client模式,没有用户的概念,直接与认证服务器交互,用配置中的客户端信息去申请accessToken,客户端有自己的 client_id,client_secret 对应于用户的 username,password,而客户端也拥有自己的authorities,当采取client模式认证时,对应的权限也就是客户端自己的authorities。
  • password模式,自己本身有一套用户体系,在认证时需要带上自己的用户名和密码,以及客户端的 client_id,client_secret。此时,accessToken所含的权限是用户本身的权限,而不是客户端的权限。

我对于两种模式的理解便是,如果你的系统已经有了一套用户体系,每个用户也有了一定的权限,可以采用password模式;如果仅仅是接口的对接,不考虑用户,则可以使用client模式。

4、配置spring security基础配置

我们在概述环节就说了,Oauth2 是基于 spring security 的一个扩展,并不是独立于 spring security ,需要依赖 spring security 中的一些基础配置完成整个认证授权功能。

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    // 【1】
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user_1").password("123456").authorities("USER")
                .and()
                .withUser("user_2").password("123456").authorities("USER");
    }
    
   @Bean
   @Override
   public AuthenticationManager authenticationManagerBean() throws Exception {
       AuthenticationManager manager = super.authenticationManagerBean();
        return manager;
    }

    // 【2】
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .requestMatchers().anyRequest()
            .and()
                .authorizeRequests()
                .antMatchers("/oauth/*").permitAll();
        // @formatter:on
    }
}

【1】配置的是认证信息, AuthenticationManagerBuilder 这个类,就是AuthenticationManager的建造者, 我们只需要向这个类中, 配置用户信息, 就能生成对应的AuthenticationManager, 这个类也提过,是用户身份的管理者, 是认证的入口。此处我们为认证管理器指定采用的认证数据源为内存,并且添加两个用户信息 user_1、user_2

【2】HttpSecurity 配置,主要功能就是对用户的请求进行拦截。通过重写 configure(HttpSecurity http) 方法就可以按照场景需求自定义认证和授权。主要配置Security的认证策略, 每个模块配置使用and结尾。

常用api参考:https://my.oschina.net/liuyuantao/blog/1922347

此处的例子,拦截所有的用户请求

四、实验环境

1、获取token

启动springboot应用就可以发现多了一些自动创建的endpoints:

{[/oauth/authorize]}
{[/oauth/authorize],methods=[POST]
{[/oauth/token],methods=[GET]}
{[/oauth/token],methods=[POST]}		// 重点关注一下/oauth/token,它是获取的token的endpoint
{[/oauth/check_token]}
{[/oauth/error]}
2、使用 postman 访问

password模式:

http://localhost:8080/oauth/token?grant_type=password&scope=select&username=user_1&password=123456&client_id=client_2&client_secret=123456

响应如下:

{
    "access_token": "7544c14f-3d01-4da8-8b59-ac490fd3e346",
    "token_type": "bearer",
    "refresh_token": "1ae3d3e6-9cbb-4463-ab59-83525b4650c3",
    "expires_in": 43199,
    "scope": "select"
}

client模式:

http://localhost:8080/oauth/token?grant_type=client_credentials&scope=select&client_id=client_1&client_secret=123456

响应如下:

{
    "access_token": "652b8bd0-79ab-4c0e-993d-e618b59c090f",
    "token_type": "bearer",
    "expires_in": 43199,
    "scope": "select"
}

访问两个资源接口

由于商品查询接口(/product/)不做安全限制,所以不传递token也是可以访问的

订单查询接口(/order/),添加了访问控制限制,所以不传token会报错

《1》商品查询接口 - 不带token请求支援(验证是否有访问限制)

http://localhost:8080/product/1

响应如下:

product id : 1

《2》订单查询接口 - 未带token进行访问

http://localhost:8080/order/1

响应如下:

{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource"
}

《3》 订单查询接口 - 带token进行访问(此处随便用哪个模式的token均可)

http://localhost:8080/order/1?access_token=652b8bd0-79ab-4c0e-993d-e618b59c090f

响应如下:

order id : 1

你可能感兴趣的:(oauth2,spring,security,spring,java,安全)