美文网首页全新框架
Spring Security实现OAuth2.0——资源服务

Spring Security实现OAuth2.0——资源服务

作者: 文景大大 | 来源:发表于2021-08-09 20:15 被阅读0次

在上一篇文章中,我们已经实现了授权服务器,拿到访问token已经不是问题了。本文主要讲述如何搭建一个资源服务器,根据第三方客户端访问请求和token来实现资源的权限控制。

一、资源服务器配置

资源服务器的核心配置类就是需要继承ResourceServerConfigurerAdapter,并重写其中的资源配置方法和安全鉴权方法。

@Configuration
// 开启资源服务器
@EnableResourceServer
public class MyResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private ResourceServerTokenServices tokenService;

    /**
     * 系统启动时就加载——资源配置
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        // 本资源服务的ID,用于在授权服务器那里验证是否存在这个ID的资源服务
        resources.resourceId("user")
                // 加载token规则
                .tokenServices(tokenService)
                // 禁用session
                .stateless(true);
    }

    /**
     * 系统启动时就加载——对请求进行鉴权的配置
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 任何请求都必须具有all这个域的授权
                .antMatchers("/**").access("#oauth2.hasScope('all')")
                .and()
                .csrf().disable()
                // 禁用session,因为我们使用的是token,session没有用途
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    /**
     * 定义校验token的规则,见下面第二小节的描述
     * @return
     */
    @Bean
    public ResourceServerTokenServices tokenService(){
        // 当授权服务器和资源服务器不是同一台机器时需要使用RemoteTokenServices
        RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
        // 指定授权服务器校验token的端点
        remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:8081/oauth/check_token");
        // 资源服务器自己独有的身份信息
        remoteTokenServices.setClientId("iSchool");
        remoteTokenServices.setClientSecret("mysecret");
        return remoteTokenServices;
    }
}

二、Token校验设置

token校验有两种方式:

  • DefaultTokenServices,适用于资源服务器和授权服务器是同一台机器的场景;
  • RemoteTokenServices,适用于资源服务器和授权服务器是不同机器的场景;

我们该案例中资源服务器和授权服务器是不同的机器,大多数实际业务场景中也是不同的,所以我们以RemoteTokenServices为例进行演示,这里放到和资源服务器配置类一块:

/**
     * 定义校验token的规则
     * @return
     */
@Bean
public ResourceServerTokenServices tokenService(){
    // 当授权服务器和资源服务器不是同一台机器时需要使用RemoteTokenServices
    RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
    // 指定授权服务器校验token的端点
    remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:8081/oauth/check_token");
    // 资源服务器自己独有的身份信息
    remoteTokenServices.setClientId("iSchool");
    remoteTokenServices.setClientSecret("mysecret");
    return remoteTokenServices;
}

三、安全策略配置

资源服务器也是一个独立的应用,所以也需要增加安全访问的配置,这个在先前讲Spring Security的时候已经详细介绍过了。

@EnableWebSecurity
// 开启prePost的方法注解
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ResourceUserDetailsService userDetailsService;

    /**
     * 对请求进行鉴权的配置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                // 没有权限进入内置的登录页面
                .formLogin()
                .and()
                // 暂时关闭CSRF校验,允许get请求登出
                .csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    /**
     * 密码加密器,供在UserDetailsService中验证密码时使用
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
@Service
public class ResourceUserDetailsService implements UserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return User
                .withUsername("root")
                .password(passwordEncoder.encode("root"))
                // 设置当前用户可以拥有的权限信息
                .authorities("login")
                .build();
    }
}

四、定义资源

最后,增加我们的访问资源即可,这里就是Controller。

@RestController
public class UserController {

    @GetMapping("info/queryUserInfo")
    @PreAuthorize("hasAuthority('user:query')")
    public String queryUserInfo(){
        return "user info return!";
    }

    @GetMapping("delete/deleteUserInfo")
    @PreAuthorize("hasAuthority('user:delete')")
    public String deleteUserInfo(){
        return "user info delete!";
    }

}

此时,我们就完成了资源服务器的所有内容。

五、测试

5.1 授权服务器

我们以客户端模式启动上一节的授权服务器后,通过HTTP工具(我这里使用IDEA的插件RestClient)来进行如下的测试:

首先访问如下请求以获取访问token:

GET http://localhost:8081/oauth/token?client_id=iSchool&client_secret=mysecret&grant_type=client_credentials

得到的内容如下:

{
  "access_token": "1146bac1-ade0-4b2d-a919-99ecf5acb4dd",
  "token_type": "bearer",
  "expires_in": 43199,
  "scope": "all"
}

此时我们先不使用资源服务器,我们使用如下的请求来模拟资源服务器的验证token,以此来判断授权服务器是否能正常工作:

POST http://localhost:8081/oauth/check_token
body {
"token":"1146bac1-ade0-4b2d-a919-99ecf5acb4dd"
}

得到的返回内容是:

{
  "aud": [
    "user"
  ],
  "scope": [
    "all"
  ],
  "active": true,
  "exp": 1628545020,
  "authorities": [
    "user:query"
  ],
  "client_id": "iSchool"
}

可以看到,允许访问的资源ID是user、访问的资源域是all,拥有的权限是user:query,供client_id为iSchool的第三方应用访问,这和我们前面配置授权服务器时的内容一致,因此授权服务是正常的。

5.2 资源服务器

然后,我们就启动资源服务器,现在来请求资源服务器的资源,并带上我们从授权服务器那里得到的token:

GET http://localhost:8082/info/queryUserInfo
head {
  "Content-Type": "application/x-www-form-urlencoded",
  "Authorization": "Bearer 1146bac1-ade0-4b2d-a919-99ecf5acb4dd"
}

得到的返回结果为:

user info return!

但是,如果我们访问另外一个接口:

GET http://localhost:8082/delete/deleteUserInfo
head {
  "Content-Type": "application/x-www-form-urlencoded",
  "Authorization": "Bearer 1146bac1-ade0-4b2d-a919-99ecf5acb4dd"
}

得到的结果就是:

{
  "error": "access_denied",
  "error_description": "不允许访问"
}

很明显,当资源服务请求授权服务的时候,授权服务告诉资源服务,当前这个访问token只有user:query权限,所以需要其它权限才能访问的资源就被禁止。

如果我们的资源服务器域名称不是all,而是改成了user,那么访问资源就会报错如下信息:

{
  "error": "insufficient_scope",
  "error_description": "Insufficient scope for this resource",
  "scope": "user"
}

六、拓展

在实际的使用场景中,一个访问token并不仅仅限制只能访问拥有权限范围内的接口,还会限制只能访问某个客户的信息,因此我们还需要在授权中增加用户的身份信息。待研究。

相关文章

网友评论

    本文标题:Spring Security实现OAuth2.0——资源服务

    本文链接:https://www.haomeiwen.com/subject/gbctbltx.html