Spring Security OAuth2入门踩坑

吐槽Spring Security

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

最简的配置

修改pom.xml

1
2
3
4
5
6
7
8
9
10
11
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配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
scala复制代码@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)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
java复制代码@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);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
scala复制代码@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。

1
2
3
4
5
6
7
8
ini复制代码// 代码来自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用于根据用户名加载用户信息。

1
2
3
java复制代码public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

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

1
2
3
java复制代码public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

6.png

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

1
2
3
4
5
6
7
java复制代码public interface AccessDecisionManager {
void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException;

boolean supports(ConfigAttribute attribute);

boolean supports(Class<?> clazz);
}

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

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%