创建资源服务器

概述

在 为什么需要 oAuth2 和 RBAC 基于角色的权限控制 章节,我们介绍过资源的概念,简单点说就是需要被访问的业务数据或是静态资源文件都可以被称作资源。

为了让大家更好的理解资源服务器的概念,我们单独创建一个名为 spring-security-oauth2-resource 资源服务器的项目,该项目的主要目的就是对数据表的 CRUD 操作,而这些操作就是对资源的操作了。

操作流程

img

  • 初始化资源服务器数据库
  • POM 所需依赖同认证服务器
  • 配置资源服务器
  • 配置资源(Controller)

POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>ml.yompc</groupId>
        <artifactId>hello-spring-security-oauth2</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <artifactId>hello-spring-security-oauth2-resource</artifactId>
    <url>http://www.funtl.com</url>

    <licenses>
        <license>
            <name>Apache 2.0</name>
            <url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
        </license>
    </licenses>


    <dependencies>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>ml.yompc.spring.security.oauth2.resource.Oath2ResourceApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

关键步骤

#配置资源服务器

创建一个类继承 ResourceServerConfigurerAdapter 并添加相关注解:

  • @Configuration

  • @EnableResourceServer:资源服务器

  • @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true):全局方法拦截

    package ml.yompc.spring.security.oauth2.resource.configura;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
    
    /**
     * @email yom535@outlook.com
     * @author: 有民(yom535)
     * @date: 2019/8/22
     * @time: 0:18
     */
    @Configuration
    @EnableResourceServer
    @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
    public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                    .exceptionHandling()
                    .and()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    // 以下为配置所需保护的资源路径及权限,需要与认证服务器配置的授权部分对应
                    .antMatchers("/").hasAuthority("SystemContent")
                    .antMatchers("/view/**").hasAuthority("SystemContentView");
    
        }
    }
    
    

    Application

    package ml.yompc.spring.security.oauth2.resource;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import tk.mybatis.spring.annotation.MapperScan;
    
    /**
     * @email yom535@outlook.com
     * @author: 有民(yom535)
     * @date: 2019/8/22
     * @time: 0:15
     */
    @SpringBootApplication
    @MapperScan(basePackages = "ml.yompc.spring.security.oauth2.resource.mapper")
    public class Oath2ResourceApplication {
        public static void main(String[] args) {
            SpringApplication.run(Oath2ResourceApplication.class,args);
        }
    }
    
    

    工具类ResponseResult

    package ml.yompc.spring.security.oauth2.resource.dto;
    import lombok.Data;
    
    import java.io.Serializable;
    /**
     * @email yom535@outlook.com
     * @author: 有民(yom535)
     * @date: 2019/8/22
     * @time: 0:47
     */
    @Data
    public class ResponseResult<T> implements Serializable {
    
        private static final long serialVersionUID = 3468352004150968551L;
    
        /**
         * 状态码
         */
        private Integer code;
    
        /**
         * 消息
         */
        private String message;
    
        /**
         * 返回对象
         */
        private T data;
    
        public ResponseResult() {
            super();
        }
    
        public ResponseResult(Integer code) {
            super();
            this.code = code;
        }
    
        public ResponseResult(Integer code, String message) {
            super();
            this.code = code;
            this.message = message;
        }
    
        public ResponseResult(Integer code, Throwable throwable) {
            super();
            this.code = code;
            this.message = throwable.getMessage();
        }
    
        public ResponseResult(Integer code, T data) {
            super();
            this.code = code;
            this.data = data;
        }
    
        public ResponseResult(Integer code, String message, T data) {
            super();
            this.code = code;
            this.message = message;
            this.data = data;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((data == null) ? 0 : data.hashCode());
            result = prime * result + ((message == null) ? 0 : message.hashCode());
            result = prime * result + ((code == null) ? 0 : code.hashCode());
            return result;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            ResponseResult<?> other = (ResponseResult<?>) obj;
            if (data == null) {
                if (other.data != null) {
                    return false;
                }
            } else if (!data.equals(other.data)) {
                return false;
            }
            if (message == null) {
                if (other.message != null) {
                    return false;
                }
            } else if (!message.equals(other.message)) {
                return false;
            }
            if (code == null) {
                if (other.code != null) {
                    return false;
                }
            } else if (!code.equals(other.code)) {
                return false;
            }
            return true;
        }
       
    }
    
    

    application.yml

    spring:
      application:
        name: oauth2-resource
      datasource:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://database-1.c0kagetmcysh.us-east-1.rds.amazonaws.com:3306/myshop?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: 12345678
        hikari:
          minimum-idle: 5
          idle-timeout: 600000
          maximum-pool-size: 10
          auto-commit: true
          pool-name: MyHikariCP
          max-lifetime: 1800000
          connection-timeout: 30000
          connection-test-query: SELECT 1
    
    security:
      oauth2:
        client:
          client-id: client
          client-secret: secret
          access-token-uri: http://localhost:8080/oauth/token
          user-authorization-uri: http://localhost:8080/oauth/authorize
        resource:
          token-info-uri: http://localhost:8080/oauth/check_token
    
    server:
      port: 8081
      servlet:
        context-path: /contents
    
    mybatis:
      type-aliases-package: ml.yompc.spring.security.oauth2.resource.domain
      mapper-locations: classpath:mapper/*.xml
    
    logging:
      level:
        root: INFO
        org.springframework.web: INFO
        org.springframework.security: INFO
        org.springframework.security.oauth2: INFO
    
    

    在服务器端配置取消拦截地址

     @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/oauth/check_token");
        }
    

    完整

    package ml.yompc.spring.security.oauth2.server.configure;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    
    /**
     * 认证
     * @email yom535@outlook.com
     * @author: 有民(yom535)
     * @date: 2019/8/21
     * @time: 15:41
     */
    
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
    @EnableWebSecurity
    public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Bean
        public BCryptPasswordEncoder passwordEncoder(){
            //加密
            return new BCryptPasswordEncoder();
    
        }
    
        @Override
        @Bean
        public UserDetailsService userDetailsService(){
            return new UserDetailsServiceImpl();
        }
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            web.ignoring().antMatchers("/oauth/check_token");
        }
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
            auth.userDetailsService(userDetailsService());
        }
    }
    
    

    访问资源

    #访问获取授权码

    打开浏览器,输入地址:

    http://localhost:8080/oauth/authorize?client_id=client&response_type=code
    

    第一次访问会跳转到登录页面

    img

    验证成功后会询问用户是否授权客户端

    img

    选择授权后会跳转到我的博客,浏览器地址上还会包含一个授权码(code=1JuO6V),浏览器地址栏会显示如下地址:

    http://www.yompc.ml/?code=1JuO6V
    

    有了这个授权码就可以获取访问令牌了

    通过授权码向服务器申请令牌

    通过 CURL 或是 Postman 请求

    curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=1JuO6V' "http://client:secret@localhost:8080/oauth/token"
    

    img

    得到响应结果如下:

    {
        "access_token": "016d8d4a-dd6e-4493-b590-5f072923c413",
        "token_type": "bearer",
        "expires_in": 43199,
        "scope": "app"
    }
    

    携带令牌访问资源服务器

    此处以获取全部资源为例,其它请求方式一样,可以参考我源码中的单元测试代码。可以使用以下方式请求:

    • 使用 Headers 方式:需要在请求头增加 Authorization: Bearer yourAccessToken
    • 直接请求带参数方式:http://localhost:8081/contents?access_token=yourAccessToken

    使用 Headers 方式,通过 CURL 或是 Postman 请求

    curl --location --request GET "http://localhost:8081/contents" --header "Content-Type: application/json" --header "Authorization: Bearer yourAccessToken"
    

    img