Spring Cloudspring bootspring-cloud

Spring Security Oauth2-授权码模式(Fin

2018-10-30  本文已影响9人  AaronSimon

一、授权码模式原理解析(来自理解OAuth 2.0)

授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。其具体的流程如下:


授权码模式流程图授权码模式流程图

具体步骤:

在步骤A中客户端告知浏览器重定向到授权服务器的URI包含以下参数:

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

在C步骤中授权服务器回应客户端的URI,包含以下参数:

HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz

在D步骤中客户端向授权服务器申请令牌的HTTP请求,包含以下参数:

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&client_id=s6BhdRkqt3
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

在E步骤中授权服务器发送的HTTP回复,包含以下参数:

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
    "access_token":"2YotnFZFEjr1zCsicMWpAA",
    "token_type":"example",
    "expires_in":3600,
    "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
    "example_parameter":"example_value"
}

更新令牌

如果用户访问的时候,客户端的访问令牌access_token已经过期,则需要使用更新令牌refresh_token申请一个新的访问令牌。
客户端发出更新令牌的HTTP请求,包含以下参数:

二、授权码示例

示例代码包含授权服务和资源服务

服务名 端口号 说明
auth-server 8080 授权服务器
resource-server 8088 资源服务器

2.1 授权服务器

2.1.1 添加依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-security</artifactId>
</dependency>

2.1.2 授权服务配置

/**
 * 授权服务器配置
 *
 * @author simon
 * @create 2018-10-29 11:51
 **/
@Configuration
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

  @Autowired
  private AuthenticationManager authenticationManager;

  @Autowired
  UserDetailsService userDetailsService;

  // 使用最基本的InMemoryTokenStore生成token
  @Bean
  public TokenStore memoryTokenStore() {
    return new InMemoryTokenStore();
  }

  /**
   * 配置客户端详情服务
   * 客户端详细信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
   * @param clients
   * @throws Exception
   */
  @Override
  public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    clients.inMemory()
            .withClient("client1")//用于标识用户ID
            .authorizedGrantTypes("authorization_code","refresh_token")//授权方式
            .scopes("test")//授权范围
            .secret(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("123456"));//客户端安全码,secret密码配置从 Spring Security 5.0开始必须以 {bcrypt}+加密后的密码 这种格式填写;
  }

  /**
   * 用来配置令牌端点(Token Endpoint)的安全约束.
   * @param security
   * @throws Exception
   */
  @Override
  public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    /* 配置token获取合验证时的策略 */
    security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
  }

  /**
   * 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
   * @param endpoints
   * @throws Exception
   */
  @Override
  public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    // 配置tokenStore,需要配置userDetailsService,否则refresh_token会报错
    endpoints.authenticationManager(authenticationManager).tokenStore(memoryTokenStore()).userDetailsService(userDetailsService);
  }
}

2.1.3 spring security配置

/**
 * 配置spring security
 *
 * @author simon
 * @create 2018-10-29 16:25
 **/
@EnableWebSecurity//开启权限验证
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  /**
   * 配置这个bean会在做AuthorizationServerConfigurer配置的时候使用
   * @return
   * @throws Exception
   */
  @Bean
  @Override
  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
  }

  /**
   * 配置用户
   * 使用内存中的用户,实际项目中,一般使用的是数据库保存用户,具体的实现类可以使用JdbcDaoImpl或者JdbcUserDetailsManager
   * @return
   */
  @Bean
  @Override
  protected UserDetailsService userDetailsService() {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(User.withUsername("admin").password(PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("admin")).authorities("USER").build());
    return manager;
  }

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

2.1.4 开启授权服务

在启动类上添加注解@EnableAuthorizationServer开启授权服务

2.2 资源服务

2.2.1 添加依赖

<?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>

  <groupId>priv.simon.resource</groupId>
  <artifactId>resource-server</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>resource-server</name>
  <description>资源服务器</description>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.6.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <spring-cloud.version>Finchley.SR2</spring-cloud.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

2.2.2 配置资源服务

auth-server-url: http://localhost:8080 # 授权服务地址

server:
  port: 8088
security:
  oauth2:
    client:
      client-id: client1
      client-secret: 123456
      scope: test
      access-token-uri: ${auth-server-url}/oauth/token
      user-authorization-uri: ${auth-server-url}/oauth/authorize
    resource:
      token-info-uri: ${auth-server-url}/oauth/check_token #检查令牌

2.2.3 开启资源服务

在启动类上新增注解@EnableResourceServer开启资源服务,并提供资源获取接口


@EnableResourceServer
@RestController
@SpringBootApplication
public class ResourceServerApplication {

  private static final Logger log = LoggerFactory.getLogger(ResourceServerApplication.class);

  public static void main(String[] args) {
    SpringApplication.run(ResourceServerApplication.class, args);
  }

  @GetMapping("/user")
  public Authentication getUser(Authentication authentication) {
    log.info("resource: user {}", authentication);
    return authentication;
  }
}

2.3 测试

1. 获取授权码

发送GET请求获取授权码,回调地址随意写就可以

http://localhost:8080/oauth/authorize?response_type=code&client_id=client1&redirect_uri=http://baidu.com

如果没有登录,浏览器会重定向到登录界面

登录界面登录界面
输入用户名和密码(admin/admin)点击登录,这时会进入授权页
授权页面授权页面
点击授权,浏览器会从定向到回调地址上,携带code参数
授权成功授权成功
2. 获取令牌

通过post请求获取令牌


postman请求postman请求

请求失败,报401 authentication is required的错误

经过研究发现:
/oauth/token端点:

那么授权服务器配置修改如下:

/**
   * 用来配置令牌端点(Token Endpoint)的安全约束.
   * @param security
   * @throws Exception
   */
  @Override
  public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    /* 配置token获取合验证时的策略 */
    security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()").allowFormAuthenticationForClients();
  }

重新发送请求


postman请求postman请求

返回数据如下:

{
    "access_token": "c0fa303f-77ad-427b-8f50-c5b3e031d109",
    "token_type": "bearer",
    "refresh_token": "a9b4a4c5-4e27-4328-ae1b-91cdd7c85f15",
    "expires_in": 43199,
    "scope": "test"
}
3. 从资源服务获取资源

携带access_token参数请求资源

http://localhost:8088/user?access_token=c0fa303f-77ad-427b-8f50-c5b3e031d109

结果返回:

{
    "authorities": [
        {
            "authority": "ROLE_test"
        }
    ],
    "details": {
        "remoteAddress": "0:0:0:0:0:0:0:1",
        "sessionId": null,
        "tokenValue": "c0fa303f-77ad-427b-8f50-c5b3e031d109",
        "tokenType": "Bearer",
        "decodedDetails": null
    },
    "authenticated": true,
    "userAuthentication": {
        "authorities": [
            {
                "authority": "ROLE_test"
            }
        ],
        "details": null,
        "authenticated": true,
        "principal": "admin",
        "credentials": "N/A",
        "name": "admin"
    },
    "principal": "admin",
    "credentials": "",
    "oauth2Request": {
        "clientId": "client1",
        "scope": [
            "test"
        ],
        "requestParameters": {
            "client_id": "client1"
        },
        "resourceIds": [],
        "authorities": [],
        "approved": true,
        "refresh": false,
        "redirectUri": null,
        "responseTypes": [],
        "extensions": {},
        "refreshTokenRequest": null,
        "grantType": null
    },
    "clientOnly": false,
    "name": "admin"
}
4. 刷新token
刷新token刷新token

github下载源码

上一篇下一篇

猜你喜欢

热点阅读