睁眼写BUG,闭眼改BUG。

Spring Boot (9.1) 安全管理-OAuth2

2019.07.12

Spring Security OAuth2

是什么?

OAuth 是一个开放标准,采用令牌的方式可以让用户灵活的对第三方应用授权或者收回权限

OAuth 2 角色

  • 资源所有者: 即用户, 具有头像、照片、视频等资源
  • 客户端: 即第三方应用
  • 授权服务器: 用来验证用户提供的信息是否正确, 并返回一个令牌给第三方应用
  • 资源服务器: 提供用户资源的服务器,例如头像、照片、视频等

一般来说, 授权服务器和资源服务器可以是同一台服务器

OAuth 2 授权流程

参考

OAuth 2 授权模式

  • 授权码模式: 功能完整、流程最严谨的授权模式。 他的特点就是通过客户端的服务器与授权服务器进行交互
  • 简化模式: 不需要客户端服务器参与, 直接在浏览器中向授权服务器申请令牌
  • 密码模式: 用户把用户名密码直接告诉客户端, 客户端使用这些信息向授权服务器申请令牌
  • 客户端模式: 客户端使用自己的名义而不是用户的名义向服务器提供者申请授权

实践

依赖

        <!-- Redis 排除Lettuce 引入Jedis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

将令牌存入Redis, 所以添加Redis 依赖

redis配置:

spring:
  # redis 配置
  redis:
    database: 0
    host: xx.xxx.xxx
    port: xxx
    password: xxx
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        max-wait: -1ms
        min-idle: 0

配置授权服务器

授权服务器和资源服务器可以是同一台服务器

/**
 * 授权服务器配置
 *
 * @author iscolt
 * @date 2019/07/12
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig
        extends AuthorizationServerConfigurerAdapter {

    // 该对象用来支持password模式
    @Autowired
    AuthenticationManager authenticationManager;

    // 用来缓存, 将token存到redis中
    @Autowired
    RedisConnectionFactory redisConnectionFactory;

    // 为刷新对象提供支持
    @Autowired
    UserDetailsService userDetailsService;

    // 密码加密
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置password授权模式
        clients.inMemory()
                .withClient("password")
                .authorizedGrantTypes("password")
                .accessTokenValiditySeconds(1800)
                .resourceIds("rid")
                .scopes("all")
                .secret("$2a$10$WtukGtXlkDrqDrH.0pUdXOi6NBN0exY8o6Upj3E1d4IK4hgT3fPuO");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 配置令牌存储
        endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 配置支持client_id和client_security支持
        security.allowFormAuthenticationForClients();
    }
}

配置资源服务器

/**
 * 资源服务器配置
 *
 * @author iscolt
 * @date 2019/07/12
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig
        extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        // 配置资源id, 和授权服务器资源id一致
        resources.resourceId("rid").stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // 配置HttpSecurity
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/user/**").hasRole("user")
                .anyRequest().authenticated();
    }
}

配置Security

/**
 * 配置Security
 *
 * @author iscolt
 * @date 2019/07/12
 */
@Configuration
public class WebSecurityConfig
        extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        return super.userDetailsService();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password("$2a$10$WtukGtXlkDrqDrH.0pUdXOi6NBN0exY8o6Upj3E1d4IK4hgT3fPuO")
                .roles("admin")
                .and()
                .withUser("iscolt")
                .password("$2a$10$WtukGtXlkDrqDrH.0pUdXOi6NBN0exY8o6Upj3E1d4IK4hgT3fPuO")
                .roles("user");
    }

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

测试验证

/**
 * This is Description
 *
 * @author iscolt
 * @date 2019/07/12
 */
@RestController
public class HelloController {

    @GetMapping("/admin/hello")
    public String admin() {
        return "hello admin";
    }

    @GetMapping("/user/hello")
    public String user() {
        return "hello user";
    }

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}