阅读该篇文章时,希望各位可以先搞懂oauth2协议的相关内容,这样该篇文档用到的一些类,各位就会觉得理所应当了。
开发前准备
我在上篇文档的代码基础上,新建一个cloud+oauth2分支。
pom文件修改
1
2
3
4<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>修改为:
1
2
3
4
5
6
7
8
9<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
简易搭建认证服务器和资源服务器
在config包下,新建AuthorizationConfig类和ResourceConfig类,内容分别如下:
1
2
3
4@Component
@EnableAuthorizationServer//声明为认证服务器
public class AuthorizationConfig {
}1
2
3
4@Component
@EnableResourceServer//声明为资源服务器
public class ResourceConfig {
}启动项目,并访问localhost:8090/user出现401错误,如下图:
我们用postman发送post请求localhost:8090/oauth/token,请求参数配置如下图:
我们点击send,会返回如下效果:
1
2
3
4
5
6
7{
"access_token": "761be070-edee-4280-96be-33051683f5b3",
"token_type": "bearer",
"refresh_token": "ce89f7a6-bd36-492d-b7b7-83aa80e8e2b6",
"expires_in": 42601,
"scope": "all"
}接下来我们继续访问localhost:8090/user,就可以访问成功了。请求参数配置如下图:
完善认证服务器和资源服务器
上面的配置中,我们需要在启动台里找clientId和clientSecret,这样我们每次启动都会重新生成,所以我们接下来改造一下。
修改config包下的WebSecurityConfig类,修改为:
1
2
3
4
5
6
7
8
9
10
11
12
13@Component
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}我们应该可以发现重写的configure()方法已经被删除了,这是因为,我们已经加入资源服务器了,我们在这里配置的一些http.xxxx()不起作用了,所以我们将其删除。
修改config包下的AuthorizationConfig类,修改为:
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/**
* Created by xk on 2018/11/28
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager)//我们使用oauth2的密码模式时需要配置authenticationManager
.userDetailsService(userService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("app")
.secret(passwordEncoder.encode("app"))//这个地方在springboot1.x版本中不需要加密的,但是2.x版本需要加密
.authorizedGrantTypes("password")//这样写的话,我们获取的token里不会有refresh_token
.scopes("all")
.accessTokenValiditySeconds(120)
.and()
.withClient("web")
.secret(passwordEncoder.encode("web"))
.authorizedGrantTypes("password","refresh_token")
.scopes("all")
.accessTokenValiditySeconds(3600);
}修改config包下的ResourceConfig类,修改为:
1
2
3
4
5
6
7
8
9
10@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class ResourceConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/user/register").permitAll();
//由于所有接口默认会被资源服务器保护的,所以这个地方我们需要放行注册接口
}
}我们重新启动项目,这时控制台不会打印client信息了,因为我们已经自定义2个client信息了(app和web),我们继续获取token(记得请求时更换postman的Authorization的username和password),然后复制token,继续访问localhost:8090/user可获得全部用户。
增加角色和权限信息
事实上我们的用户有三六九之分,每个用户的角色决定着能访问哪些接口。
接下来我们加入角色的相关配置。
修改config包下的UserService类,修改为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22/**
* Created by xk on 2018/11/26
*/
@Component
public class UserService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
top.xiekun.springbootsecurityoauth2.domain.User user = userRepository.findByName(name);
if (user != null) {
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
if ("xiekun".equals(user.getName())) {
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_admin"));//只有xiekun这个用户才有管理员权限,这个地方必须要加ROLE_
}
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_normal"));
return new User(user.getName(), user.getPassword(), grantedAuthorities);
}
return null;
}
}这里我们引入了GrantedAuthority的概念,这个类里面存取我们用户的角色和权限。(上篇内容这个地方AuthorityUtils.commaSeparatedStringToAuthorityList(“admin”),其实只是一个工具类而已)。目前,我们暂时是硬编码给指定用户赋予指定权限的,后面我们介绍RBAC时,我们会从数据库里获取用户的角色。
修改UserController里的get(),修改为:
1
2
3
4
5@GetMapping
@PreAuthorize("hasRole('admin')")
public List<User> get(){
return userRepository.findAll();
}
启动项目,我们用xk用户获取token后,访问localhost:8090/user报如下错误:
因为我们再UserService类里配置了,只有xiekun这个用户才是admin用户。
我们用xiekun用户获取token后,访问localhost:8090/user则访问成功。
此时我们已经完成了用户的角色不同,访问的接口也会受到限制。
注:目前为止我们还没有给接口配置需要哪种权限才可以访问,这个其实非常简单。看源码我们可以发现,权限和角色的比对都是走的一个方法。所以我们只需要改动2个地方就行了。这就你们自己动手吧。
偷偷告诉你们其中一个改动点,就是controller的get()的@PreAuthorize(“hasRole(‘admin’)”)注解改为@PreAuthorize(“hasRole(‘admin’) and hasAuthority(‘read’)”)