乐趣区

三SpringCloud鉴权之OAuth20上篇

鉴权中心

springsecurity +oauth2.0

1、前言

必备知识

学习本文之前你应该会熟练使用 Springboot,并对 SpringSecurityOAuth2.0有所理解,如有需要请参考下面的一些内容,简单理解下相关知识

SpringSecurity

Spring Security 是一个功能强大、高度可定制的身份验证和访问控制框架。它用于保护基于 Spring 的应用程序。Spring Security 是一个专注于向 Java 应用程序提供身份验证和授权的框架。
与所有 Spring 项目一样,Spring 安全的真正威力在于它可以很容易地扩展以满足定制需求。

OAuth2.0

OAuth 2.0 是用于授权的行业标准协议。OAuth2.0 注重客户端开发人员的简单性,同时为 Web 应用程序、桌面应用程序、移动电话和客厅设备提供特定的授权流。
更多请参考 OAuth2.0

OAuth2.0 模式

模式 应用场景 描述
授权码(Auth Code) 成功后跳转其他页面,如第三方微信登录等
简化模式(implicit) 不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌
密码模式(password credentials) web 页面用户名密码登录
客户端模式(client credentials) 主要针对 openapi,使用 apikey 和 secretkey 方式

JWT

JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。更多请参考 JWT 入门教程

2、前期必备

核心 pom 依赖如下:

<!-- 注意是 starter, 自动配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 不是 starter, 手动配置 -->
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.6.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 将 token 存储在 redis 中 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

创建一个 rest 接口用于后面测试资源

@Slf4j
@RestController
public class TestSecurityController {@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、操作步骤

很多文章写的特别复杂,其实主要的内容也就分为下面几步

3.1、授权服务器AuthorizationServerConfigurerAdapter

需要自定义授权服务器,继承AuthorizationServerConfigurerAdapter,详细代码如下

@Configuration
@EnableAuthorizationServer
public   class AuthServerConfiguration extends AuthorizationServerConfigurerAdapter {
    private static final String DEMO_RESOURCE_ID = "order";

    @Autowired
    AuthenticationManager authenticationManager;
    @Autowired
    RedisConnectionFactory redisConnectionFactory;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置两个客户端, 一个用于 password 认证一个用于 client 认证
        String secret = new BCryptPasswordEncoder().encode("123456");//// 对密码进行加密
        clients.inMemory().withClient("client_1")
                .resourceIds(DEMO_RESOURCE_ID)
                .authorizedGrantTypes("client_credentials", "refresh_token")
                .scopes("select")
                .authorities("client")
                .secret(secret)
                .and().withClient("client_2")
                .resourceIds(DEMO_RESOURCE_ID)
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("select")
                .authorities("client")
                .secret(secret);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .tokenStore(new RedisTokenStore(redisConnectionFactory))
                .authenticationManager(authenticationManager);
    }

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

}

3.2、资源服务器ResourceServerConfigurerAdapter

同上,需要实现自己的资源服务器,继承ResourceServerConfigurerAdapter,详细代码如下

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    private static final String DEMO_RESOURCE_ID = "order";
    @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()
                .requestMatchers().anyRequest()
                .and()
                .anonymous()
                .and()
                .authorizeRequests()
//                    .antMatchers("/product/**").access("#oauth2.hasScope('select') and hasRole('ROLE_USER')")
                .antMatchers("/order/**").authenticated();// 配置 order 访问控制,必须认证过后才可以访问
        // @formatter:on
    }
}

3.3、配置 SpringSecurity

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    protected UserDetailsService userDetailsService(){InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        String pwd = new BCryptPasswordEncoder().encode("123456");// 对密码进行加密
        manager.createUser(User.withUsername("user_1").password(pwd).authorities("USER").build());
        manager.createUser(User.withUsername("user_2").password(pwd).authorities("USER").build());
        return manager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .requestMatchers().anyRequest()
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/*").permitAll();}
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {AuthenticationManager manager = super.authenticationManagerBean();
        return manager;
    }

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

4、测试

我们设计的是 product 服务可以匿名访问,而 order 服务需要签名才可以访问,验证如下:

  • password 模式

利用 postman 进行 post 访问 http://localhost:8080/oauth/token?username=user_1&password=123456&grant_type=password&scope=select&client_id=client_2&client_secret=123456
获取如下结果

{
    "access_token": "c2340190-48f3-4291-bb17-1e4d51bcb284",
    "token_type": "bearer",
    "refresh_token": "03ee113c-a942-452a-9918-7ffe24472a7f",
    "expires_in": 40399,
    "scope": "select"
}

  • client 模式

同样利用 postman 的 POST 方式访问 http://localhost:8080/oauth/token?grant_type=client_credentials&scope=select&client_id=client_1&client_secret=123456
结果如下

{
    "access_token": "05a4e614-f34b-4c83-9ec1-89ea55c0afd2",
    "token_type": "bearer",
    "expires_in": 40396,
    "scope": "select"
}

对资源进行访问
  • product 服务:访问 http://localhost:8080/product/1 得到如下数据

product id : 1

  • order 服务: 访问http://localhost:8080/order/1,返回数据如下
<oauth>
<error_description>
Full authentication is required to access this resource
</error_description>
<error>unauthorized</error>
</oauth>

验证结果,说明 order 服务需要签名才可以访问,接下来,我们输入签名访问 order 服务。
我们分别利用上面 password 模式获取的 token,访问 http://localhost:8080/order/1?access_token=c2340190-48f3-4291-bb17-1e4d51bcb284
得到数据 order id : 1

通用利用 client 模式获取的 token,访问 http://localhost:8080/order/1?access_token=05a4e614-f34b-4c83-9ec1-89ea55c0afd2
同样可以得到 order id : 1

示例代码

分支为springsecurity-oauth2-1,master 为最终代码

https://github.com/liangliang1259/fast-cloud-examples/tree/springsecurity-oauth2-1

参考

http://blog.didispace.com/spr…

退出移动版