Spring Security OAuth2入门踩坑

1,238 阅读3分钟

吐槽Spring Security

刚开始使用的时候确实很简单,只需要添加依赖和几个配置类就行,但到后面只是想OAuth2和Spring Security默认登录功能一起用的时候,发现无从下手。看来不对Spring Security有个大概的了解,那么灵活是不存在的。

最简的配置

修改pom.xml

<!-- 若使用Spring Security则需要配置该依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 若使用OAuth2则还需额外配置该依赖 -->
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.5.RELEASE</version>
</dependency>

新增Spring Security配置类

@EnableWebSecurity
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public UserDetailsService userDetailsServiceBean() {
        UserDetails user = User
                // 这里配置个用户名是user1密码是123456的用户
                .withUsername("user1")
                .password(passwordEncoder().encode("123456"))
                // 这里的USER是自定义,没有什么特殊的含义
                .roles("USER")
                .build();

        // InMemoryUserDetailsManager设计目的主要是测试和功能演示
        return new InMemoryUserDetailsManager(user);
    }

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

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

}

新增OAuth2配置类(注意Spring Security过滤器会因为@Order顺序而在执行的时候有先后顺序,默认情况下过滤器执行顺序是OAuth2 Authorization -> OAuth2 Resource -> Spring Security)

@EnableAuthorizationServer
@Configuration
public class OAuth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
    private final AuthenticationManager authenticationManager;
    private final PasswordEncoder passwordEncoder;

    public OAuth2AuthorizationConfig(AuthenticationManager authenticationManager,
                                     PasswordEncoder passwordEncoder) {
        this.authenticationManager = authenticationManager;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 开启 /oauth/token_key 验证端口成无权限访问
        security.tokenKeyAccess("permitAll()")
            // 开启 /oauth/check_token 验证端口成认证权限访问
            .checkTokenAccess("isAuthenticated()")
            // 主要是让 /oauth/token 支持 client_id 以及 client_secret 做登录认证
            .allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 这里配置个客户ID是client1密码是123456的客户端
        clients.inMemory()
                .withClient("client1")
                .secret(passwordEncoder.encode("123456"))
                .authorizedGrantTypes("authorization_code", "password", "refresh_token")
                // 这里的ALL是自定义,没有什么特殊的含义
                .scopes("ALL")
                .accessTokenValiditySeconds(3600);
    }

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

}
@EnableResourceServer
@Configuration
public class OAuth2ResourceConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // !!!这里我配置成只处理请求头里有"Authorization"属性的请求
        http.requestMatcher((request) -> {
            return request.getHeader("Authorization") != null;
        });

        http.authorizeRequests()
                .anyRequest()
                .authenticated();
    }

}

通过Spring Security自带的登录页面登录http://127.0.0.1:8080/login

1.png

通过OAuth2获取token http://127.0.0.1:8080/oauth/token?username=user1&password=123456&grant_type=password&client_id=client1&client_secret=123456

2.png

通过获取的token访问资源

3.png

介绍Spring Security原理

通过DelegatingFilterProxy、FilterChainProxy、SecurityFilterChain实现认证和授权,需要特别注意的是1.一次请求里只有最先匹配到的SecurityFilterChain会被执行。2.SecurityFilterChain里有一系列跟认证/授权相关的过滤器,其中包括FilterSecurityInterceptor。

4.png

5.png

Spring Security主要组成部分

  • SecurityContextHolder, 提供获取 SecurityContext 的方法。
  • SecurityContext, 安全上下文,提供获取 Authentication 等方法。
  • Authentication, 代表认证的对象。
  • GrantedAuthority, 代表赋予的权限。

SecurityContextHolder,可以获取安全上下文SecurityContext。

// 代码来自https://www.springcloud.cc/spring-security-zhcn.html
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
  String username = ((UserDetails)principal).getUsername();
} else {
  String username = principal.toString();
}

UserDetailsService用于根据用户名加载用户信息。

public interface UserDetailsService {
   UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

AuthenticationManager是认证管理器,其实现类ProviderManager通过一系列AuthenticationProvider实现类验证认证信息。

public interface AuthenticationManager {
   Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

6.png

AccessDecisionManager是访问决策管理器,可以通过该类实现动态权限控制。其实现类AffirmativeBased通过一系列AccessDecisionVoter实现类决定授权结果。

public interface AccessDecisionManager {
    void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException;

    boolean supports(ConfigAttribute attribute);

    boolean supports(Class<?> clazz);
}

以下是阅读过的其他作者写的文章