spring-cloud-starter-oauth2

阅读该篇文章时,希望各位可以先搞懂oauth2协议的相关内容,这样该篇文档用到的一些类,各位就会觉得理所应当了。

开发前准备

  1. 我在上篇文档的代码基础上,新建一个cloud+oauth2分支。

  2. 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>

简易搭建认证服务器和资源服务器

  1. 在config包下,新建AuthorizationConfig类和ResourceConfig类,内容分别如下:

    1
    2
    3
    4
    @Component
    @EnableAuthorizationServer//声明为认证服务器
    public class AuthorizationConfig {
    }
    1
    2
    3
    4
    @Component
    @EnableResourceServer//声明为资源服务器
    public class ResourceConfig {
    }
  2. 启动项目,并访问localhost:8090/user出现401错误,如下图:

    image-20190318150633767

  3. 我们用postman发送post请求localhost:8090/oauth/token,请求参数配置如下图:

    image-20190318153652499

    image-20190318152140251

    我们点击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"
    }
  4. 接下来我们继续访问localhost:8090/user,就可以访问成功了。请求参数配置如下图:

    image-20190318152511198

完善认证服务器和资源服务器

  1. 上面的配置中,我们需要在启动台里找clientId和clientSecret,这样我们每次启动都会重新生成,所以我们接下来改造一下。

  2. 修改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()不起作用了,所以我们将其删除。

  3. 修改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);
    }
  4. 修改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();
    //由于所有接口默认会被资源服务器保护的,所以这个地方我们需要放行注册接口
    }
    }
  5. 我们重新启动项目,这时控制台不会打印client信息了,因为我们已经自定义2个client信息了(app和web),我们继续获取token(记得请求时更换postman的Authorization的username和password),然后复制token,继续访问localhost:8090/user可获得全部用户。

增加角色和权限信息

  1. 事实上我们的用户有三六九之分,每个用户的角色决定着能访问哪些接口。

  2. 接下来我们加入角色的相关配置。

    1. 修改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时,我们会从数据库里获取用户的角色。

    2. 修改UserController里的get(),修改为:

      1
      2
      3
      4
      5
      @GetMapping
      @PreAuthorize("hasRole('admin')")
      public List<User> get(){
      return userRepository.findAll();
      }
  3. 启动项目,我们用xk用户获取token后,访问localhost:8090/user报如下错误:

    image-20190318160311868

    因为我们再UserService类里配置了,只有xiekun这个用户才是admin用户。

  4. 我们用xiekun用户获取token后,访问localhost:8090/user则访问成功。

    image-20190318160546596

  5. 此时我们已经完成了用户的角色不同,访问的接口也会受到限制。

注:目前为止我们还没有给接口配置需要哪种权限才可以访问,这个其实非常简单。看源码我们可以发现,权限和角色的比对都是走的一个方法。所以我们只需要改动2个地方就行了。这就你们自己动手吧。

image-20190318162043329

偷偷告诉你们其中一个改动点,就是controller的get()的@PreAuthorize(“hasRole(‘admin’)”)注解改为@PreAuthorize(“hasRole(‘admin’) and hasAuthority(‘read’)”)

Xie Kun wechat
觉得不错,请喝奶茶吧😃