<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>yellow</title>
    <description>做一些技术笔记、分享和讨论</description>
    <link>https://mtide.net/</link>
    <atom:link href="https://mtide.net/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sat, 12 Jul 2025 09:19:05 +0000</pubDate>
    <lastBuildDate>Sat, 12 Jul 2025 09:19:05 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Spring OAuth2 开发指南（二）：OAuth2 密码模式开发实例</title>
        <description>&lt;p&gt;[TOC]&lt;/p&gt;

&lt;h2 id=&quot;一开篇&quot;&gt;一、开篇&lt;/h2&gt;

&lt;p&gt;本篇是《Spring OAuth2 开发指南》系列文章的第二篇，通过代码实例详细介绍 OAuth2 密码模式的开发细节。网络上关于 OAuth2 开发的代码示范十分多而且杂乱，基本上都是官方手册的摘录搬运，或者过多地受制于框架本身如 Spring Security，约束太多，缺乏系统性，容易造成同学们云里雾里，以至于生搬硬套。&lt;/p&gt;

&lt;p&gt;本人主张在开发落地过程中，既不能完全自己造轮子，也不应完全依赖轮子，应该从本质出发，在理清技术原理和细节的条件下，选择适合的方法。从这个原则出发，本文将根据“密码模式的典型架构层次和主要流程”（见《Spring OAuth2 开发指南（一）》）中描述的流程节点，展示其代码实现。另外，文章的要点在于后半部分，提出了资源服务器端鉴权/权限控制，和授权服务器端鉴权/权限控制两种实现方法。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;需要注意的是 password 模式由于 OAuth2.1 不推荐使用所以只提供旧的组件代码版本，具体请参见 https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-02&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;二-演示案例&quot;&gt;二、 演示案例&lt;/h2&gt;

&lt;p&gt;我们继续用相册预览系统（PAPS，Photo Album Preview System）作为演示案例。&lt;/p&gt;

&lt;p&gt;PAPS 是一个社交平台的子系统，与 IBCS 类似，采用 RESTful API 对外交互，主要功能是允许用户预览自己的相册，以下是 PAPS 演示项目的必要服务：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;服务名&lt;/th&gt;
      &lt;th&gt;类别&lt;/th&gt;
      &lt;th&gt;描述&lt;/th&gt;
      &lt;th&gt;技术选型&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;photo-service&lt;/td&gt;
      &lt;td&gt;内部服务&lt;/td&gt;
      &lt;td&gt;资源服务器角色，相册预览服务&lt;/td&gt;
      &lt;td&gt;Spring Boot 开发的 RESTful 服务&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;idp&lt;/td&gt;
      &lt;td&gt;内部服务&lt;/td&gt;
      &lt;td&gt;授权服务器角色，具体指负责认证、授权和鉴权&lt;/td&gt;
      &lt;td&gt;Spring Boot 开发&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;demo-h5&lt;/td&gt;
      &lt;td&gt;外部应用&lt;/td&gt;
      &lt;td&gt;demo 应用的前端&lt;/td&gt;
      &lt;td&gt;使用 Postman 代替&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;为此，我们将搭建两个工程项目：photo-service 和 idp，客户端用 Postman 代替。&lt;/p&gt;

&lt;h2 id=&quot;三-工程结构&quot;&gt;三、 工程结构&lt;/h2&gt;

&lt;p&gt;接下来演示两个工程项目的框架代码，这部分代码包含工程的框架结构、Spring Security 和 OAuth2 的基础配置，尽量采用最精简的方式书写。其他项目可以 copy 这部分代码作为基础模板使用。&lt;/p&gt;

&lt;h3 id=&quot;photo-service-相册服务&quot;&gt;photo-service 相册服务&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;基础工程结构&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;src/main
    java
        com.example.demophoto
            config
                oauth2
                    CheckTokenAuthentication.java
                    CheckTokenFilter.java
                    CustomPermissionEvaluator.java
                    CustomRemoteTokenServices.java
                    ResourceServerConfigurer.java
            service
                PermisionEvaluatingService.java
            web
                PhotoController.java
            DemoPhotoApplication.java
    resources
        applicaton.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;pom.xml&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
         xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&amp;gt;
    &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;
    &amp;lt;parent&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-parent&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;2.3.4.RELEASE&amp;lt;/version&amp;gt;
        &amp;lt;relativePath/&amp;gt; &amp;lt;!-- lookup parent from repository --&amp;gt;
    &amp;lt;/parent&amp;gt;
    &amp;lt;groupId&amp;gt;com.example&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;oauth2-demo-1a-photo-service&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;0.0.1-SNAPSHOT&amp;lt;/version&amp;gt;
    &amp;lt;name&amp;gt;oauth2-demo-1a-photo-service&amp;lt;/name&amp;gt;
    &amp;lt;description&amp;gt;oauth2-demo-1a-photo-service&amp;lt;/description&amp;gt;
    &amp;lt;properties&amp;gt;
        &amp;lt;java.version&amp;gt;1.8&amp;lt;/java.version&amp;gt;
    &amp;lt;/properties&amp;gt;

    &amp;lt;dependencies&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-boot-starter&amp;lt;/artifactId&amp;gt;
        &amp;lt;/dependency&amp;gt;

        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;
        &amp;lt;/dependency&amp;gt;

        &amp;lt;!-- https://mvnrepository.com/artifact/org.springframework.security.oauth.boot/spring-security-oauth2-autoconfigure --&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.springframework.security.oauth.boot&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-security-oauth2-autoconfigure&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;2.1.2.RELEASE&amp;lt;/version&amp;gt;
        &amp;lt;/dependency&amp;gt;
    &amp;lt;/dependencies&amp;gt;

    &amp;lt;build&amp;gt;
        &amp;lt;plugins&amp;gt;
            &amp;lt;plugin&amp;gt;
                &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
                &amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;
            &amp;lt;/plugin&amp;gt;
        &amp;lt;/plugins&amp;gt;
    &amp;lt;/build&amp;gt;

&amp;lt;/project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;applicaton.yaml&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server:
  port: 8010

security:
  oauth2:
    client:
      clientId: client2
      clientSecret: client2p
    resource:
      tokenInfoUri: http://127.0.0.1:8000/oauth/check_token
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;ResourceServerConfigurer.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.demophoto.config.oauth2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.config.annotation.web.builders.HttpSecurity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableResourceServer&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceServerConfigurer&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceServerConfigurerAdapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/**
     * spring-security-oauth2 组件一般性配置
     *
     * @param resources
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ResourceServerSecurityConfigurer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;resourceId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;demo-1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * spring-security-oauth2 组件一般性配置
     *
     * @param http
     * @throws Exception
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpSecurity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authorizeRequests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;anyRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authenticated&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;idp-授权服务&quot;&gt;idp 授权服务&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;基础工程结构&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;src/main
    java
        com.example.demoidp
            config
                oauth2
                    AuthorizationServerConfigurer.java
                    CheckTokenInterceptor.java
                    WebSecurityConfig.java
            service
                业务逻辑，如鉴权逻辑
            DemoIdpApplication.java
    resources
        applicaton.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;pom.xml&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
         xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&amp;gt;
    &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;
    &amp;lt;parent&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-starter-parent&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;2.3.4.RELEASE&amp;lt;/version&amp;gt;
        &amp;lt;relativePath/&amp;gt; &amp;lt;!-- lookup parent from repository --&amp;gt;
    &amp;lt;/parent&amp;gt;
    &amp;lt;groupId&amp;gt;com.example&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;oauth2-demo-1a-idp&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;0.0.1-SNAPSHOT&amp;lt;/version&amp;gt;
    &amp;lt;name&amp;gt;oauth2-demo-1a-idp&amp;lt;/name&amp;gt;
    &amp;lt;description&amp;gt;oauth2-demo-1a-idp&amp;lt;/description&amp;gt;
    &amp;lt;properties&amp;gt;
        &amp;lt;java.version&amp;gt;1.8&amp;lt;/java.version&amp;gt;
    &amp;lt;/properties&amp;gt;

    &amp;lt;dependencies&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-boot-starter&amp;lt;/artifactId&amp;gt;
        &amp;lt;/dependency&amp;gt;

        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;
        &amp;lt;/dependency&amp;gt;

        &amp;lt;!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 --&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.springframework.security.oauth&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-security-oauth2&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;2.3.8.RELEASE&amp;lt;/version&amp;gt;
        &amp;lt;/dependency&amp;gt;
    &amp;lt;/dependencies&amp;gt;

    &amp;lt;build&amp;gt;
        &amp;lt;plugins&amp;gt;
            &amp;lt;plugin&amp;gt;
                &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
                &amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;
            &amp;lt;/plugin&amp;gt;
        &amp;lt;/plugins&amp;gt;
    &amp;lt;/build&amp;gt;

&amp;lt;/project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;applicaton.yaml&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server:
  port: 8000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;AuthorizationServerConfigurer.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.demoidp.config.oauth2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.beans.factory.annotation.Autowired&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.authentication.AuthenticationManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.crypto.factory.PasswordEncoderFactories&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.crypto.password.PasswordEncoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableAuthorizationServer&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthorizationServerConfigurer&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthorizationServerConfigurerAdapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthenticationManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authenticationManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * spring-security-oauth2 组件一般性配置
     *
     * @param authenticationManager
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AuthorizationServerConfigurer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AuthenticationManager&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authenticationManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authenticationManager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authenticationManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 配置密码加密方法
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;PasswordEncoder&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;passwordEncoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PasswordEncoderFactories&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createDelegatingPasswordEncoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * spring-security-oauth2 组件一般性配置
     *
     * @param endpoints
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AuthorizationServerEndpointsConfigurer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authenticationManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authenticationManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * spring-security-oauth2 组件一般性配置
     *
     * @param security
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AuthorizationServerSecurityConfigurer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;security&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;security&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// /oauth/check_token 请求放行&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;checkTokenAccess&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;permitAll()&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;passwordEncoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;passwordEncoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;WebSecurityConfig.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package com.example.demoidp.config.oauth2;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * spring-security-oauth2 组件一般性配置
     *
     * @return AuthenticationManager
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;四-代码实现&quot;&gt;四、 代码实现&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;https://mtide.net/assets/images/post/20210809/1.png&quot; alt=&quot;OAuth2 密码模式典型架构层次&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如图所示，是密码模式的最精简架构层次和主要流程。下面我们逐步实现该流程：&lt;/p&gt;

&lt;h4 id=&quot;一第一阶段认证授权阶段&quot;&gt;一）第一阶段：认证授权阶段&lt;/h4&gt;

&lt;h5 id=&quot;1用户代理demo-h5将用户输入的用户名和密码发送给客户端demo-service&quot;&gt;1）用户代理（demo-h5）将用户输入的用户名和密码，发送给客户端（demo-service）&lt;/h5&gt;

&lt;p&gt;此步骤我们使用 Postman 执行，这里不展开介绍。&lt;/p&gt;

&lt;h5 id=&quot;2客户端demo-service将用户输入的用户名和密码连同-client_id--client_secret-由-idp-分配一起发送到-idp-以请求令牌如果-idp-约定了-scope-则还需要带上-scope-参数&quot;&gt;2）客户端（demo-service）将用户输入的用户名和密码，连同 client_id + client_secret (由 idp 分配）一起发送到 idp 以请求令牌，如果 idp 约定了 scope 则还需要带上 scope 参数&lt;/h5&gt;

&lt;p&gt;此步骤我们使用 Postman 执行，这里不展开介绍。需要注意的是，Postman 在这里仍然是一个 client 角色，client_id 代表的是它自己。请求的 URL 为：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;POST http://127.0.0.1:8000/oauth/token
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;3idp-首先验证-client_id--client_secret--的合法性再检查-scope-是否无误最后验证用户名和密码是否正确正确则生成-token这一步也叫认证&quot;&gt;3）idp 首先验证 client_id + client_secret  的合法性，再检查 scope 是否无误，最后验证用户名和密码是否正确，正确则生成 token。这一步也叫“认证”&lt;/h5&gt;

&lt;p&gt;为了实现这个步骤，我们在 idp 工程的 AuthorizationServerConfigurer 类中加入以下代码：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;首先是 client_id + client_secret + scope 的校验&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableAuthorizationServer&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthorizationServerConfigurer&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthorizationServerConfigurerAdapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 3. [密码模式的典型架构层次和主要流程] 中的第 3 步：
     *    idp 首先验证 client_id + client_secret 的合法性，再检查 scope 是否无误
     *
     *    PS: 这里为演示方便，就地创建了账号，生产环境应自行替换成数据库查询等方式
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MockJDBCClientDetailsService&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ClientDetailsService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ClientDetails&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loadClientByClientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ClientRegistrationException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;cm&quot;&gt;/**
             * GrantedAuthority 与 hasAuthority() 关联
             */&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;GrantedAuthority&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authorities&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashSet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;authorities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SimpleGrantedAuthority&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;READ&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;authorities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SimpleGrantedAuthority&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;WRITE&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    
            &lt;span class=&quot;nc&quot;&gt;BaseClientDetails&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;details1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BaseClientDetails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setClientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setClientSecret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;passwordEncoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client1p&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAuthorizedGrantTypes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setScope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;resource:write&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;resource:read&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setResourceIds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;demo-1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAuthorities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authorities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    
            &lt;span class=&quot;nc&quot;&gt;BaseClientDetails&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;details2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BaseClientDetails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setClientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setClientSecret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;passwordEncoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client2p&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAuthorizedGrantTypes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client_credentials&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setScope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;resource:write&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;resource:read&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setResourceIds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;demo-1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAuthorities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authorities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    
            &lt;span class=&quot;nc&quot;&gt;BaseClientDetails&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;details3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BaseClientDetails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setClientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client3&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setClientSecret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;passwordEncoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client3p&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAuthorizedGrantTypes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setScope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;resource:write&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;resource:read&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setResourceIds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;demo-1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;details3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAuthorities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authorities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    
            &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ClientDetails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clients&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;clients&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;details1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;clients&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;details2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;clients&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client3&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;details3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clients&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ClientRegistrationException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Client not found&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clients&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;cm&quot;&gt;/**
     * spring-security-oauth2 组件一般性配置
     * 配置自定义 ClientDetails
     *
     * @param clients
     * @throws Exception
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ClientDetailsServiceConfigurer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clients&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;clients&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withClientDetails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MockJDBCClientDetailsService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;然后是用户名和密码的校验&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableWebSecurity&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WebSecurityConfig&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WebSecurityConfigurerAdapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cm&quot;&gt;/**
     * 3. [密码模式的典型架构层次和主要流程] 中的第 3 步：
     *    验证用户名和密码是否正确，正确则生成 token
     *
     *    PS: 这里为演示方便，就地创建了账号，生产环境应自行替换成数据库查询等方式
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MockJDBCUserDeatilsService&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserDetailsService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserDetails&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loadUserByUsername&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UsernameNotFoundException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;pwd1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;pwd2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;UsernameNotFoundException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;User not found&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;withDefaultPasswordEncoder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;roles&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;USER&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserDetailsService&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;userDetailsService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MockJDBCUserDeatilsService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;当 client_id + client_secret + scope，以及用户名和密码都校验通过后，spring-security-oauth2 会调用合适的 tokenServices 生成 token。有兴趣的同学可以自行查阅源代码追踪整个过程，这里介绍源码追踪的入口方法：&lt;/p&gt;

&lt;p&gt;我们知道 demo-h5 客户端（Postman）首先向 http://127.0.0.1:8000/oauth/token 发起请求，因此我们找到 spring-security-oauth2 组件源码中的 /oauth/token 端点，具体路径为：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;4idp-返回认证结果给客户端认证通过返回-token认证失败返回-401如果认证成功则此步骤也叫授权&quot;&gt;4）idp 返回认证结果给客户端，认证通过返回 token，认证失败返回 401。如果认证成功则此步骤也叫“授权”&lt;/h5&gt;

&lt;p&gt;这一步 spring-security-oauth2 已经为我们处理好了，不需要额外处理。想要追踪源码过程的同学，可参考上一步骤介绍的入口方法。&lt;/p&gt;

&lt;h5 id=&quot;5客户端收到-token-后进行暂存并创建对应的-session&quot;&gt;5）客户端收到 token 后进行暂存，并创建对应的 session&lt;/h5&gt;

&lt;p&gt;这个步骤通过 Postman 演示（直接复制返回的 token 字符串即可），这里不展开介绍。&lt;/p&gt;

&lt;h5 id=&quot;6客户端颁发-cookie-给用户代理浏览器&quot;&gt;6）客户端颁发 cookie 给用户代理/浏览器&lt;/h5&gt;

&lt;p&gt;这个步骤通过 Postman 演示，这里不展开介绍。&lt;/p&gt;

&lt;h4 id=&quot;二第二阶段授权后请求资源阶段&quot;&gt;二）第二阶段：授权后请求资源阶段&lt;/h4&gt;

&lt;h5 id=&quot;7用户通过用户代理demo-h5访问我的相册页面用户代理携带-cookie-向客户端demoservice发起请求&quot;&gt;7）用户通过用户代理（demo-h5）访问“我的相册”页面，用户代理携带 cookie 向客户端（demo—service）发起请求&lt;/h5&gt;

&lt;p&gt;此步骤使用 Postman 执行，不展开叙述。&lt;/p&gt;

&lt;h5 id=&quot;8客户端通过-session-找到对应的-token携带此-token-向资源服务器photo-service发起请求&quot;&gt;8）客户端通过 session 找到对应的 token，携带此 token 向资源服务器（photo-service）发起请求&lt;/h5&gt;

&lt;p&gt;此步骤使用 Postman 执行，我们将第 5) 步获取的 token 作为 Bearer Token，向 photo-service 发起请求，请求的 URL 为：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET http://127.0.0.1:8010/api/photo

该请求只需要携带 token 即可，不需要其他参数
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;9资源服务器photo-service向-idp-请求验证-token-有效性&quot;&gt;9）资源服务器（photo-service）向 idp 请求验证 token 有效性&lt;/h5&gt;

&lt;p&gt;在介绍如何处理请求前，我们先在 photo-service 工程中新增相关代码：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;PhotoController.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.demophoto.web&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.bind.annotation.GetMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.bind.annotation.RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.bind.annotation.RestController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api/&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PhotoController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/photo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetchPhoto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;GET photo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此外，还有几个关键配置：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;ResourceServerConfigurerAdapter.configure(HttpSecurity http) 方法配置了 http.authorizeRequests().anyRequest().authenticated() 使得所有请求都要先鉴权；&lt;/li&gt;
  &lt;li&gt;application.yaml 中配置了 client_id、client_secret 和 resource.tokenInfoUri，当资源服务接受到请求时，会携带 token 向 tokenInfoUri 指定的地址发起鉴权请求。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;默认情况下，当 demo-h5 向 photo-service 发起资源访问的请求时，photo-service 会将获取的 token 发到 idp 进行校验，在这个过程中 spring-security-oauth2 不会对 scope 做任何处理。我们知道 scope 是用来约束 client 的权限范围的，因此 scope 权限检查（也视为鉴权的工作之一）这个工作需要自己编码实现。&lt;/p&gt;

&lt;p&gt;通常来说，scope 权限检查的业务逻辑可以灵活设定，甚至可以忽略它。本文介绍两种 scope 检查的实现方法：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;资源服务器端检查；&lt;/li&gt;
  &lt;li&gt;授权服务器端检查。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;接下来的第 10) 步将拆分成两种方式，分别对此进行介绍。&lt;/p&gt;

&lt;h5 id=&quot;10方式一资源服务器端-scope-检查-idp-校验-token-有效性资源服务器校验-scope&quot;&gt;10）【方式一：资源服务器端 scope 检查】 idp 校验 token 有效性，资源服务器校验 scope&lt;/h5&gt;

&lt;p&gt;idp 校验 token 有效性，通过则返回 client 相关信息（包含 scope ）给 photo-service，photo-service 再根据 scope 判断客户端（demo-h5）是否有权限调用此 API，如通过检查则继续下一步骤，否则返回 403 错误给 demo-h5。这一步也叫“鉴权”&lt;/p&gt;

&lt;p&gt;我们在 photo-service 工程中添加以下代码：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ResourceServerConfigurer.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableResourceServer&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceServerConfigurer&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceServerConfigurerAdapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpSecurity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authorizeRequests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;antMatchers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api/photo/**&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#oauth2.hasScope(&apos;resource:read&apos;)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;antMatchers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api/photo2/**&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#oauth2.hasScope(&apos;resource:read&apos;)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;antMatchers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api/photo3/**&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#oauth2.hasScope(&apos;resource:write&apos;)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;anyRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authenticated&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过 access(“#oauth2.hasScope(‘resource:write’)”) 方法可以实现资源服务器端的 scope 检查。其主要流程为：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;photo-service 收到客户端请求后，将获取到的 token 发往 idp 校验；&lt;/li&gt;
  &lt;li&gt;idp 校验通过后，将 clientDetails 信息返回给 photo-service，其中就包括 scope 参数；&lt;/li&gt;
  &lt;li&gt;photo-service 拿到 scope 后，根据 access(“#oauth2.hasScope(‘resource:write’)”) 判断该请求是否在 scope 范围内。&lt;/li&gt;
&lt;/ol&gt;

&lt;h5 id=&quot;10方式二idp-端-scope-检查-idp-校验-token--scope-有效性&quot;&gt;10）【方式二：idp 端 scope 检查】 idp 校验 token + scope 有效性&lt;/h5&gt;

&lt;p&gt;idp 校验 token 有效性，再根据 scope 判断客户端（demo-h5）是否有权限调用此 API，最后返回校验结果给资源服务器。由于 spring-security-oauth2 本身没有处理 scope 检查，且默认情况下，photo-service 向 idp 请求 token 鉴权时，并未携带任何其他请求信息，因此 idp 无法知道本次请求的细节，因此无法执行 socpe 检查。&lt;/p&gt;

&lt;p&gt;所以重点有两个：一是 photo-service 向 idp 请求 token 鉴权的同时如何携带请求的细节（比如访问的是什么资源？请求的是哪个API？）；二是如何拦截 token 鉴权过程使得 scope 校验失败是返回 403 错误？&lt;/p&gt;

&lt;p&gt;当然实现这个目的，有很多方法，本文采用了比较直观的方法：利用 Filter。&lt;/p&gt;

&lt;p&gt;我们在 photo-service 工程中添加以下代码：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ResourceServerConfigurer.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.demophoto.config.oauth2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.beans.factory.annotation.Autowired&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.config.annotation.web.builders.HttpSecurity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableResourceServer&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceServerConfigurer&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceServerConfigurerAdapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceServerProperties&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ResourceServerConfigurer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ResourceServerProperties&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 自定义 RemoteTokenServices 以取代资源服务器默认使用的
     * RemoteTokenServices 向 IDP 发起 /oauth/check_token 鉴权请求
     *
     * @return
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomRemoteTokenServices&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;customRemoteTokenServices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;CustomRemoteTokenServices&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomRemoteTokenServices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setCheckTokenEndpointUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getTokenInfoUri&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setClientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getClientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setClientSecret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getClientSecret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ResourceServerSecurityConfigurer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;resourceId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;demo-1&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;tokenServices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customRemoteTokenServices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpSecurity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addFilterBefore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CheckTokenFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AbstractPreAuthenticatedProcessingFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authorizeRequests&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;antMatchers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api/photo/**&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#oauth2.hasScope(&apos;resource:read&apos;)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;antMatchers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api/photo2/**&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#oauth2.hasScope(&apos;resource:read&apos;)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;antMatchers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api/photo3/**&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#oauth2.hasScope(&apos;resource:write&apos;)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;anyRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authenticated&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;CheckTokenFilter.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.demophoto.config.oauth2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.context.SecurityContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.context.SecurityContextHolder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;javax.servlet.*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;javax.servlet.http.HttpServletRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.io.IOException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/**
 * 在向 IDP 发起 /oauth/check_token 请求前，将请求细节存储到 SecurityContext 中，
 * 以便 CustomRemoteTokenServices.loadAuthentication() 可以获取到该请求细节
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CheckTokenFilter&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Filter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;doFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ServletRequest&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ServletResponse&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FilterChain&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;ServletException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;HttpServletRequest&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpServletRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getRequestURI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;cm&quot;&gt;/**
         * 仅处理 /api/**
         */&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api/&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;chain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;doFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;SecurityContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SecurityContextHolder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;CheckTokenAuthentication&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CheckTokenAuthentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAuthentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CheckTokenAuthentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;cm&quot;&gt;/**
         * 将用户代理或其他服务请求访问本资源服务器的细节（此处为 HTTP-Method + URI）
         * 存储到 SecurityContext 的 authentication 对象中
         */&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;details&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;details&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;uri&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;details&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setDetails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;details&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setAuthentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;chain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;doFilter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;CustomRemoteTokenServices.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.demophoto.config.oauth2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.commons.logging.Log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.apache.commons.logging.LogFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.http.*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.http.client.ClientHttpResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.access.AccessDeniedException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.Authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.AuthenticationException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.context.SecurityContextHolder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.crypto.codec.Base64&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.common.OAuth2AccessToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.common.exceptions.InvalidTokenException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.provider.OAuth2Authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.provider.token.AccessTokenConverter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.provider.token.ResourceServerTokenServices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.util.LinkedMultiValueMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.util.MultiValueMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.client.DefaultResponseErrorHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.client.RestOperations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.client.RestTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.io.IOException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.io.UnsupportedEncodingException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/**
 * 以 RemoteTokenServices 为模板
 * 基本思路是在向 IDP 发起 /oauth/check_token 的请求中，
 * 添加用户代理或其他服务请求访问本资源服务器的 API 的细节，
 * 以便 IDP 可以判断该用户代理或其他服务（即 client）是否可以调用此 API
 * &amp;lt;p&amp;gt;
 * （PS：也可以由 IDP 返回 ClientDetails 给资源服务，由资源服务处理放行逻辑）
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomRemoteTokenServices&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceServerTokenServices&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Log&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LogFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RestOperations&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;restTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;checkTokenEndpointUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientSecret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;token&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 与 IDP 约定的存储 API 请求细节的参数
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reqPayload&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;payload&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AccessTokenConverter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenConverter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DefaultAccessTokenConverter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomRemoteTokenServices&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;restTemplate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RestTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RestTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;restTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setErrorHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DefaultResponseErrorHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Ignore 400&lt;/span&gt;
            &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;handleError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ClientHttpResponse&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IOException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statusCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getRawStatusCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;statusCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;statusCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;401&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statusCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;403&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                        &lt;span class=&quot;nc&quot;&gt;HttpStatus&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpStatus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;statusCode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                        &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AccessDeniedException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
                    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
                    &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;handleError&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setRestTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;RestOperations&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;restTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;restTemplate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;restTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setCheckTokenEndpointUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;checkTokenEndpointUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;checkTokenEndpointUrl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;checkTokenEndpointUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setClientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;clientId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setClientSecret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientSecret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;clientSecret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientSecret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setAccessTokenConverter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AccessTokenConverter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accessTokenConverter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;tokenConverter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accessTokenConverter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setTokenName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;tokenName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 当使用自定义的 tokenServices 替换默认的 tokenServices 后，
     * 原来流程中的第 9 步就变成由该方法执行。
     *
     * 9. [密码模式的典型架构层次和主要流程] 中的第 9 步：
     * 资源服务器（photo-service）向 idp 请求验证 token 有效性
     *
     * @param accessToken
     * @return
     * @throws AuthenticationException
     * @throws InvalidTokenException
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2Authentication&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loadAuthentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthenticationException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;InvalidTokenException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authDetails&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

        &lt;span class=&quot;cm&quot;&gt;/**
         * 取得在 CheckTokenFilter 过滤器中置入的 API 请求细节
         */&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Authentication&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SecurityContextHolder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAuthentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;authDetails&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDetails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;MultiValueMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;formData&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LinkedMultiValueMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;formData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tokenName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authDetails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;formData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reqPayload&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authDetails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;method&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authDetails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;uri&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;HttpHeaders&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpHeaders&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getAuthorizationHeader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientSecret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;postForMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;checkTokenEndpointUrl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;formData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;cm&quot;&gt;/**
         * 11. [密码模式的典型架构层次和主要流程] 中的第 11 步：
         *     如果 token 校验失败则返回 401 给客户端，如果 scope 检查不通过则返回 403
         */&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isDebugEnabled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;check_token returned error: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;403&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;status&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;OAuth2AccessDeniedException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;InvalidTokenException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// gh-838&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;active&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;active&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;check_token returned active attribute: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;active&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;InvalidTokenException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenConverter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;extractAuthentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2AccessToken&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;readAccessToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;UnsupportedOperationException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Not supported: read access token&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getAuthorizationHeader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientSecret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clientId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientSecret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;warn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Null Client ID or Client Secret detected. Endpoint that requires authentication will reject request with 401 error.&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;creds&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%s:%s&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientSecret&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Basic &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Base64&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;creds&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBytes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)));&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;UnsupportedEncodingException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Could not convert String&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;postForMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MultiValueMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;formData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpHeaders&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getContentType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setContentType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MediaType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;APPLICATION_FORM_URLENCODED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;nd&quot;&gt;@SuppressWarnings&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;rawtypes&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;restTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;exchange&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;POST&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpEntity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MultiValueMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;formData&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBody&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;CheckTokenAuthentication.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.demophoto.config.oauth2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.authentication.AbstractAuthenticationToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.GrantedAuthority&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Collection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CheckTokenAuthentication&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AbstractAuthenticationToken&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * Creates a token with the supplied array of authorities.
     *
     * @param authorities the collection of &amp;lt;tt&amp;gt;GrantedAuthority&amp;lt;/tt&amp;gt;s for the principal
     *                    represented by this authentication object.
     */&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CheckTokenAuthentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Collection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;?&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GrantedAuthority&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authorities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authorities&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getCredentials&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getPrincipal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;接着在 idp 工程中添加以下代码：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;AuthorizationServerConfigurer.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableAuthorizationServer&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthorizationServerConfigurer&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AuthorizationServerConfigurerAdapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
    
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AuthorizationServerEndpointsConfigurer&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;authenticationManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authenticationManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

                &lt;span class=&quot;c1&quot;&gt;// 通过插入 interceptor 来实现自定义的鉴权方法&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CheckTokenInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getTokenStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;CheckTokenInterceptor.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.demoidp.config.oauth2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.access.AccessDeniedException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.provider.OAuth2Authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.provider.OAuth2Request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.oauth2.provider.token.TokenStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.util.StringUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.servlet.HandlerInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;javax.servlet.http.HttpServletRequest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;javax.servlet.http.HttpServletResponse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/**
 * /oauth/check_token 校验 token 请求拦截器
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CheckTokenInterceptor&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HandlerInterceptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TOKEN_NAME&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;token&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;TOKEN_INFO_URI&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/oauth/check_token&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenStore&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CheckTokenInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;TokenStore&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;tokenStore&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// for test only&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clientScopes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client1[resource:read]&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;GET /api/photo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client1[resource:write]&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;POST /api/photo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client2[resource:read]&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;GET /api/photo2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client2[resource:write]&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;POST /api/photo2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client3[resource:read]&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;GET /api/photo3&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;client3[resource:write]&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;POST /api/photo3&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 10. [密码模式的典型架构层次和主要流程] 中的第 10 步：
     *     idp 校验 token 有效性和 scope 权限
     * &amp;lt;p&amp;gt;
     * 即 IDP 根据 scope 判断客户端（demo-service）
     * 是否有权限调用此 API，最后返回校验结果给资源服务器
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;preHandle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpServletRequest&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpServletResponse&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getRequestURI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;cm&quot;&gt;/**
         * 仅拦截 /oauth/check_token
         */&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TOKEN_INFO_URI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;cm&quot;&gt;/**
         * payload 是 IDP 和资源服务器角色约定的传参格式
         * 即 client 请求访问资源服务器的 API 的细节
         * 可要求必须携带 payload
         *
         * 此部分可根据业务逻辑自行处理
         */&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;paylad&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;payload&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;StringUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isEmpty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;paylad&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AccessDeniedException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;insufficient_payload&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;GET /error&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;paylad&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;cm&quot;&gt;/**
         * 10. [密码模式的典型架构层次和主要流程] 中的第 10 步：
         * 【方式二：idp 端 scope 检查】 idp 校验 token + scope 有效性
         * 
         * 根据 token 查得 clientId，再根据 scope 检查该 client 是否有权限调用此 API
         * 此部分可根据业务逻辑自行处理，比如从数据库中查询 client、API 和 scope 的关系
         */&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getParameter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TOKEN_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;OAuth2Authentication&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oAuth2Authentication&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tokenStore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;readAuthentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;OAuth2Request&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oAuth2Request&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oAuth2Authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getOAuth2Request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scopeKey&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oAuth2Request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getClientId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oAuth2Request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getScope&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clientScopes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;containsKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scopeKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clientScopes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scopeKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;paylad&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AccessDeniedException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;insufficient_scope&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;idp 端的 scope 检查实现起来稍微麻烦点，其主要思路是：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;在 photo-service 向 idp 发起 /oauth/check_oauth 鉴权请求前，添加过滤器，将客户端的请求细节保存到某个全局对象中；&lt;/li&gt;
  &lt;li&gt;替换 photo-service 默认的 tokenServices，在向 idp 发起 /oauth/check_oauth 鉴权请求的过程中，将请求细节附加到请求中；&lt;/li&gt;
  &lt;li&gt;idp 在 AuthorizationServerEndpointsConfigurer 中添加自定义 Interceptor，在每次 check token 前先执行 自定义 Interceptor；&lt;/li&gt;
  &lt;li&gt;idp 在自定义 Interceptor 中取出请求细节，根据请求细节和 clientDetails 信息（scope），执行 scope 检查。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;以上方法，虽然实现麻烦，但是定制性和灵活性很强，不受框架约束，可以适应各种复杂的业务逻辑。&lt;/p&gt;

&lt;h5 id=&quot;11资源服务器根据-idp-检验结果truefalse-或其他等效手段决定是否返回用户相册数据给客户端如果-token-校验失败则返回-401-给客户端如果-scope-检查不通过则返回-403这一步也叫权限控制&quot;&gt;11）资源服务器根据 idp 检验结果（true/false 或其他等效手段）决定是否返回用户相册数据给客户端。如果 token 校验失败则返回 401 给客户端，如果 scope 检查不通过则返回 403。这一步也叫“权限控制”&lt;/h5&gt;

&lt;p&gt;与鉴权工作中的 scope 范围检查类似，实现权限控制的方法也有两种：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;授权服务器端的权限控制，属于集中式权限控制；&lt;/li&gt;
  &lt;li&gt;资源服务器端的权限控制，属于分散型权限控制。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;其中，授权服务器端的权限控制比较简单，在 idp 工程的 CheckTokenInterceptor.preHandle() 方法中添加权限控制的业务代码即可：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;CheckTokenInterceptor.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CheckTokenInterceptor&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HandlerInterceptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;preHandle&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;HttpServletRequest&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HttpServletResponse&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

        &lt;span class=&quot;cm&quot;&gt;/**
         * 11. [密码模式的典型架构层次和主要流程] 中的第 11 步：
         *  授权服务器短的权限控制，即集中式权限控制
         *
         * 实现更细粒度的权限控制，从某种程度上来说，这个过程也可以称作鉴权
         */&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 授权服务器端鉴权/权限控制业务的逻辑&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后来看资源服务器端的权限控制。我们使用 spring-secutity 提供的标准方法来实现：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;资源服务器端 PreAuthorize hasRole/hasAuthority&lt;/li&gt;
  &lt;li&gt;资源服务器端 PreAuthorize 自定义实现 hasPermission&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;以上说法在某种程度上也可以理解为鉴权。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;首先，我们添加或修改 photo-service 工程的相关代码：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;PhotoController.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.demophoto.web&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.access.prepost.PreAuthorize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.bind.annotation.GetMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.bind.annotation.RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.web.bind.annotation.RestController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/**
 * 1、 权限控制的两种类型：资源服务端权限控制、授权服务器端权限控制
 * 2、 权限控制的三种方法：
 *      A、 资源服务器端 PreAuthorize hasRole/hasAuthority
 *      B、 资源服务器端 HttpSecurity access 自定义实现 hasPermission
 *      D、 授权服务器端 HandlerInterceptor
 *     以上说法在某种程度上也可以理解为鉴权。
 */&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/api/&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PhotoController&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/photo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@PreAuthorize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hasRole(&apos;USER&apos;) and hasAuthority(&apos;WRITE&apos;)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetchPhoto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;GET photo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/photo2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetchPhoto2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;GET photo 2&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@GetMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/photo3&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@PreAuthorize&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hasPermission(&apos;PhotoController&apos;, &apos;read&apos;)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetchPhoto3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;GET photo 3&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;ResourceServerConfigurer.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableResourceServer&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceServerConfigurer&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ResourceServerConfigurerAdapter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
     * 旧版本的 spring-security-oauth2 还需要将执行 resources.expressionHandler(oAuth2WebSecurityExpressionHandler) 
     * 以注入自定义的 expressionHandler，当前及以后版本不需要了
     * 
     * @return
     */&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2WebSecurityExpressionHandler&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;oAuth2WebSecurityExpressionHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;OAuth2WebSecurityExpressionHandler&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oAuth2WebSecurityExpressionHandler&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;OAuth2WebSecurityExpressionHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 在新版本的 spring-security-oauth2 中，这行代码可以不用，&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 框架会自动注入 customPermissionEvaluator 替换默认的 DenyAllPermissionEvaluator&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// oAuth2WebSecurityExpressionHandler.setPermissionEvaluator(customPermissionEvaluator);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;oAuth2WebSecurityExpressionHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;CustomPermissionEvaluator.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.demophoto.config.oauth2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.demophoto.service.PermisionEvaluatingService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.access.PermissionEvaluator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.Authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.stereotype.Component&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.io.Serializable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@Component&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomPermissionEvaluator&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PermissionEvaluator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PermisionEvaluatingService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;permisionEvaluatingService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PermisionEvaluatingService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hasPermission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Authentication&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targetDomainObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;permission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;permisionEvaluatingService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;hasPermission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targetDomainObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;permission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hasPermission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Authentication&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Serializable&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targetId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targetType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;permission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;permisionEvaluatingService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;hasPermission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targetId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targetType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;permission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;PermisionEvaluatingService.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.demophoto.service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.springframework.security.core.Authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.io.Serializable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PermisionEvaluatingService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hasPermission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Authentication&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targetDomainObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;permission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 业务逻辑&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hasPermission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Authentication&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;authentication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Serializable&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targetId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targetType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;permission&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 业务逻辑&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;DemoPhotoApplication.java&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@SpringBootApplication&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableGlobalMethodSecurity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;prePostEnabled&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// 开启 hasRole/hasAuthority/hasPermission 支持&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DemoPhotoApplication&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;经过以上配置，当客户端向 photo-service 发起 GET /api/photo3 请求时，将会进入 CustomPermissionEvaluator.hasPermission() 方法进行判断，因此可以实现非常灵活的资源服务器端权限控制。&lt;/p&gt;
</description>
        <pubDate>Wed, 29 Dec 2021 00:00:00 +0000</pubDate>
        <link>https://mtide.net/Spring-OAuth2-%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97-%E4%BA%8C-OAuth2-%E5%AF%86%E7%A0%81%E6%A8%A1%E5%BC%8F%E5%BC%80%E5%8F%91%E5%AE%9E%E4%BE%8B.html</link>
        <guid isPermaLink="true">https://mtide.net/Spring-OAuth2-%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97-%E4%BA%8C-OAuth2-%E5%AF%86%E7%A0%81%E6%A8%A1%E5%BC%8F%E5%BC%80%E5%8F%91%E5%AE%9E%E4%BE%8B.html</guid>
        
        <category>spring security,oauth2</category>
        
        
        <category>ARCHITECTURE</category>
        
      </item>
    
      <item>
        <title>Spring OAuth2 开发指南（一）：体系架构和开发概览</title>
        <description>&lt;p&gt;[TOC]&lt;/p&gt;

&lt;h3 id=&quot;一开篇&quot;&gt;一、开篇&lt;/h3&gt;

&lt;p&gt;《Spring OAuth2 开发指南》是系列文章，详细介绍基于 Spring 生态（包括 Spring Cloud） OAuth2 的实战开发。本系列将由五篇文章组成：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;（一）体系架构和开发概览：是系列文章的开篇，主要对 OAuth2 的体系架构和主要流程进行梳理剖析，并对当前 Spring OAuth2 开发做一个概括性、全局性介绍；&lt;/li&gt;
  &lt;li&gt;（二）OAuth2 密码模式开发实例&lt;/li&gt;
  &lt;li&gt;（三）OAuth2 客户端模式开发实例&lt;/li&gt;
  &lt;li&gt;（四）OAuth2 授权码模式开发实例&lt;/li&gt;
  &lt;li&gt;（五）OAuth2 微服务场景实例开发：以密码模式为例，介绍在微服务场景下使用 OAuth2 以及整合 JWT 的关键技术方法，主要围绕客户端、微服务网关、认证授权服务进行，不涉及微服务的其他模块等。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;本系列第一篇主要聚焦 OAuth2 体系的概念介绍，其余各篇偏向于实战，主要实现 OAuth2 的三种授权模式：密码模式、客户端模式和授权码模式，包括展示授权服务器、资源服务器、客户端等几种角色的交互，以及 JWT 的整合。并且每个实例都提供两个代码版本：一个是基于旧的 Spring Security OAuth2 组件；一个是基于新的 Spring Authorization Server 组件。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;需要注意的是 password 模式由于 OAuth2.1 不推荐使用所以只能提供旧的组件代码版本，具体请参见 https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-02&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;如果对认证和授权平台的相关理论感兴趣，可以移步本人早前的两篇文章：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;平台级 SaaS 架构的基础：统一身份管理系统， https://www.jianshu.com/p/990d8acfdb69&lt;/li&gt;
  &lt;li&gt;微服务架构下的统一身份认证和授权， https://www.jianshu.com/p/2571f6a4e192&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;二oauth2-体系结构&quot;&gt;二、OAuth2 体系结构&lt;/h3&gt;

&lt;p&gt;OAuth 授权体系设计之初主要是为了解决第三方应用登录和授权的问题，但由于其严格规范的流程定义，广泛的授权通用性，且与具体技术平台无关等诸多优点，逐渐发展成为认证和授权领域的主流技术规范。但其实 OAuth2 规范归纳起来并不复杂，就四种主要的授权模式和五种角色。&lt;/p&gt;

&lt;p&gt;四种授权模式，分别是：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;授权码模式（authorization code）&lt;/li&gt;
  &lt;li&gt;简化模式（implicit）&lt;/li&gt;
  &lt;li&gt;密码模式（resource owner password credentials）&lt;/li&gt;
  &lt;li&gt;客户端模式（client credentials）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;五种主要的角色，分别是：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;用户代理/浏览器 User Agent&lt;/li&gt;
  &lt;li&gt;客户端 Client&lt;/li&gt;
  &lt;li&gt;资源所有者 Resource Owner&lt;/li&gt;
  &lt;li&gt;资源服务器 Resource Server （受保护资源）&lt;/li&gt;
  &lt;li&gt;授权服务器 Authorization Server&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;建议熟练掌握以上概念，理清其中的关系，有助于对整个体系有一个全局性的了解。倘若一上来就撸代码，那造出来的系统必然不健康。须知身份管理体系是任何平台的重要基础设施，一旦建造的不牢固，必然会种下隐患，好似大厦地基埋下的一颗延迟炸弹。&lt;/p&gt;

&lt;p&gt;回到主题，四种模式都有其特定的使用场景，但是在落地过程中，也可以根据实际情况自行取舍。为了方便接下来的介绍，我们假设两套演示案例：基于图像的物品分类系统（IBCS，Image-Based Classification System）、相册预览系统（PAPS，Photo Album Preview System）。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;为什么假设两套？主要是为了分析对比各种授权模式的适用场景。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;场景假设-a基于图像的物品分类系统ibcsimage-based-classification-system&quot;&gt;场景假设 A：基于图像的物品分类系统（IBCS，Image-Based Classification System）&lt;/h4&gt;

&lt;p&gt;假设团队正在开发一套“基于图像的物品分类系统（Image-Based Classification System）”，该系统采用 RESTful API 对外交互，主要功能是允许用户通过 H5 应用或 APP 上传图片，系统分析后返回分类结果。为了表演 OAuth2 体系认证、授权、鉴权和权限控制的全流程，我们构建一个最简化的 IBCS 演示项目：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;服务名&lt;/th&gt;
      &lt;th&gt;类别&lt;/th&gt;
      &lt;th&gt;描述&lt;/th&gt;
      &lt;th&gt;技术选型&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;ibc-service&lt;/td&gt;
      &lt;td&gt;内部服务&lt;/td&gt;
      &lt;td&gt;资源服务器角色，图像分类服务&lt;/td&gt;
      &lt;td&gt;Spring Boot 开发的 RESTful 服务&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;idp&lt;/td&gt;
      &lt;td&gt;内部服务&lt;/td&gt;
      &lt;td&gt;授权服务器角色，具体指负责认证、授权和鉴权&lt;/td&gt;
      &lt;td&gt;Spring Boot 开发&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;demo-h5&lt;/td&gt;
      &lt;td&gt;外部应用&lt;/td&gt;
      &lt;td&gt;demo 应用的前端&lt;/td&gt;
      &lt;td&gt;Antd-Pro 开发&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;demo-service&lt;/td&gt;
      &lt;td&gt;外部应用&lt;/td&gt;
      &lt;td&gt;demo 应用的后端&lt;/td&gt;
      &lt;td&gt;Spring Boot 开发&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;这里设计了一个 demo 应用，对于 IBCS 来说，demo 应用就是一个外部应用，是一个消费者，包括前端 demo-h5 和后端 demo-service。但是从整个演示项目来说，ibc-service、idp、demo-h5 和 demo-service 都是自家应用，是相互信任的。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;值得注意的是，在现实世界里，应用有两种类型：一是有 Server 端的应用；另一种是无 Server 端的应用。前者比较好理解，它可能是前后端分离的 MVC 单体应用，也有可能是 REST 型的前后端应用，这些都是常见的开发结构。除此之外，还有一些没有或者不需要后端的应用，比如 SinglePage H5，由 Vue 或 React 开发的简单 H5，整个应用仅由 JavaScript 代码组成，前端即应用的全部。这种类型的应用，有一个最大的安全问题，即 client_secret 如何安全存储，在无 Server 场景中无论是经典的授权码模式还是密码模式，都无法有效解决这个问题，因为一个全部由 JavaScript 写成的程序，本质上是完全透明的，client_secret 存在泄露的风险。client_secret 将导致客户端被伪造，即攻击者在拿到 client_id 和 client_secret 后伪造一个 App 发起虚假请求。当然许多平台会在 idp 端增加 redirect_uri 的绑定或 IP/域名白名单机制，从而有效降低伪造/劫持等安全风险。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;有人说 OAuth 2.0 规范提出的 PKCE（Proof Key for Code Exchange by OAuth Public Clients）协议可以解决这个问题，这是一个错误的观点，PKCE 在技术原理上并不能解决 client_secret 泄露的风险，所以对安全性要求高的情况下最好不要采用无 Server 端方式。不过 PKCE 作为一种增强协议可以搭配 OAuth2 组合使用以提高整体安全性。其主要流程为：由暴露在外的“前端”自身生成一串随机的验证码，再根据验证码生成挑战码，然后用挑战码向授权服务器请求授权码，授权服务器保存挑战码后返回授权码给“前端”，“前端”拿到授权码后用第一步生成的随机验证码+授权码向授权服务器申请令牌，授权服务器拿到验证码+授权码后根据此验证码生成挑战码，如果生成的挑战码跟上一步保存的挑战码一致则颁发令牌。有兴趣的同学可以自行查阅相关资料。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;场景假设-b相册预览系统papsphoto-album-preview-system&quot;&gt;场景假设 B：相册预览系统（PAPS，Photo Album Preview System）&lt;/h4&gt;

&lt;p&gt;PAPS 是一个社交平台的子系统，与 IBCS 类似，采用 RESTful API 对外交互，主要功能是允许用户预览自己的相册，我们构建一个最简化的 PAPS 演示项目：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;服务名&lt;/th&gt;
      &lt;th&gt;类别&lt;/th&gt;
      &lt;th&gt;描述&lt;/th&gt;
      &lt;th&gt;技术选型&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;photo-service&lt;/td&gt;
      &lt;td&gt;内部服务&lt;/td&gt;
      &lt;td&gt;资源服务器角色，相册预览服务&lt;/td&gt;
      &lt;td&gt;Spring Boot 开发的 RESTful 服务&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;idp&lt;/td&gt;
      &lt;td&gt;内部服务&lt;/td&gt;
      &lt;td&gt;授权服务器角色，具体指负责认证、授权和鉴权&lt;/td&gt;
      &lt;td&gt;Spring Boot 开发&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;demo-h5&lt;/td&gt;
      &lt;td&gt;外部应用&lt;/td&gt;
      &lt;td&gt;demo 应用的前端&lt;/td&gt;
      &lt;td&gt;Antd-Pro 开发&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;demo-service&lt;/td&gt;
      &lt;td&gt;外部应用&lt;/td&gt;
      &lt;td&gt;demo 应用的后端&lt;/td&gt;
      &lt;td&gt;Spring Boot 开发&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;两个演示案例唯一的不同就在于核心服务不同，一个是图像分类服务，一个是相册预览服务。看起来似乎没什么差别，实际上他们的业务模型还很不同的，接下来会分析这一点。&lt;/p&gt;

&lt;h4 id=&quot;授权码模式-vs-密码模式-vs-客户端模式&quot;&gt;授权码模式 vs 密码模式 vs 客户端模式&lt;/h4&gt;

&lt;p&gt;大家思考一下，授权码模式、密码模式和客户端模式，在不同的业务场景下该如何选择？&lt;/p&gt;

&lt;p&gt;无非有两种做法：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;不管三七二十几，整个项目只使用一种授权模式，简单明了；&lt;/li&gt;
  &lt;li&gt;仔细分析各种业务场景，给予合适的模式选型。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;第一种做法，简单粗暴，看起来好像不可取，但也不能一棍子打死，具体还是看项目的业务模型、设计目标和具体要求。但仅从科学严谨或者技术研究角度来说，肯定是第二种方式更加可取。接下来，我们就以第二种做法展开，详细分析下模式选型的问题。&lt;/p&gt;

&lt;h5 id=&quot;授权码模式和密码模式&quot;&gt;授权码模式和密码模式&lt;/h5&gt;

&lt;p&gt;我们先来看授权码模式和密码模式之间的比较，大家知道，授权码模式是 OAuth2 体系安全性最高的模式，密码模式与其相比，主要差别是少了一层用户确认授权的动作，缺乏这一动作就导致在授权阶段，用户需要把用户名密码告知客户端，造成潜在的密码泄露风险。我们看一下对比：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;比较项&lt;/th&gt;
      &lt;th&gt;授权码模式&lt;/th&gt;
      &lt;th&gt;密码模式&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;适用场景&lt;/td&gt;
      &lt;td&gt;不可信/第三方认证和授权、可信/内部服务认证和授权&lt;/td&gt;
      &lt;td&gt;可信/内部服务认证和授权&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;开发难度&lt;/td&gt;
      &lt;td&gt;较为复杂&lt;/td&gt;
      &lt;td&gt;相对简单&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;安全性&lt;/td&gt;
      &lt;td&gt;最高&lt;/td&gt;
      &lt;td&gt;只要不运用于不可信/第三方场景则同样安全&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;在这里，可信/内部服务场景的定义是相对的概念，指纳入同一套 OAuth2 体系的应用和服务，且这些应用和服务是由相同的或者相互信任的团队开发，也可以称作第一方应用。打个比方，微服务架构下的 B2C 商城系统，基本组成有前端 H5、无线端 APP、API 网关、认证授权服务、订单服务、商品服务等，由于上述所有组成部分都同属于一套 OAuth2 体系，且都由相同团队开发，那么他们全都归属于可信/内部服务场景。&lt;/p&gt;

&lt;p&gt;那么为什么又说是相对概念？我们把视角聚焦到整个商城系统，毫无疑问，网关属于安全边界，网关以内的认证授权服务、订单服务、商品服务属于内部服务，而前端 H5、无线端 APP 则属于外部应用，如果这些外部应用是其他团队开发，我们也可以定义它们为第三方应用。这样就以网关为边界，划分出了内部服务和外部服务，这就是所说的相对概念。&lt;/p&gt;

&lt;p&gt;那么，电商系统的前端 H5、无线端 APP 在认证和授权阶段，采用授权码模式和采用密码模式，有什么差别吗？授权码模式有一层用户确认授权的动作，从而避免泄露用户名和密码给第三方应用，除此之外，两者之间几乎提供了相同的安全流程。这里所指的第三方应用不正是前端 H5 和无线端 APP 吗？都是自家开发的应用，自然是可信的，因此无须担忧泄露不泄露的风险。&lt;/p&gt;

&lt;p&gt;以此来看，在上述条件的限定下，两种模式的安全性打平了。遵循“简化原则”，采用密码模式即可，当然喜欢挑战复杂的同学选择授权码模式也是可以的。&lt;/p&gt;

&lt;p&gt;但是也不是说授权码模式就可以被密码模式取代了，授权码模式主要的应用场景，是在第三方/不可信应用的登录和授权，主要解决在不泄露用户密码的情况下如何安全授权某个应用向另一个应用提供用户资源的问题，举例来说，某第三方应用（客户端）需要获得用户（资源所有者）在另一个不可信应用（资源服务器）上的该用户的用户数据（资源）的场景就特别适合采用授权码模式。&lt;/p&gt;

&lt;p&gt;综上，选择授权码模式还是密码模式，具体要根据业务场景来确定，其中的关键决策点是应用或服务之间是否互相信任。&lt;/p&gt;

&lt;h5 id=&quot;客户端模式&quot;&gt;客户端模式&lt;/h5&gt;

&lt;blockquote&gt;
  &lt;p&gt;PAPS 相册预览系统演示案例应该采用何种授权模式？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;回答这个问题之前，大家先思考一个问题：在 PAPS 中，资源所有者所指代的对象是什么？&lt;/p&gt;

&lt;p&gt;首先要明确资源是什么，其次该资源是受保护的，最后资源归谁所有，谁就是资源所有者。在 PAPS 中，很明显受保护的资源是用户的相册，资源所有者自然是用户本人。&lt;/p&gt;

&lt;p&gt;明确资源所有者的含义后，再根据前文的分析，毫无疑问：如果 PAPS 的 demo 应用是第三方的不可信应用，则应该采用授权码模式；如果是第一方可信应用，则可以采用密码模式，当然不怕麻烦也可以用授权码模式。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;接下来，我们来分析 IBCS 图片分类系统演示案例该用何种模式。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;同样地，回答这个问题之前，大家再思考一下：在 IBCS 中，资源所有者所指代的对象是什么？&lt;/p&gt;

&lt;p&gt;首先资源所有者所指代的对象不是一成不变的。在 IBCS 演示案例中，demo 应用向 ibc-service 传送一张图片，并希望返回分类结果，那么这里面的受保护资源具体是什么呢？不似 PAPS 相册预览服务有实体资源概念，其受保护资源是用户的相册，而 IBCS 难以抽象出一个实体的资源来。&lt;/p&gt;

&lt;p&gt;可以这么理解，IBCS 提供的核心能力是图片分类算法，这就是它的受保护资源，图片分类算法的所有权人显然是持有此算法的实体组织或个人，因此资源所有者是该实体组织或个人。那么矛盾点来了，以密码模式为例，按照 OAuth2 的设计，资源所有者向客户端提供用户名和密码，客户端将 client_id 和 client_secret 连同该用户名和密码，向授权服务器申请令牌，此处的资源所有者是 IBCS 的普通用户，但是按照刚才所述，资源所有者应该是持有图片分类算法的实体组织或个人，这不就矛盾了吗？&lt;/p&gt;

&lt;p&gt;因此，从资源所有者的角度来分析，IBCS 演示案例不适合采用授权码模式，也不适合采用密码模式，客户端模式才是最佳选择。IBCS 并没有用户的资源，授权码模式和密码模式都是需要用户授权才能跑通的，而 IBCS 提供的资源或服务并不属于用户，所以法理上来说不需要用户的授权，IBCS 提供的分析服务是跟用户无关的。&lt;/p&gt;

&lt;p&gt;以上就是选择何种授权模式的一种分析模型，从资源所有者的维度切入。总结一下：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;案例&lt;/th&gt;
      &lt;th&gt;场景&lt;/th&gt;
      &lt;th&gt;适合模式&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;IBCS 图像分类系统案例&lt;/td&gt;
      &lt;td&gt;可信/内部服务&lt;/td&gt;
      &lt;td&gt;客户端模式&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;IBCS 图像分类系统案例&lt;/td&gt;
      &lt;td&gt;不可信/外部应用&lt;/td&gt;
      &lt;td&gt;客户端模式&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PAPS 相册预览系统案例&lt;/td&gt;
      &lt;td&gt;可信/内部服务&lt;/td&gt;
      &lt;td&gt;密码模式&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;PAPS 相册预览系统案例&lt;/td&gt;
      &lt;td&gt;不可信/外部应用&lt;/td&gt;
      &lt;td&gt;授权码模式&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
  &lt;p&gt;其实资源所有者的具体指代对于实际开发来说，并不是一个重要的方面，关键是开发人员要有自己的理解，能够灵活巧用。说句题外话，虽然 OAuth2 是当今领域内的权威，但我们也不应盲目地无条件相信权威，技术发展一定是有漏洞和不完美之处的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;密码模式的典型架构层次和主要流程&quot;&gt;密码模式的典型架构层次和主要流程&lt;/h4&gt;

&lt;p&gt;我们以 PAPS 相册预览系统为例，介绍密码模式的架构层次和主要流程。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mtide.net/assets/images/post/20210809/1.png&quot; alt=&quot;OAuth2 密码模式典型架构层次&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如图所示，是密码模式的最精简架构层次，在实际开发中可以此作为基础进行扩展。密码模式涉及到五种主要角色，另外还有一个用户代理/浏览器角色：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;用户代理/浏览器：一般单体应用都是前后端分离的 MVC 结构，从这个角度看，这里具体可以将用户代理/浏览器理解为前端的 H5 应用或者无线端的 APP，换句话说 H5/APP 承载用户的交互操作而成为用户代理。具体到 PAPS 演示案例就是 demo-h5；&lt;/li&gt;
  &lt;li&gt;客户端：具体指单体应用的后端服务，具体到 PAPS 演示案例就是 demo-service；&lt;/li&gt;
  &lt;li&gt;授权服务器：具体指负责认证、授权和鉴权的 IDP（Identify Provider），也是 OAuth2 体系的核心服务，也可以简单叫做 auth-service。具体到 PAPS 演示案例就是 idp；&lt;/li&gt;
  &lt;li&gt;受保护资源：即资源服务器，一般是内部服务，比如产品服务、订单服务等。具体到 PAPS 演示案例就是 photo-service；&lt;/li&gt;
  &lt;li&gt;资源所有者：顾名思义，即受保护资源的所有者，一般具体指代用户自己。具体到 PAPS 演示案例就是用户。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;整个流程分为两个阶段：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;第一阶段：认证授权阶段&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;用户代理（demo-h5）将用户输入的用户名和密码，发送给客户端（demo-service）；&lt;/li&gt;
  &lt;li&gt;客户端（demo-service）将用户输入的用户名和密码，连同 client_id + client_secret (由 idp 分配）一起发送到 idp 以请求令牌，如果 idp 约定了 scope 则还需要带上 scope 参数；&lt;/li&gt;
  &lt;li&gt;idp 首先验证 client_id + client_secret  的合法性，再检查 scope 是否无误，最后验证用户名和密码是否正确，正确则生成 token。这一步也叫“认证”；&lt;/li&gt;
  &lt;li&gt;idp 返回认证结果给客户端，认证通过返回 token，认证失败返回 401。如果认证成功则此步骤也叫“授权”；&lt;/li&gt;
  &lt;li&gt;客户端收到 token 后进行暂存，并创建对应的 session；&lt;/li&gt;
  &lt;li&gt;客户端颁发 cookie 给用户代理/浏览器。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;至此，认证授权阶段完成。其中步骤 5-6 也有其他会话方案，比如 REST 型应用可能会将 token 存储在浏览器端，但 session/cookie 方案无疑是最稳妥的选择。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;在一般的 Web 应用中，可以将此阶段看作是一次用户登录过程。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;第二阶段：授权后请求资源阶段&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;用户通过用户代理（demo-h5）访问“我的相册”页面，用户代理携带 cookie 向客户端（demo—service）发起请求；&lt;/li&gt;
  &lt;li&gt;客户端通过 session 找到对应的 token，携带此 token 向资源服务器（photo-service）发起请求；&lt;/li&gt;
  &lt;li&gt;资源服务器（photo-service）向 idp 请求验证 token 有效性；&lt;/li&gt;
  &lt;li&gt;idp 校验 token 有效性，再根据 scope 判断客户端（demo-service）是否有权限调用此 API，最后返回校验结果给资源服务器。这一步也叫“鉴权”；&lt;/li&gt;
  &lt;li&gt;资源服务器根据 idp 检验结果（true/false 或其他等效手段）决定是否返回用户相册数据给客户端。如果 token 校验失败则返回 401 给客户端，如果 scope 检查不通过则返回 403。这一步也叫“权限控制”。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;至此，授权后请求资源阶段完成。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;事实上 scope 参数不是核心的内容，实际工作中为了简化开发步骤甚至可以忽略它。scope 参数是用来约束客户端的权限的，跟用户权限（authorities）是不同的。比如可以在 idp 中利用 scope 参数约束某客户端只能发起读（GET）型请求，或只能调用指定的几个 API 等，具体业务逻辑自行编写。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;密码模式的微服务架构层次和主要流程&quot;&gt;密码模式的微服务架构层次和主要流程&lt;/h4&gt;

&lt;p&gt;我们仍以 PAPS 相册预览系统为例，介绍密码模式在微服务场景下的架构层次和主要流程。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mtide.net/assets/images/post/20210809/2.png&quot; alt=&quot;OAuth2 密码模式微服务架构层次&quot; /&gt;&lt;/p&gt;

&lt;p&gt;微服务场景下，增加了一个网关，网关实际上是一个反向代理，将用户的请求转发到内部服务器。类似地，微服务场景下也分为两个阶段，而且第一阶段没什么变化，主要不同在于第二阶段：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;用户通过用户代理（demo-h5）访问“我的相册”页面，用户代理携带 cookie 向客户端（demo—service）发起请求；&lt;/li&gt;
  &lt;li&gt;客户端通过 session 找到对应的 token，携带此 token 向网关发起对资源服务器（photo-service）的请求；&lt;/li&gt;
  &lt;li&gt;网关截取 token 连同本次请求的细节，一并向 idp 请求校验；&lt;/li&gt;
  &lt;li&gt;idp 校验 token 有效性，再根据 scope 和请求细节判断客户端（demo-service）是否有权限调用此 API，最后返回校验结果给网关。如果校验全部通过，idp 生成 JWT 并返回给网关；如果 token 校验失败返回 401；如果 scope 检查不通过则返回 403；&lt;/li&gt;
  &lt;li&gt;如果校验通过，网关将得到 JWT，携带此 JWT 转发请求到资源服务器；&lt;/li&gt;
  &lt;li&gt;资源服务器解析 JWT 得到用户信息，查询用户相册数据后返回给网关；&lt;/li&gt;
  &lt;li&gt;网关将用户相册数据返回给客户端。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;此流程有两项重大变化：一是加入网关使得整个流程复杂了一些；二是网关以内使用 JWT 作为令牌。关于这两点的解读，本文不再赘述，感兴趣的同学可以参阅本人早前的文章《微服务架构下的统一身份认证和授权》。&lt;/p&gt;

&lt;p&gt;值得注意的是，步骤 9-11，还有另一种处理方法，即将 scope 客户端权限检查放到网关进行：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;网关截取 token 后向 idp 请求校验；&lt;/li&gt;
  &lt;li&gt;idp 校验 token 有效性，通过校验则根据 token 查询用户信息和 scope，生成 JWT 返回给网关；如果不通过则返回 401；&lt;/li&gt;
  &lt;li&gt;网关得到 JWT，解析后根据 scope 判断客户端是否有权限调用此 API，如有则携带 JWT 转发请求到资源服务器，否则返回 403 给客户端。&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;客户端权限检查放到网关，则网关要维护 scope 和客户端权限的逻辑。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;客户端模式的微服务架构层次和主要流程&quot;&gt;客户端模式的微服务架构层次和主要流程&lt;/h4&gt;

&lt;p&gt;我们以 IBCS 图片分类系统为例，介绍客户端模式在微服务场景下的架构层次和主要流程。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mtide.net/assets/images/post/20210809/3.png&quot; alt=&quot;OAuth2 客户端模式微服务架构层次&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以看到，客户端模式流程比较简单，这里就不再叙述具体流程了，不过请注意第 2 步：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;客户端用向 idp 申请令牌之前，应该先检查是否缓存了有效令牌，有的话直接跳到第 6 步发起服务访问请求。&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;授权码模式的微服务架构层次和主要流程&quot;&gt;授权码模式的微服务架构层次和主要流程&lt;/h4&gt;

&lt;p&gt;我们仍以 PAPS 相册预览系统为例，介绍授权码模式在微服务场景下的架构层次和主要流程。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mtide.net/assets/images/post/20210809/4.png&quot; alt=&quot;OAuth2 授权码模式微服务架构层次&quot; /&gt;&lt;/p&gt;

&lt;p&gt;整个流程分为两个阶段：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;第一阶段：认证授权阶段&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;用户在用户代理（demo-h5）处点击登录按钮，或请求授权登录按钮，此操作将访问客户端的某个 URI；&lt;/li&gt;
  &lt;li&gt;客户端（demo-service）将用户导向 idp 提供的认证授权页面，并在页面 ULR 参数中携带 client_id，response_type=code，redirect_uri（可选），scope（可选），state（可选）；&lt;/li&gt;
  &lt;li&gt;用户通过用户代理（demo-h5），在 idp 的认证授权页面选择是否给予授权，如用户未登录，则需要先登录后再操作；&lt;/li&gt;
  &lt;li&gt;用户给予授权，idp 将用户导向 redirect_uri 指定的页面，并附加授权码（code）；如果未指定 redirect_uri，则导向发起该请求时的 URI，同时附加授权码（code）；&lt;/li&gt;
  &lt;li&gt;客户端收到授权码（code），向 idp 发起令牌申请，同时附上 client_id（必填） + client_secret（必填） + state（如有） + scope（如有）。注意这一步是客户端在后台发起的，用户层面无法感知；&lt;/li&gt;
  &lt;li&gt;idp 收到请求后，先核对 client_id + client_secret + scope（如有）是否无误，然后校验授权码（code），全部正确后颁发 token 给客户端。&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
  &lt;li&gt;第二阶段：授权后请求资源阶段&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;该阶段的流程，与密码模式的微服务场景流程一致，此处不在赘述。&lt;/p&gt;

&lt;h5 id=&quot;对于-rest-型-demo-应用&quot;&gt;对于 REST 型 demo 应用&lt;/h5&gt;

&lt;p&gt;如果 demo 应用是一个 REST 型应用，则在第 1、2 步骤中，还可以这么处理：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;用户在用户代理（demo-h5）处点击登录按钮或请求授权登录按钮后，通知客户端（demo-service），客户端收到通知后返回重定向的指示，以及 scope（可选），state（可选）等；&lt;/li&gt;
  &lt;li&gt;demo-h5 收到响应后，直接将用户导向 idp 提供的认证授权页面，并在页面 URL 参数中携带客户端返回的参数（除了 state 参数外，其他参数可以在 demo-h5 中写死）client_id，response_type=code，redirect_uri（必选），scope（可选），state（可选），其中 redirect_uri 建议是必选的，而且必须是客户端提供的 URI（回调地址）。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;还可以这么处理：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;用户在用户代理（demo-h5）处点击登录按钮或请求授权登录按钮后，直接将用户导向 idp 提供的认证授权页面，并在页面 URL 参数中携带 client_id，response_type=code，redirect_uri（必选），scope（可选），但是不需要携带 state 参数，因为客户端不知道此参数的存在，其中 redirect_uri 建议是必选的，而且必须是客户端提供的 URI（回调地址）。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;至此，授权码模式的认证授权全流程完毕。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;讨论：客户端第一次将用户导向 idp 提供的认证授权页面时，idp 是否需要验证客户端的身份呢？或者说需不需要提供 client_secret 呢？&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;微服务网关的负载问题&quot;&gt;微服务网关的负载问题&lt;/h4&gt;

&lt;p&gt;在微服务场景下，大量的请求和响应都经过网关转发，我们设想一下，如果类似 PAPS 相册系统那样，返回的是图片数据，而且不采用 CDN 分发网络或文件存储服务等技术，那么图片流通过网关再返回给用户，网关的负载是不是将会非常庞大呢？&lt;/p&gt;

&lt;p&gt;当然，网关本身可以做负载均衡，可以引入缓存，数据流可以做 CDN 处理等，这些都是非常好的高性能方案，除此之外，有没有其他办法呢？&lt;/p&gt;

&lt;p&gt;这里引入一个网络技术领域的概念——负载均衡有三种模式：反向代理、透传模式和三角模式。这里简单介绍一下三角模式：&lt;/p&gt;

&lt;p&gt;假设一种网络结构，包括 Web 服务器、PC 客户端和负载均衡器。PC 通过负载均衡器发起对 Web 服务器的访问。在三角模式下的访问流程如下：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;PC 向负载均衡器发起对 Web 服务器的访问请求；&lt;/li&gt;
  &lt;li&gt;负载均衡器将 PC 的请求转发给 Web 服务器；&lt;/li&gt;
  &lt;li&gt;Web 服务器收到请求后，直接将响应数据发送给 PC 端。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;PC、Web 服务器和负载均衡器三者呈三角形状，因此叫做三角模式。这个模式的优点就是负载均衡器只负责转发请求，而响应数据包则不需要接收和转发，从而大副降低负载均衡器自身的数据流通压力。三角模式也类似于 LVS 的 DR 模式，LVS 调度器只负责接收和转发请求，后端的集群服务器返回的真实数据包将直接发往请求的客户端。&lt;/p&gt;

&lt;p&gt;按照这个思路，我们可以将三角模式引入网关，从而大副降低网关的负载压力。&lt;/p&gt;

&lt;h4 id=&quot;令牌的复用问题&quot;&gt;令牌的复用问题&lt;/h4&gt;

&lt;p&gt;我们设想一个场景，团队研发的平台同时包含了 IBCS 图片分类服务和 PAPS 相册预览服务，那么用户在登录平台（用密码模式认证授权）后，先访问“我的相册”，然后从中选择一张照片发起物品识别的请求。&lt;/p&gt;

&lt;p&gt;根据文章的模式选型分析，IBCS 服务应采用客户端模式，PAPS 服务应采用密码模式，那么，客户端是否应该申请两套令牌？&lt;/p&gt;

&lt;p&gt;回答这个问题，我们还是要从具体场景切入分析：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;如果用户需要登录平台后才能使用 IBCS 和 PAPS 的服务，那么，只需要用密码模式一种令牌即可；&lt;/li&gt;
  &lt;li&gt;如果 PAPS 功能无需登录，游客也能使用，那么密码模式和客户端模式要分开处理。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;授权码模式是最严格的，密码模式次之，客户端模式最差，因此一般情况下，授权码模式的令牌可以给其他模式使用，密码模式令牌可以给客户端模式使用，客户端模式只能自己使用。&lt;/p&gt;

&lt;h3 id=&quot;三spring-家族-oauth2-相关组件概览&quot;&gt;三、Spring 家族 OAuth2 相关组件概览&lt;/h3&gt;

&lt;p&gt;好了，从本节开始我们脱离枯燥的理论环节，进入一样枯燥的实战开发频道。&lt;/p&gt;

&lt;p&gt;目前构建 OAuth2 授权系统有三种方式：一是基于主流的开源组件构建；二是接入第三方授权服务（如 Google、GitHub OAuth2）；三是根据 OAuth2 的标准规范自行开发相关授权组件。&lt;/p&gt;

&lt;p&gt;常用的开源组件有 RedHat Keycloak、Spring Security、Spring Security OAuth2，以及刚起步的 Spring Authorization Server 等。值得一提的是 RedHat Keycloak，它是一款开源、成熟的 IAM 解决方案，功能强大且可私有化部署。&lt;/p&gt;

&lt;p&gt;在 Spring 的开发生态里，建议采用 Spring Security 方向的开源组件方案，可扩展性好，定制性强，用户广泛且社区活跃。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Spring Security OAuth2 目前已停止更新，官方不推荐继续使用。相关功能已经迁移到 Spring Security，但授权服务器（Authorization Server）功能并未包含。Authorization Server 功能将由 Spring Security 团队主导开发的 Spring Authorization Server 开源组件提供，详细信息请查看官方通告：https://spring.io/blog/2020/04/15/announcing-the-spring-authorization-server&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Spring Authorization Server 项目目前还在迭代中，该项目的开发计划托管在 ZenHub 上，感兴趣的同学可以自行了解： https://app.zenhub.com/workspaces/authorization-server-5e8f3182b5e8f5841bfc4902/board?repos=248032165&lt;/p&gt;

&lt;h4 id=&quot;一基于-spring-framework&quot;&gt;一）基于 Spring Framework&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;spring-security&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;核心组件，当前几乎所有 Spring OAuth2 开源技术方案都依赖于它。核心模块： spring-security-core。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;dependencies&amp;gt;
    &amp;lt;!-- ... other dependency elements ... --&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.security&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-security-web&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;5.5.1&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.security&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-security-config&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;5.5.1&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;[已停止更新] spring-security-oauth2&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;依赖于 spring-security。该组件现已合并到 spring-security 中，官方已不建议使用。在此之前是 Spring Security 团队官方维护的唯一且被广泛使用的组件。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.security.oauth&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-security-oauth2&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.5.1.RELEASE&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;spring-security-oauth2-authorization-server&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;依赖于 spring-security。在 Spring Security 官方宣布停止 spring-security-oauth2 组件更新后，推出的授权服务器 Authorization Server 的替代组件。 spring-security 整合 spring-security-oauth2 后，并不包括 Authorization Server 功能，因此如果需要开发此功能，则要搭配 spring-security-oauth2-authorization-server 一起使用。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;!-- https://mvnrepository.com/artifact/org.springframework.security.experimental/spring-security-oauth2-authorization-server --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.security.experimental&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-security-oauth2-authorization-server&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;0.1.2&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;二基于-spring-boot&quot;&gt;二）基于 Spring Boot&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;spring-boot-starter-security&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;依赖于 spring-security。相当于 spring-boot + spring-security ，默认包含 SecurityAutoConfiguration.class ，因此会执行一些自动化配置，可以简化开发步骤。如果想关闭自动配置，可以修改 Spring Boot 启动注解为 @SpringBootApplication(exclude = { SecurityAutoConfiguration.class })&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-security&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;三基于-spring-cloud&quot;&gt;三）基于 Spring Cloud&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;spring-cloud-starter-oauth2&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;依赖于 spring-boot-starter-security + spring-security-oauth2，并额外提供了许多功能实现，在微服务场景下比较实用。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;dependencyManagement&amp;gt;
    &amp;lt;dependencies&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-cloud-dependencies&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;Hoxton.SR12&amp;lt;/version&amp;gt;
            &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;
            &amp;lt;scope&amp;gt;import&amp;lt;/scope&amp;gt;
        &amp;lt;/dependency&amp;gt;
    &amp;lt;/dependencies&amp;gt;
&amp;lt;/dependencyManagement&amp;gt;
    
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-cloud-starter-oauth2&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;三核心组件选型&quot;&gt;三、核心组件选型&lt;/h3&gt;

&lt;h4 id=&quot;最佳方案-spring-boot-starter-security--spring-authorization-server&quot;&gt;最佳方案： spring-boot-starter-security + spring-authorization-server&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;由于 spring-security-oauth2 已经迁移到 spring-security，而 spring-boot-starter-security 集成了 spring-security，且做了许多简化配置，特别适合于构建 spring-boot 程序。截至 2021年8月，spring-authorization-server 的最新版本是 0.1.2，最新的消息请关注官方动态 https://spring.io/blog/2020/04/15/announcing-the-spring-authorization-server&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-security&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;!-- https://mvnrepository.com/artifact/org.springframework.security.experimental/spring-security-oauth2-authorization-server --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.security.experimental&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-security-oauth2-authorization-server&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;0.1.2&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Spring Authorization Server 项目遵守 OAuth2.1 规范而不支持 password 模式，因此需要是用此模式的项目可以考虑备选方案。&lt;/p&gt;

&lt;p&gt;https://app.zenhub.com/workspaces/authorization-server-5e8f3182b5e8f5841bfc4902/issues/spring-projects/spring-authorization-server/459&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mtide.net/assets/images/post/20210809/5.png&quot; alt=&quot;Spring Authorization Server 不支持 password 模式的官方答复截图&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;备选方案-spring-security-oauth2-或-spring-cloud-starter-oauth2&quot;&gt;备选方案： spring-security-oauth2 或 spring-cloud-starter-oauth2&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;此方案采用了 spring-security-oauth2，已被 Sprnig Security 官方停止更新，因此不再推荐。另外请注意的 spring-security-oauth2 2.4.0.RELEASE 及之后的版本会提示 deprecated。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;如果采用 spring-security-oauth2，则 pom 引用如下：&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.security.oauth&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-security-oauth2&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.3.8.RELEASE&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;!-- https://mvnrepository.com/artifact/org.springframework.security.oauth.boot/spring-security-oauth2-autoconfigure --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.security.oauth.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-security-oauth2-autoconfigure&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.1.2.RELEASE&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;!-- spring-security-oauth2-autoconfigure 2.1.3.RELEASE 及之后的版本会提示 deprecated。
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;或采用 spring-cloud-starter-oauth2，则 pom 引用如下：&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;spring-cloud-starter-oauth2 集成了 spring-security-oauth2，且做了许多简化配置，是在很长一段时间里基于开源软件构建 OAuth2 的主要方案之一。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-cloud-starter-oauth2&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</description>
        <pubDate>Mon, 09 Aug 2021 00:00:00 +0000</pubDate>
        <link>https://mtide.net/Spring-OAuth2-%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97-%E4%B8%80-%E4%BD%93%E7%B3%BB%E6%9E%B6%E6%9E%84%E5%92%8C%E5%BC%80%E5%8F%91%E6%A6%82%E8%A7%88.html</link>
        <guid isPermaLink="true">https://mtide.net/Spring-OAuth2-%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97-%E4%B8%80-%E4%BD%93%E7%B3%BB%E6%9E%B6%E6%9E%84%E5%92%8C%E5%BC%80%E5%8F%91%E6%A6%82%E8%A7%88.html</guid>
        
        <category>spring security,oauth2</category>
        
        
        <category>ARCHITECTURE</category>
        
      </item>
    
      <item>
        <title>微服务架构下的统一身份认证和授权</title>
        <description>&lt;p&gt;[TOC]&lt;/p&gt;

&lt;h2 id=&quot;一预备知识&quot;&gt;一、预备知识&lt;/h2&gt;

&lt;p&gt;本文讨论基于微服务架构下的身份认证和用户授权的技术方案，在阅读之前，最好先熟悉并理解以下几个知识点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;微服务架构相关概念：服务注册、服务发现、API 网关&lt;/li&gt;
  &lt;li&gt;身份认证和用户授权：SSO、CAS、OAuth2.0、JWT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;文章在涉及到上述知识内容时，会附上参考链接。此外，还有以下几个基础概念，在身份治理领域容易混淆：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;认证&lt;/li&gt;
  &lt;li&gt;授权&lt;/li&gt;
  &lt;li&gt;鉴权&lt;/li&gt;
  &lt;li&gt;权限控制&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;建议参考 pphh 的博文《认证、授权、鉴权和权限控制》：&lt;/p&gt;

&lt;p&gt;http://www.hyhblog.cn/2018/04/25/user_login_auth_terms&lt;/p&gt;

&lt;h2 id=&quot;二背景&quot;&gt;二、背景&lt;/h2&gt;

&lt;p&gt;当企业的应用系统逐渐增多后，每个系统单独管理各自的用户数据容易行成信息孤岛，分散的用户管理模式阻碍了企业应用向平台化演进。当企业的互联网业务发展到一定规模，构建统一的标准化账户管理体系将是必不可少的，因为它是企业互联网云平台的重要基础设施，能够为平台带来统一的帐号管理、身份认证、用户授权等基础能力，为企业带来诸如跨系统单点登录、第三方授权登录等基础能力，为构建开放平台和业务生态提供了必要条件。&lt;/p&gt;

&lt;h2 id=&quot;三需求分析&quot;&gt;三、需求分析&lt;/h2&gt;

&lt;p&gt;在微服务架构下，必须对企业的平台生态进行合理的业务划分，每个业务板块将自成系统，例如负责宣发的企业官网、主打文体的 B2B2C 商城、面向社区的物业服务系统等，这些系统业务比较独立，应当独立拆分。每个系统又可根据各自的业务模型进行切分，将业务模型和用户需求统筹分析后建立恰当的领域模型，形成独立的服务。&lt;/p&gt;

&lt;p&gt;另外，企业平台的客户范围比较复杂，有 2B 的业务，也有 2C 的，还有 2G（goverment）的，因此平台级的统一身份管理必须涉及组织实体和个人实体两类，其中组织实体包括政府机关（G）、企业单位（B）、团体组织（B）等，这类似于多租户架构的概念，但又比传统多租户架构复杂。&lt;/p&gt;

&lt;h4 id=&quot;一统一身份管理unified-identity-manager&quot;&gt;一）统一身份管理（Unified Identity Manager）&lt;/h4&gt;

&lt;p&gt;统一身份管理（UIM）是整个平台帐号和权限管控的基础，由此构建的系统称为UIMS（Unified Identity Management  System），平台下所有系统的账户管理、身份认证、用户授权、权限控制等行为都经由 UIMS 处理，提供帐号密码管理、基本资料管理、角色权限管理等功能。UIMS 基于『统一身份治理』的概念，可划分为两级账户体系、基础权限模块和基础信息模块三大模块。其中两级账户体系将账户分为组织实体帐号和个人实体账户两大类，个人实体从属于组织实体，也可以不从属任何组织实体，且个人实体可同时从属于多个组织实体；基础权限模块将各业务系统的资源权限进行统一管理和授权；基础信息模块用于描述组织实体和个人实体的基本信息，如组织实体名称、地址、法人，个人实体姓名、电话号码、性别等基础信息。UIMS 提供统一的 API 与各子系统连接。&lt;/p&gt;

&lt;p&gt;可以看到，众多系统和众多服务都将依赖于 UIMS 。本文仅涉及 UIMS 下的两级账户体系和基础权限模块这两个模块，据此可以独立出账户管理服务和权限管理服务，也可以合并成一个账户权限管理服务。其中账户管理服务包括业务系统实体、组织实体和个人实体管理、权限管理服务包含认证、授权和鉴权三个部分。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于统一身份管理系统的介绍，请参考 https://mtide.net/平台级SaaS架构的基础-统一身份管理系统.html&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;二软件即服务saas&quot;&gt;二）软件即服务（SaaS）&lt;/h4&gt;

&lt;p&gt;企业提供对外的 IT 服务，有两种部署模式：一是私有云部署，二是公有化服务。公有云服务即 SaaS，提供系统级的应用服务，包括企业服务如企业邮箱、办公 OA、人力资源系统等，个人服务如个人邮箱、云笔记、云网盘等。平台级的 SaaS 应用架构，实际上可以理解为多租户架构的升级版，加大了统一身份治理的难度。而基于 UIMS 系统的两级账户体系，可以很容易做到这一点。值得注意的是，有些系统仅提供个人账户服务，有些系统仅提供组织账户服务，有的则两者都提供，必须处理好个人实体和组织实体之间的关系。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于 SaaS 的介绍，请参考 http://www.ruanyifeng.com/blog/2017/07/iaas-paas-saas.html&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;三组织实体orginization-entity&quot;&gt;三）组织实体（Orginization Entity）&lt;/h4&gt;

&lt;p&gt;在 UIMS 中，组织机构应当是一种实体，与之对应的另一种实体是个人实体。注意实体（Entity）不是账户（Account），因此要设计一种用于组织实体登入受控系统的方法，这里有两种可选方案：一是增加组织实体账户，组织实体自身拥有账户，可直接进行认证登录；二是将从属于组织实体的个人账户作为组织实体的登入凭证。无论何种方法，在认证和授权时，都应当向 UIMS 提供统一的标准的账户凭证，凭证的规格由 UIMS 定义，因此，组织实体的认证授权与个人实体的认证授权并无二致。&lt;/p&gt;

&lt;h4 id=&quot;四单点登录sso&quot;&gt;四）单点登录（SSO）&lt;/h4&gt;

&lt;p&gt;企业平台涉及众多子系统，为简化各子系统的用户管理，提升用户体验，因此实现 SSO 是统一身份认证的重要目标：一次登录，全部访问。对于企业内部应用来说，SSO 是必须的选项，例如企业 OA、HR、CRM 等内部系统；对于外部应用来说，SSO 是可选项，具体哪个应用应当加入 SSO 系统，由该业务系统决定，例如外部商城、物业系统等对外服务系统。无论何种应用是否采用 SSO，UIMS 在技术上应当具备 SSO 的能力。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于SSO的介绍，请参考 https://www.cnblogs.com/EzrealLiu/p/5559255.html&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;五授权登录&quot;&gt;五）授权登录&lt;/h4&gt;

&lt;p&gt;随着平台业务的逐渐增长，依托于平台的，和平台依托的厂商和客户等资源将极大的丰富平台，因此必须构筑开放的生态系统，以支撑业务的进一步发展。可以开放平台级的授权登录功能，以允许第三方应用接入。通过三方授权登录，将平台的服务各能力开发给第三方，并将第三方的服务和能力接入平台，繁荣共生，共同发展。&lt;/p&gt;

&lt;h4 id=&quot;六服务间鉴权&quot;&gt;六）服务间鉴权&lt;/h4&gt;

&lt;p&gt;业务系统切分出不同的服务，根据粒度粗细和业务需求不同，服务的数量和权限要求都不同。微服务架构下的身份认证和授权，可分为两种：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;内部服务的认证和授权；&lt;/li&gt;
  &lt;li&gt;外部服务的认证和授权。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;通常，内部服务处于安全的内网环境之下，例如 BFF 层（Backend For Frontend Layer）的商品服务、订单服务等，在对安全需求不高的情况下，可不执行认证过程，服务与服务之间是相互信任的。&lt;/p&gt;

&lt;p&gt;而外部服务的认证和授权，通常由外部应用发起，通过反向代理或网关向安全边界内的服务发起请求，因此必须执行严格的认证过程。无线端APP、Web端、桌面客户端等外部应用下的各类服务，都属于外部服务。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mtide.net/assets/images/post/20181005/1.png&quot; alt=&quot;服务间鉴权示意图&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;七帐号登出和销毁&quot;&gt;七）帐号登出和销毁&lt;/h4&gt;

&lt;p&gt;与 SSO 相对应，UIMS 应该支持一次登出，全部登出，即 SSOff（Single Sign-Off，非标准术语）；或者一次登出，部分登出，而是否全部登出或部分登出取决于用户的选择，例如用户在 Web 端登出后，是否无线端 APP 也登出，这取决于用户偏好，但系统应当提供这种能力。&lt;/p&gt;

&lt;p&gt;此外，必须提供统一的销毁功能，以支持用户删除其账户，一次销毁，全部销毁。&lt;/p&gt;

&lt;h4 id=&quot;八付费授权&quot;&gt;八）付费授权&lt;/h4&gt;

&lt;p&gt;云平台应具备付费授权机制，针对用户账户和组织账户进行独立授权。根据产品的商业策略，可执行灵活的付费模式:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;时效限制:年付、季付、月付，不同时效费用不同；&lt;/li&gt;
  &lt;li&gt;功能限制:授权不同的功能，费用不同；&lt;/li&gt;
  &lt;li&gt;数量限制:最大组织数量限制、最大用户数量限制，不同的数量费用不同。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;四技术方案&quot;&gt;四、技术方案&lt;/h2&gt;

&lt;h3 id=&quot;一备选方案&quot;&gt;一）备选方案&lt;/h3&gt;

&lt;p&gt;上文基于『统一身份治理』的理念，提出了统一身份管理系统（UIMS）下关于身份认证和授权部分的主要需求。目前实现统一身份认证和授权的技术手段较多，总体可以归纳为以下两类：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;传统的 Cookie + Session 解决方案，有状态会话模式；&lt;/li&gt;
  &lt;li&gt;基于令牌/票据的解决方案，无状态交互模式。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;具体有：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;分布式 Session&lt;/li&gt;
  &lt;li&gt;OAuth2.0&lt;/li&gt;
  &lt;li&gt;CAS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上述方案各有利弊：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;分布式 Session 是老牌的成熟解决方案，但因其状态化通信的特性与微服务提倡的API导向无状态通信相互违背，且共享式存储存在安全隐患，因此微服务一般不太采用。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;OAuth2.0 是业内成熟的授权登录解决方案，然而 OA2.0 提供了4种授权模式，能够适应多种场景，作为基于令牌的安全框架，可以广泛用于需要统一身份认证和授权的场景。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于 OAuth2.0 的介绍，请参考 http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在 OAuth2.0 的实施过程中，一般会采用 JWT 作为令牌的主要标准：JWT（JSON Web Token）是一种简洁的自包含的 JSON 声明规范，因其分散存储和自解签等特点而广泛应用于非集中式认证/授权场景。由于 JWT 信息是经过签名的，可以确保发送方的真实性，确保信息未经篡改和伪造。但由于其自包含的客户端验签特性，令牌一经签发，即无法撤销，因此单纯采用 JWT 作为统一身份认证和授权方案无法满足帐号统一登出和销毁、帐号封禁和解除这几种类型的需求，所以一般配合 OAuth2.0 一起使用。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于 JWT 的介绍，请参考 http://blog.leapoahead.com/2015/09/06/understanding-jwt/&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;CAS 是时下最成熟的开源单点登录方案，包含 CAS Server 和 CAS Client 两部分。CAS Server 是一个 war 包需要独立部署，负责用户认证；CAS Client 负责处理对客户端受保护资源的访问请求，需要认证时，重定向到 CAS Server。值得注意的是，CAS 是一个认证框架，其本身定义了一套灵活完整的认证流程，但其兼容主流的认证和授权协议如 OAuth2、SAML、OpenID 等，因此一般采用 CAS + OAuth2 的方案实现 SSO 和授权登录。&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于 CAS 的介绍，请参考 https://apereo.github.io/cas/&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在微服务架构下，身份认证和用户授权通常分离出来成为独立的 IDP （Identity Provider）服务。在做技术选型时，应从以下几点考虑：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;满足 SSO 的技术需求；&lt;/li&gt;
  &lt;li&gt;满足简便性和安全性的需求；&lt;/li&gt;
  &lt;li&gt;满足开放性和扩展性的需求。&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;综合考虑，推荐采用无状态 API 模式，其中基于 OAuth2.0 的方案能够完全满足。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;场景假设：构建基于图像的物品识别系统（Image-Based Classification System）&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;为便于理解统一认证和授权方案的细节，假定一种场景：团队准备构建一套基于图像的物品识别系统，允许用户通过 H5 应用或 APP 上传图像，系统分析后返回识别结果；同时期望将此系统开放给社区和行业用户以便商用；最后允许第三方应用将自身的功能接入 IBCS 以增强后者的能力。&lt;/p&gt;

&lt;p&gt;下图是该系统的微服务架构图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://mtide.net/assets/images/post/20181005/2.png&quot; alt=&quot;IBCS 微服务架构图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在微服务架构下，IBCS 分为内外两层，内层处于物理内网环境下，也称为内部服务，外层处于物理外网环境下，也称为外部服务，内外通过 API 网关连接。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;内部服务：IDP 服务、配置服务、图像识别服务属于内部服务，通常内部服务之间是相互信任的，但在安全要求较高的场景下，内部服务也不能互信。&lt;/li&gt;
  &lt;li&gt;外部服务：桌面 APP、无线 APP、H5、第三方应用属于外部服务，外部服务分为两种类型：一种是第一方应用，即 IBCS 的一部分，如 IBCS 的 APP、H5 这些自家前端和无线端应用；另一种是 IBCS 之外的第三方应用。两种类型的授权方式是不一样的，前者一般采用 OAuth2.0 的密码模式，后者则采用授权码模式。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下文将以物品识别系统为例子，介绍这两种推荐方案：&lt;/p&gt;

&lt;h3 id=&quot;二最佳方案-oauth20&quot;&gt;二）最佳方案： OAuth2.0&lt;/h3&gt;

&lt;h4 id=&quot;1-oauth20-的四种授权模式&quot;&gt;1. OAuth2.0 的四种授权模式&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;授权码模式（authorization code）&lt;/li&gt;
  &lt;li&gt;简化模式（implicit）&lt;/li&gt;
  &lt;li&gt;密码模式（resource owner password credentials）&lt;/li&gt;
  &lt;li&gt;客户端模式（client credentials）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其中密码模式常用于外部服务的认证、授权和鉴权，客户端模式常用于内部服务的认证、授权和鉴权和开放平台应用的授权，授权码模式常用于社会化登录和 SSO，因此 OAuth2.0 可作为完整的统一身份认证和授权方案。&lt;/p&gt;

&lt;h4 id=&quot;2-oauth20-的几种重要角色&quot;&gt;2. OAuth2.0 的几种重要角色&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;必须注意的是，这些角色是相对的概念。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;客户端 Client：一般指第三方应用程序，例如用 QQ 登录豆瓣网站，这里的豆瓣网就是 Client；但在微服务体系里，Client 通常是服务本身，如 APP 端的注册登录服务；&lt;/li&gt;
  &lt;li&gt;资源所有者 Resource Owner：一般指用户，例如用 QQ 登录豆瓣网站，这里的所有者便是用户；但在微服务体系里，资源所有者的指向不是一成不变的，要具体分析；&lt;/li&gt;
  &lt;li&gt;资源服务器 Resource Server：一般指资源所有者授权存放用户资源的服务器，例如用 QQ 登录豆瓣网站，这里的 QQ 就是资源服务器；但在微服务体系里，服务提供者本身便是资源服务器；&lt;/li&gt;
  &lt;li&gt;授权服务器 Authorization Server：一般是指服务提供商的授权服务，例如用 QQ 登录豆瓣网站，这里的 QQ 便是授权服务器；类似地，在微服务体系里，IDP 服务便是授权服务器。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;3-ibcs-提供哪些功能&quot;&gt;3. IBCS 提供哪些功能&lt;/h4&gt;

&lt;h5 id=&quot;1核心功能以-api-形式暴露&quot;&gt;1）核心功能，以 API 形式暴露：&lt;/h5&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;接口&lt;/th&gt;
      &lt;th&gt;描述&lt;/th&gt;
      &lt;th&gt;body&lt;/th&gt;
      &lt;th&gt;返回&lt;/th&gt;
      &lt;th&gt;权限&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;POST /image-classify&lt;/td&gt;
      &lt;td&gt;图像识别&lt;/td&gt;
      &lt;td&gt;{ 图片内容, token }&lt;/td&gt;
      &lt;td&gt;{ 识别结果 }&lt;/td&gt;
      &lt;td&gt;受控接口&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h5 id=&quot;2由-uims-提供的功能&quot;&gt;2）由 UIMS 提供的功能：&lt;/h5&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;功能/API&lt;/th&gt;
      &lt;th&gt;描述&lt;/th&gt;
      &lt;th&gt;body&lt;/th&gt;
      &lt;th&gt;返回&lt;/th&gt;
      &lt;th&gt;权限&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;POST /accounts/&lt;/td&gt;
      &lt;td&gt;注册接口&lt;/td&gt;
      &lt;td&gt;{ username, password }&lt;/td&gt;
      &lt;td&gt;{ sign-up-result }&lt;/td&gt;
      &lt;td&gt;非受控接口&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;POST /accounts/login&lt;/td&gt;
      &lt;td&gt;登录接口&lt;/td&gt;
      &lt;td&gt;{ username, password }&lt;/td&gt;
      &lt;td&gt;{ token }&lt;/td&gt;
      &lt;td&gt;受控接口&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;POST /accounts/logout&lt;/td&gt;
      &lt;td&gt;登出接口&lt;/td&gt;
      &lt;td&gt;{ token }&lt;/td&gt;
      &lt;td&gt;{ logout-result }&lt;/td&gt;
      &lt;td&gt;受控接口&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;SignUp-Page&lt;/td&gt;
      &lt;td&gt;统一注册页面（UI）&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;非受控页面&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Login-Page&lt;/td&gt;
      &lt;td&gt;统一登录页面（UI）&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;非受控页面&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;其中，注册接口、SignUp-Page 和 Login-Page 页面是非受控接口/页面，意味着无须鉴权即可访问。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;SignUp-Page 和 Login-Page 页面是由 UIMS 提供的统一的注册和登录页面，当外部服务发起注册或登录请求时，有两种作法：一是统一跳转到 UIMS 的注册或登录页面，用户完成操作后调用 UIMS 的注册和登录 API 完成请求；二是在自己的注册和登录页面完成操作，然后以用户名和密码作为参数调用 UIMS 的注册和登录 API 完成请求。推荐采用第一种方法，类似于全网通行证，用户体验统一，不用重复开发注册登录页面。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h5 id=&quot;3成为开发者获取-ibcs-的能力集&quot;&gt;3）成为开发者，获取 IBCS 的能力集：&lt;/h5&gt;

&lt;ol&gt;
  &lt;li&gt;第一步：申请成为开发者。开发者分为个人开发者和组织开发者两类，实名认证也分两种类型进行。成为开发者的前提是成为平台的注册用户；&lt;/li&gt;
  &lt;li&gt;第二步：创建应用，平台审核通过后，生成 AppId 和 AppSecret 给到开发者，每个应用对应一对 AppId 和 AppSecret。值得注意的是，每个 AppID 与用户账号是绑定的，因此每个 AppId 获取资源和能力的权限受到该用户账户权限的限制，典型的例子是对象存储服务（OSS/OBS）；&lt;/li&gt;
  &lt;li&gt;第三步：获取 Access Token，开发者按照 OAuth2.0 的规范，采用客户端（client_credentials）模式，利用申请到的 AppId 和 AppSecret 向 IBCS 申请 token；&lt;/li&gt;
  &lt;li&gt;第三步：使用 Access Token 与平台进行交互，每次调用受控 API 时都携带此 token。&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
  &lt;p&gt;在更高安全性要求的场景下，也会采用授权码模式。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h5 id=&quot;4成为开发者创建第三方应用&quot;&gt;4）成为开发者，创建第三方应用：&lt;/h5&gt;

&lt;p&gt;一般来说，应当独立建设一个开放平台，开发平台作为整个云平台的一个子系统，同样依赖于 UIMS。在开放平台上，应当提供一套完善的界面和流程，以引导用户完成开发者认证和第三方应用接入的所有工作。此外，在前期阶段，也可以采用线下申请的方式，由管理员人工审核，在后台手动录入。&lt;/p&gt;

&lt;p&gt;在开放平台上，创建第三方应用的流程和步骤，与上一步骤『成为开发者，获取 IBCS 的能力集』一致。所不同的是，上个步骤是获取 IBCS 的能力，而本步骤『创建第三方应用』，是基于开放平台开发应用，类似于微信小程序。&lt;/p&gt;

&lt;h5 id=&quot;5成为开发者将异构系统第三方应用接入-ibcs&quot;&gt;5）成为开发者，将异构系统（第三方应用）接入 IBCS：&lt;/h5&gt;

&lt;p&gt;应该允许开发者将异构系统（第三方应用）接入 IBCS，以增强 IBCS 的能力。例如，假设 IBCS 本身不具备识别特种汽车的能力，但允许接入其他开发者开发的基于图像的特种汽车识别应用。&lt;/p&gt;

&lt;p&gt;第三方应用接入，归属于开放平台的范畴。接入的流程和步骤，与第三步骤『成为开发者，获取 IBCS 的能力集』一致，由开放平台提供标准的 API，第三方按照接入规范执行。&lt;/p&gt;

&lt;h4 id=&quot;4-oauth20-四种授权模式的应用场景&quot;&gt;4. OAuth2.0 四种授权模式的应用场景&lt;/h4&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;场景&lt;/th&gt;
      &lt;th&gt;描述&lt;/th&gt;
      &lt;th&gt;适用模式&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;用户注册（外部服务）&lt;/td&gt;
      &lt;td&gt;用户在 APP 提供的注册页面，完成注册请求&lt;/td&gt;
      &lt;td&gt;非受控接口，无须鉴权&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;用户登录（外部服务），返回 token&lt;/td&gt;
      &lt;td&gt;用户在 APP 提供的登录页面，完成登录请求，获得 token&lt;/td&gt;
      &lt;td&gt;密码模式（resource owner password credentials）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;用户注册（UIMS）&lt;/td&gt;
      &lt;td&gt;用户跳转到 UIMS 的注册页面，完成注册请求，注册成功后，跳回到原服务&lt;/td&gt;
      &lt;td&gt;非受控接口，无须鉴权&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;用户登录（UIMS），返回 token&lt;/td&gt;
      &lt;td&gt;用户跳转到 UIMS 的登录页面，完成登录操作，获得授权码，然后携带授权码跳转到重定向URI，再获得 token&lt;/td&gt;
      &lt;td&gt;授权码模式（authorization code）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;外部服务的鉴权&lt;/td&gt;
      &lt;td&gt;用户在 APP 上使用图像识别服务，APP 调用 IBCS 的图像识别 API 并返回结果给用户&lt;/td&gt;
      &lt;td&gt;密码模式（resource owner password credentials）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;内部服务的鉴权&lt;/td&gt;
      &lt;td&gt;图像识别服务向配置服务获取配置信息&lt;/td&gt;
      &lt;td&gt;客户端模式（client credentials），或简单的 HTTP Basic 验证&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;开发者：获取 IBCS 能力集&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;客户端模式（client credentials）或授权码模式（authorization code）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;开发者：创建第三方应用&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;客户端模式（client credentials）或授权码模式（authorization code）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;开发者：接入第三方应用&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt;客户端模式（client credentials）或授权码模式（authorization code）&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h4 id=&quot;5-客户端鉴权和用户鉴权&quot;&gt;5. 客户端鉴权和用户鉴权&lt;/h4&gt;

&lt;p&gt;服务鉴权，从形式上分为：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;非受控服务/接口，无须认证或鉴权；&lt;/li&gt;
  &lt;li&gt;客户端认证或鉴权（服务自身认证或鉴权）：客户端（服务）在访问另一个服务时，必须先表明客户端自己的身份；&lt;/li&gt;
  &lt;li&gt;业务认证或鉴权（用户认证或鉴权）：用户通过客户端（服务）访问某个资源时，必须验证用户自己的身份。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;例如，用户通过 APP 登录 IBCS 后，访问图像识别服务的过程：用户登录后获得 token，由 APP 携带此 token 向图像识别服务发起请求，图像识别服务首先调用 IDP 服务验证 APP 的身份（通过 ClientId 和 ClientSecret），然后再验证用户的身份（通过 token），如果 APP 和用户都获得授权，则请求通过，返回识别结果。&lt;/p&gt;

&lt;h4 id=&quot;6-跨域问题&quot;&gt;6. 跨域问题&lt;/h4&gt;

&lt;p&gt;浏览器的同源策略给 Web 应用划定了安全边界，是 Web 应用安全模型的重要基础。基于令牌的安全系统，在同源策略的约束下面临两个问题：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;跨域请求；&lt;/li&gt;
  &lt;li&gt;SSO 登录状态的跨域保持。&lt;/li&gt;
&lt;/ol&gt;

&lt;h5 id=&quot;1cors-方案&quot;&gt;1）CORS 方案&lt;/h5&gt;

&lt;p&gt;第一个问题，一般采用 CORS 方案，在服务端的响应头声明 Access-Control-Allow-Origin 参数即可解决跨域请求的问题。&lt;/p&gt;

&lt;h5 id=&quot;2同域-sso-方案&quot;&gt;2）同域 SSO 方案&lt;/h5&gt;

&lt;p&gt;第二个问题，在同域环境下，传统方法是采用 Cookie 的方案。跨域环境下，也有几种方案，从安全性和简便性考虑，推荐采用这种方案：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;根据业务需求将应用切分为同域 SSO 应用和跨域 SSO 应用两类；&lt;/li&gt;
  &lt;li&gt;将需要 SSO 状态保持的应用归到同域 SSO 应用中，将其他应用归到跨域 SSO 应用中；&lt;/li&gt;
  &lt;li&gt;对于同域 SSO 应用，一般是企业内部应用，或相关性较高的应用，这些应用的域名采用相同的父级域名，继续使用 Cookie 方案；&lt;/li&gt;
  &lt;li&gt;对于跨域 SSO 应用，不提供 SSO 状态保持。当用户首次打开此类应用时，由于 Cookie 无法跨域，因此服务端无法感知用户的登录状态，此时用户是处于未登录状态的；当用户访问受控页面或点击登录页面时，须重新执行登录操作。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;7-登出和关闭账户&quot;&gt;7. 登出和关闭账户&lt;/h4&gt;

&lt;p&gt;OAuth2.0 是集中式的令牌安全系统，可以通过撤销令牌登出系统。关闭账户与此类似。&lt;/p&gt;

&lt;h4 id=&quot;8-软件授权&quot;&gt;8. 软件授权&lt;/h4&gt;

&lt;p&gt;可在 IDP 服务或 API 网关增加规则过滤器，将商业授权策略应用到授权规则中。&lt;/p&gt;

&lt;h4 id=&quot;9-技术选型&quot;&gt;9. 技术选型&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;后续会写实践篇，敬请期待……&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;三将-jwt-应用到-oauth20-方案中&quot;&gt;三）将 JWT 应用到 OAuth2.0 方案中&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;JWT 是一种自包含的客户端令牌系统技术规范，这是其与 OAuth2.0 默认令牌的最大不同，在应用 JWT 时，有几个要点须加以说明。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;1-搭配-api-网关实现令牌撤销&quot;&gt;1. 搭配 API 网关实现令牌撤销&lt;/h4&gt;

&lt;p&gt;由于 JWT 属于自包含的客户端令牌系统，令牌发出后无须服务器验证，只需在客户端验证。客户端验证并解签后将得到必要的信息，例如用户基本信息和权限标识符。这种设计天然地存在无法撤销令牌的问题。解决方案是网关之外的外部服务继续使用 OAuth2.0 的 AccessToken，在网关以内的内部服务使用 JWT，这样做有几个好处：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;外部服务处在不安全的场景中，OAuth2.0 的 AccessToken 本身是一串无意义的字符串，并非结构化数据，这加强了令牌的安全性；&lt;/li&gt;
  &lt;li&gt;有效解决 JWT 无法撤销的问题：OAuth2.0 的 AccessToken 仍然是集中式的令牌系统，外部服务请求资源时会携带 AccessToken 经过网关的拦截。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;不过此方案仍然存在两个问题：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;将一定程度丧失 JWT 客户端解签的优势，因为外部服务仍然采用 OAuth2.0 的 AccessToken，但相较于传统的 Cookie + Session 方案，此方案更加轻巧，也更加符合微服务无状态 API 的风格；&lt;/li&gt;
  &lt;li&gt;对于已发出的 AccessToken，客户端在下一次请求之前，服务端的令牌系统对此没有控制能力，例如 SSOff 将无法很好地实现。&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;2-客户端认证鉴权和用户认证鉴权&quot;&gt;2. 客户端认证/鉴权和用户认证/鉴权&lt;/h4&gt;

&lt;p&gt;与 OAuth2.0 方案一致，客户端同样需要使用 ClientId 和 ClientSecret 认证/鉴权。&lt;/p&gt;

&lt;h4 id=&quot;3-公钥和密钥&quot;&gt;3. 公钥和密钥&lt;/h4&gt;

&lt;p&gt;JWT 一般采用非对称加密算法对 Header 和 Payload 进行签名，公钥可以公开存储在外部服务中，如 H5、APP。&lt;/p&gt;

&lt;h5 id=&quot;1非对称算法&quot;&gt;1）非对称算法&lt;/h5&gt;

&lt;p&gt;非对称算法的重要特点是，使用密钥加密时，必须用公钥解密；用公钥加密时，必须用密钥解密。利用此特性，通常在服务端采用密钥加密信息，然后客户端采用公钥解密信息。由于密钥存储在服务端，因此安全性高；公钥本身可以公开，因此可以在客户端存储。&lt;/p&gt;

&lt;h5 id=&quot;2公钥解密&quot;&gt;2）公钥解密&lt;/h5&gt;

&lt;p&gt;JWT 经由服务端用密钥加密附加签名后，发往客户端，客户端使用公钥进行解密验签，如果签名校验通过则信任该 JWT。JWT 包含了丰富的信息（通常是用户基本信息和权限标识符），验签通过后客户端完全可以信任此 JWT，因此不必再依赖于服务端重复鉴权。值的注意的是，JWT的前两部分 Header 和 Payload 仅采用 Base64 编码，因此与明文无异，不应在 JWT 中存储敏感信息。&lt;/p&gt;

&lt;h4 id=&quot;4-服务间认证鉴权&quot;&gt;4. 服务间认证/鉴权&lt;/h4&gt;

&lt;h5 id=&quot;1内部服务认证鉴权&quot;&gt;1）内部服务认证/鉴权&lt;/h5&gt;

&lt;p&gt;以 IBCS 为例，当图像识别服务服务携带 JWT 向配置服务请求资源时，配置服务使用公钥解密，配置服务完全可以信任图像识别服务，因此也不必再依赖于鉴权服务的重复认证/鉴权。对于安全性要求不高的场景，也可以使用 HTTP Basic 验证进行简单认证/鉴权甚至不认证/鉴权。&lt;/p&gt;

&lt;h5 id=&quot;2外部服务认证鉴权&quot;&gt;2）外部服务认证/鉴权&lt;/h5&gt;

&lt;p&gt;同样，外部的 APP 携带 Oauth AccessToken 向内网的图像识别服务发起请求时，属于外部服务向内网服务发起请求，必须经过 API 网关，由网关执行规则过滤和代理认证（网关向 IDP 请求校验令牌），确保 AccessToken 仍处于有效状态，如果 Token 通过检验，则由 IDP 生成包含权限标识（或Scope）的 JWT 返回给网关，网关拿到 JWT 后利用公钥进行解密，并校验权限标识（或Scope），通过后再携带此 JWT 向图像识别服务转发请求，图像识别服务收到请求后，利用公钥解密 JWT 并自行检验其有效性、获取用户信息等，无须再请求 IDP。&lt;/p&gt;

&lt;p&gt;必须留意，在网关拿到 JWT 以后，虽然可以连同 Oauth AccessToken 一起缓存，下次该服务再发起请求时，可直接转发 JWT 而不用再经由 IDP 的进行 JWT 令牌转换，但这么做有两个问题：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;在一定程度上，网关抢夺了 IDP 的鉴权工作；&lt;/li&gt;
  &lt;li&gt;如果网关不执行令牌有效性（过期时间等）的检验，而是简单通过 Oauth AccessToken 查找出 JWT，则丧失了 IDP 集中鉴权的优势。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;所以，综上所述，不建议网关缓存 JWT 并直接转发。&lt;/p&gt;

&lt;h4 id=&quot;5-跨域问题&quot;&gt;5. 跨域问题&lt;/h4&gt;

&lt;p&gt;与 OAuth2.0 的跨域解决方案一致。&lt;/p&gt;

&lt;h2 id=&quot;五总结&quot;&gt;五、总结&lt;/h2&gt;

&lt;p&gt;本文给出了微服务架构下的统一身份认证和授权的设计方案，从平台级 SaaS 模式下『统一身份治理』的概念出发，梳理了关键的需求点，提出了对应的解决方案。其中 OAuth2.0 是最佳解决方案，不过在实际运用中，应当遵循『合适原则』、『简单原则』和『演化原则』三个原则，不能盲目照搬。&lt;/p&gt;

&lt;h2 id=&quot;六参考链接&quot;&gt;六、参考链接&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;认证、授权、鉴权和权限控制， http://www.hyhblog.cn/2018/04/25/user_login_auth_terms&lt;/li&gt;
  &lt;li&gt;SaaS，http://www.ruanyifeng.com/blog/2017/07/iaas-paas-saas.html&lt;/li&gt;
  &lt;li&gt;SSO， https://www.cnblogs.com/EzrealLiu/p/5559255.html&lt;/li&gt;
  &lt;li&gt;CAS， https://apereo.github.io/cas/&lt;/li&gt;
  &lt;li&gt;UIMS， https://mtide.net/平台级SaaS架构的基础-统一身份管理系统.html&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Fri, 05 Oct 2018 00:00:00 +0000</pubDate>
        <link>https://mtide.net/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84%E7%BB%9F%E4%B8%80%E8%BA%AB%E4%BB%BD%E8%AE%A4%E8%AF%81%E4%B8%8E%E6%8E%88%E6%9D%83.html</link>
        <guid isPermaLink="true">https://mtide.net/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84%E7%BB%9F%E4%B8%80%E8%BA%AB%E4%BB%BD%E8%AE%A4%E8%AF%81%E4%B8%8E%E6%8E%88%E6%9D%83.html</guid>
        
        <category>微服务,身份治理</category>
        
        
        <category>ARCHITECTURE</category>
        
      </item>
    
      <item>
        <title>平台级SaaS架构的基础：统一身份管理系统</title>
        <description>&lt;p&gt;[TOC]&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;业内在用户统一身份认证及授权管理领域，主要关注 4 个方面：集中账号管理（Account）、集中认证管理（Authentication）、集中授权管理（Authorization）和集中审计管理（Audit）， 简称 4A 管理。后来发展了 IAM（Identity and Access Management，即身份识别与访问管理）的相关技术，在云计算等领域应用广泛。整体来说，不管是 4A 还是 IAM 还是未来可能的其他技术体系，都可以归纳为『统一身份治理』的范畴。本文基于『统一身份治理』的概念提出了统一身份管理系统（Unified Identity Management System）的设计思路。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;一统一身份管理系统unified-identity-management-system&quot;&gt;一、统一身份管理系统（Unified Identity Management System）&lt;/h2&gt;

&lt;p&gt;统一身份管理系统（简称 UIMS）可以简单理解为多租户软件架构的升级版，通常是整个平台帐号和权限管控的基础性系统，平台下所有系统的账户管理、身份认证、用户授权、权限控制等行为都必须经由该系统处理，提供帐号密码管理、基本资料管理、角色权限管理等功能。UIMS 基于『统一身份治理』的概念，可划分为两级账户体系、基础权限模块和基础信息模块三大模块。其中两级账户体系将账户分为组织实体帐号和个人实体账户两大类，个人实体从属于组织实体，也可以不从属任何组织实体，且个人实体可同时从属于多个组织实体；基础权限模块将各业务系统的资源权限进行统一管理和授权；基础信息模块用于描述组织实体和个人实体的基本信息，如组织实体名称、地址、法人，个人实体姓名、电话号码、性别等基础信息。UIMS 提供统一的 API 与各子系统连接。&lt;/p&gt;

&lt;p&gt;从整个平台的角度来看，UIMS 除了提供上述功能和服务，还应该满足以下需求：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;编号&lt;/th&gt;
      &lt;th&gt;需求&lt;/th&gt;
      &lt;th&gt;描述&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;软件授权&lt;/td&gt;
      &lt;td&gt;云平台付费授权机制，可按时间、功能、数量等进行付费授权&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;组织入驻&lt;/td&gt;
      &lt;td&gt;允许组织主动申请加入平台&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;实名认证&lt;/td&gt;
      &lt;td&gt;个人实名认证、组织实名认证&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;资质审核&lt;/td&gt;
      &lt;td&gt;个人和组织的资质审核，如对获得的证书或荣誉进行审核&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;组织绑定&lt;/td&gt;
      &lt;td&gt;个人账户绑定组织，与组织建立关联关系&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;组织解绑&lt;/td&gt;
      &lt;td&gt;个人账户与组织进行解绑&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;7&lt;/td&gt;
      &lt;td&gt;账户注销&lt;/td&gt;
      &lt;td&gt;个人账户注销，并销毁所有个人资料和档案&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt;统一登录&lt;/td&gt;
      &lt;td&gt;即 SSO&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;9&lt;/td&gt;
      &lt;td&gt;统一注册&lt;/td&gt;
      &lt;td&gt;提供统一的用户注册页面&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;因此，从功能的角度可以将 UIMS 划分为以下模块：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;一）功能

    系统设置 System Configuration
      系统标识管理 System Identifiers Management
      服务账户管理 Service Accounts Management
    
    账户实体管理 Account Entities Management
      组织实体管理 Organization Entities Management
      组织架构管理 Organization Management
      个体账户管理 Individual Accounts Management
    
    账户权限管理 Account Permissions Management
      用户组管理 User Group Management
      角色管理 User Roles Management
      资源权限管理 Permission Resources Management
      权限策略组管理 Permission Group Management
    
    认证审核管理 Certification Management
      个人认证管理 Individual Certification Management
      组织认证管理 Organization Certification Management
      资质审核管理 Qualification Management
    
    付费授权管理 Authorization Management
      组织授权管理 Organization Authorization Management
  
二）页面

    统一注册页面 Unified Signup Page
    统一登录页面 Unified Signin Page
    组织入驻页面 Organization Signup Page
    个人实名认证页面 Individual Authentication Page
    组织实名认证页面 Organization Authentication Page

三）API

    IDP（Identity Provider）相关的APIs
    业务相关的APIs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;其中组织绑定和解绑的功能，可以放到『组织实体管理』 或『个体账户管理』的功能中。需要注意的是，组织绑定与解绑功能，是否与业务系统关联，下文将进行阐述。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;二两级账户体系和基础权限模块&quot;&gt;二、两级账户体系和基础权限模块&lt;/h2&gt;

&lt;p&gt;基于『统一身份治理』的理念，采用两级账户体系（UIMS 提供接口）实现多系统融合的平台级 SaaS。两级账户体系将账户类别分为组织实体和个人实体两类（详见下文用户分类）。个人实体可以从属于组织实体（可以从属于多个组织实体），也可以不从属。个人账户体系和组织账户体系在云平台内享有的权限是不一样的，虽然大部分功能和服务两个体系的实体均可独立使用，互不干扰，但部分功能和服务有所不同。&lt;/p&gt;

&lt;h3 id=&quot;1-基本原则&quot;&gt;1. 基本原则&lt;/h3&gt;

&lt;p&gt;平台级 SaaS 模式账户体系应遵循以下几个基本原则：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;个人账户统一原则：个人账户一次注册，全平台通用，类似于全网通行证和 SSO，注册和登录都在 UIMS 进行。&lt;/li&gt;
  &lt;li&gt;业务权限独立原则：每个子系统的权限体系是独立管理的。『个人账户统一原则』明确了账户体系是统一的，但是对于每个子系统而言，每个账户所能使用的功能和服务，所能查看的数据权限是独立维护的，比如 XXX 公司（组织）-研发T3组（用户组）-张三（用户）-研发人员（角色），在 CRM 系统中，拥有的资源权限（详见下文），与其在 OA 系统中的所拥有的资源权限肯定是不一致的。&lt;/li&gt;
  &lt;li&gt;组织实体隔离原则：不同的组织实体之间，是相互隔离，独立管理的。每个组织实体可以自行组织自己的组织体系、账户体系和权限体系。不同的组织实体资源权限也是隔离的。&lt;/li&gt;
  &lt;li&gt;从属关系隔离原则（非强制）：个体账户与组织实体的从属关系是基于单独的业务系统存在的，『个人账户统一原则』明确的仅是个人账户的全网统一，但组织实体、从属关系并没有统一，并且是隔离的。比如在 CRM 系统中，张三（用户）从属于 XXXX 公司（组织），但在 OA 系统中，张三（用户）默认是不从属于任何组织的，从属关系受到具体业务系统的影响。事实上，这个原则是非强制的，具体取决于各自的业务逻辑和业务场景。如果要简化从属关系的管理，那么可以不遵循此原则，即个体账户与组织实体的从属关系是全平台统一的，与业务系统无关，但这会为降低平台的灵活性和扩展性。灵活性和复杂度之间通常要做一个取舍。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2-权限原则&quot;&gt;2. 权限原则&lt;/h3&gt;

&lt;p&gt;类似于 RBAC 原则，平台的权限体系采用 OS-RBAC 的概念：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;OS：O 代表 Organization 组织，S 代表 System 业务系统，即权限是受到组织实体和业务系统双重影响的。&lt;/li&gt;
  &lt;li&gt;RBAC：基于角色的访问控制。&lt;/li&gt;
  &lt;li&gt;OS-RBAC：组织实体-业务系统-用户-角色-权限标识。分为两种情况：一种是有从属组织的个人账户；另一种是无从属组织的个人账户，后者无组织，但同样遵守 RBAC 的权限限定，且其权限标识体系允许组织为空。&lt;/li&gt;
  &lt;li&gt;资源标识：分为逻辑资源和实体资源。逻辑资源如菜单、页面、表单、按钮组、按钮、字段等功能型资源，或人员档案、考勤记录、任务记录、位置数据、积分、电子钱包等数据资源；实体资源如椅子、凳子、电脑、车辆等实物资产，另外有时候部分逻辑资源也可以归纳为实体资源，如电子照片、视频文件、音乐文件等。&lt;/li&gt;
  &lt;li&gt;条件标识：权限的约束条件，主要有可见组织架构范围限定、时间限定、区域限定等。例如某权限仅财务部可见，有效期至11月2号，这里『财务部』属于可见组织架构范围限定，『至11月2号』则是时间限定。&lt;/li&gt;
  &lt;li&gt;权限标识：用于标识账户实体在指定的条件下拥有访问某项功能、查看某些数据的权限。资源标识和条件标识与权限标识关联，权限标识与角色关联，角色与用户关联。例如张三（用户）-研发人员（角色）-拥有『研发部』所有人员档案的增上改查权限。&lt;/li&gt;
  &lt;li&gt;业务系统标识符：受『业务权限独立原则』的约束，与传统的资源权限有所不同的是，所有权限标识都与具体的业务系统关联，例如企业CRM系统就是一个业务系统，具体的权限标识与业务系统有直接的关系，例如菜单、表单、页面、按钮、图片等资源。&lt;/li&gt;
  &lt;li&gt;权限策略组：权限策略组是在 OG-RBAC 基础上设置的，为简化权限配置的一种辅助手段，在实际应用中可以不创建策略组。策略组分为平台级策略组和业务系统级别的策略组，两种策略组的作用域仅限于相同组织实体内部，但对于无从属组织的个人账户除外。策略组与角色类似，可以将资源权限绑定到策略组中，但不同之处是，平台级策略组可以横跨业务系统进行平台级的资源权限绑定。因为账户体系跨越多个子系统，在遵循『业务权限独立原则』的限定下，每个子系统都需要做一套权限配置，操作上较为繁琐，因此充分运用策略组可以大大简化权限配置工作。平台可以内置多套常用的策略组，终端用户可以直接选用策略组，也可以基于某个策略组为基础，进行修改。值得注意的是，策略组的作用域仅限于相同组织实体内部，即策略组可以横跨业务系统，但不能同时作用于多个组织实体。&lt;/li&gt;
  &lt;li&gt;权限交集：与 RBAC2 的静态职责分离-角色互斥原则相反，平台采用多角色权限并集的设计。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;http://public-md.oss-cn-shenzhen.aliyuncs.com/18-12-26/81410855.jpg&quot; alt=&quot;RBAC模型&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;『权限标识』示例：在企业CRM系统[1]中，在2019年3月5号以前[2]，对百度科技[3]，研发中心[4]，在广东区域[5]的所有人事档案[6]拥有只读权限[7]。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
  &lt;li&gt;[1]业务系统标识；&lt;/li&gt;
  &lt;li&gt;[2]条件标识：时间限定；&lt;/li&gt;
  &lt;li&gt;[3]组织实体标识；&lt;/li&gt;
  &lt;li&gt;[4]条件标识：可见组织架构范围限定；&lt;/li&gt;
  &lt;li&gt;[5]条件标识：区域范围限定；&lt;/li&gt;
  &lt;li&gt;[6]资源标识；&lt;/li&gt;
  &lt;li&gt;[7]权限类型。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;3-从属关系梳理&quot;&gt;3. 从属关系梳理&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;为简单起见，我们将不遵守『从属关系隔离原则』，即用户实体与组织实体的从属关系与业务系统无关。系统涉及的实体类型有：&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;业务系统（系统标识）&lt;/li&gt;
  &lt;li&gt;服务账户（客户端）&lt;/li&gt;
  &lt;li&gt;个人账户实体&lt;/li&gt;
  &lt;li&gt;组织账户实体&lt;/li&gt;
  &lt;li&gt;组织架构&lt;/li&gt;
  &lt;li&gt;用户组（非必选项）&lt;/li&gt;
  &lt;li&gt;角色实体&lt;/li&gt;
  &lt;li&gt;权限实体&lt;/li&gt;
  &lt;li&gt;资源实体&lt;/li&gt;
  &lt;li&gt;限定条件实体&lt;/li&gt;
  &lt;li&gt;权限策略组（非必选项）&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;31-与组织实体强关联的实体&quot;&gt;3.1 与组织实体强关联的实体&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;基于『组织实体隔离原则』，这类实体类型不能脱离组织实体独立存在。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;组织架构&lt;/li&gt;
  &lt;li&gt;角色实体&lt;/li&gt;
  &lt;li&gt;权限实体&lt;/li&gt;
  &lt;li&gt;资源实体&lt;/li&gt;
  &lt;li&gt;限定条件实体&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;由于组织架构不能脱离组织实体单独存在，因此当用户实体绑定组织架构时，该用户实体必须隶属于该组织架构所从属的组织实体。同理可知以下从属关系遵从同样的约束——即每对关系的两个实体对象必须属于相同的组织实体：&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;用户与角色&lt;/li&gt;
  &lt;li&gt;角色与权限&lt;/li&gt;
  &lt;li&gt;资源与权限&lt;/li&gt;
  &lt;li&gt;限定条件与权限&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;32-与业务系统强关联的实体&quot;&gt;3.2 与业务系统强关联的实体&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;基于『业务系统隔离原则』，这类实体类型不能脱离业务系统独立存在。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;权限实体&lt;/li&gt;
  &lt;li&gt;资源实体&lt;/li&gt;
  &lt;li&gt;限定条件实体&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;4-实体类型&quot;&gt;4. 实体类型&lt;/h3&gt;

&lt;p&gt;基于以上各项原则，实体类型又分为以下几种情况：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;组织实体（未认证）：在组织实体的模式下，可以按照组织的管理要求，独立设置一套组织架构、账户和数据权限体系，比如设置下属企业、分公司、部门、岗位职务、角色权限，组织实体缺省分配一个管理员帐户，拥有全部权限，由管理员初始化配置信息。&lt;/li&gt;
  &lt;li&gt;组织实体（已认证）：拥有未认证组织实体的所有权利，但已认证的实体通常拥有更多的配额更少的功能限制，此外有些特定的业务功能和业务流程，必须是实名认证的实体才能使用，比如支付和交易。&lt;/li&gt;
  &lt;li&gt;个人实体（未认证）：在个人实体的模式下，享受的权利由具体的业务系统决定，原则上个人实体作为独立的账户类型，应该享有基本的功能权限和数据权限，如个人中心的各项功能等。&lt;/li&gt;
  &lt;li&gt;个人实体（已认证）：与组织实体（已认证）类似。&lt;/li&gt;
  &lt;li&gt;个人实体（未从属于组织）：未从属组织的个人实体账户，与上述个人实体类型一致。&lt;/li&gt;
  &lt;li&gt;个人实体（从属单个组织）：从属单个组织的个人实体账户，除了具备个人实体账户的原本权利外，还受到组织权限的约束，原本个人实体不享受的权利，可能现在可以享受，原本享受的权利，可能现在不可以享受了。&lt;/li&gt;
  &lt;li&gt;个人实体（从属多个组织）：当个人实体账户从属于多个组织时，除了个人账户原本拥有的权利外，所从属的组织所带来的权利须遵循『组织实体隔离原则』，且受到『从属关系隔离原则』的约束，具体的权利配置由各个业务系统独立管理。这里有两种情况：一是在用户登录时，必须选择所属的组织机构，类似于 LOL 游戏，在登录时须选择所属的区域和服务器；二是在用户登录后，可以自由选择组织实体，类似于阿里云或华为云的区域选择，在用户未选择所属组织时，应当按照未从属于组织的个人实体账户对待。&lt;/li&gt;
  &lt;li&gt;组织管理员：组织管理员拥有该组织内部的全部资源权限，例如可以创建个人账户，在个人未完成首次登录前，可以删除（解雇），修改，在个人完成登录后，则权限移交给了个人；删除（解雇）时，只是个人脱离组织，个人不再拥有组织员工的权限，在组织内的个人工作经历仍然保留，组织清除离职员工，则这些在职经历将不为企业可管理，但个人自己可见，不可变更。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5-用户分类&quot;&gt;5. 用户分类&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://mtide.net/assets/images/post/20181004/1.png&quot; alt=&quot;用户分类&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;6-组织分类&quot;&gt;6. 组织分类&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;https://mtide.net/assets/images/post/20181004/2.png&quot; alt=&quot;组织分类&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;三基础信息模块&quot;&gt;三、基础信息模块&lt;/h2&gt;

&lt;p&gt;基础信息，主要针对个人实体和组织实体，如企业工商信息、通用信息等要满足灵活扩展的需求，实体的类型种类繁多，随着业务场景的变化，信息结构的变化也可能比较频繁。在技术上建议采用以下两种方式应对：&lt;/p&gt;

&lt;h3 id=&quot;1-eav-数据模型&quot;&gt;1. EAV 数据模型&lt;/h3&gt;

&lt;p&gt;EAV 即 Entity（实体）-Attribute（属性）-Value（值）数据模型，将传统的 ORM 映射模型——即实体属性与数据库表字段一一对应的模型，变换为实体属性与数据表的行记录一一对应的模型。EAV 模型大大增加了数据映射和相关业务逻辑的复杂程度，但是具备高度的灵活性，能够满足随时变化的信息结构，满足动态变更的实体结构、满足字段级权限控制、满足字段级数据版本历史等功能。&lt;/p&gt;

&lt;h3 id=&quot;2-采用松散型数据结构的数据库方案&quot;&gt;2. 采用松散型数据结构的数据库方案&lt;/h3&gt;

&lt;p&gt;其中的代表便是 MongoDB：一个介于关系数据库和非关系数据库之间的分布式文件存储数据库产品，在 CAP 理论中属于 CP 范畴，支持松散数据结构，支持复杂的混合数据类型，支持 JSON 和文档存储。采用此方案的优势比较明显，除了能够满足 EAV 模型所具备的大部分功能外，还大大简化了技术复杂度，支持分布式部署，推荐采用此方案。&lt;/p&gt;

&lt;h3 id=&quot;3-信息分类&quot;&gt;3. 信息分类&lt;/h3&gt;

&lt;p&gt;平台的信息主要分为基础信息和业务信息两大类。基础信息分为个人实体信息和组织实体信息，主要描述实体的基本信息、通用信息，与业务相关性不大，例如姓名、性别、身份证号码、手机号码、企业通用信息、企业工商信息等。业务信息由各业务系统自行管理和维护，UIMS 不涉及。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;实体类别&lt;/th&gt;
      &lt;th&gt;信息类别&lt;/th&gt;
      &lt;th&gt;信息范围&lt;/th&gt;
      &lt;th&gt;备注&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;个人信息&lt;/td&gt;
      &lt;td&gt;基础信息&lt;/td&gt;
      &lt;td&gt;昵称、性别&lt;/td&gt;
      &lt;td&gt;默认公开&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;个人信息&lt;/td&gt;
      &lt;td&gt;基础信息&lt;/td&gt;
      &lt;td&gt;身份证信息、籍贯、性别、出生日期、学历、工作履历、电话号码、通信地址、照片、银行卡号&lt;/td&gt;
      &lt;td&gt;须用户授权收集和公开&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;个人信息&lt;/td&gt;
      &lt;td&gt;业务信息&lt;/td&gt;
      &lt;td&gt;LBS数据&lt;/td&gt;
      &lt;td&gt;须用户授权收集和公开&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;个人信息&lt;/td&gt;
      &lt;td&gt;业务信息&lt;/td&gt;
      &lt;td&gt;用户移动终端的设备信息，包括IP地址、Mac地址、操作系统信息、设备型号、识别码等&lt;/td&gt;
      &lt;td&gt;须用户授权收集和公开&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;个人信息&lt;/td&gt;
      &lt;td&gt;业务信息&lt;/td&gt;
      &lt;td&gt;用户的行为信息，包括操作记录，cookies，通过平台编辑或传送的文字、图片、语音或视频信息等&lt;/td&gt;
      &lt;td&gt;须用户授权收集和公开&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;个人信息&lt;/td&gt;
      &lt;td&gt;业务信息&lt;/td&gt;
      &lt;td&gt;用户喜好、特长、手工标签、自动标签、社交互动信息等&lt;/td&gt;
      &lt;td&gt;须用户授权收集和公开&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;组织信息&lt;/td&gt;
      &lt;td&gt;基础信息&lt;/td&gt;
      &lt;td&gt;组织工商信息：名称、法人、营业范围、注册日期、注册资本、通信地址、工商注册号、公司类型、纳税人识别号&lt;/td&gt;
      &lt;td&gt;默认公开，自动审核&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;组织信息&lt;/td&gt;
      &lt;td&gt;基础信息&lt;/td&gt;
      &lt;td&gt;组织介绍、品牌介绍、微信公众号、企业官网、对外联络电话、客服电话&lt;/td&gt;
      &lt;td&gt;默认公开，须人工审核&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;组织信息&lt;/td&gt;
      &lt;td&gt;基础信息&lt;/td&gt;
      &lt;td&gt;企业资质、股权结构、对外投资、工商登记变更记录、企业年报、公司发展历程、行政许可&lt;/td&gt;
      &lt;td&gt;须组织授权收集和公开&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;组织信息&lt;/td&gt;
      &lt;td&gt;基础信息&lt;/td&gt;
      &lt;td&gt;核心团队和成员、融资历程、核心产品、公司规模、知识产权&lt;/td&gt;
      &lt;td&gt;须组织授权收集和公开&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;组织信息&lt;/td&gt;
      &lt;td&gt;基础信息&lt;/td&gt;
      &lt;td&gt;组织架构、组织成员档案、司法风险、法律诉讼&lt;/td&gt;
      &lt;td&gt;须组织授权收集和公开&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;所有与信息收集、储存、处理及数据安全有关的书面政策，应当出具《隐私政策》并进行声明。部分组织信息由于可在网上公开查到，且是法定必须公布的信息，因此可以默认公开。&lt;/p&gt;

&lt;h2 id=&quot;四其他功能&quot;&gt;四、其他功能&lt;/h2&gt;

&lt;h3 id=&quot;一软件授权&quot;&gt;一）软件授权&lt;/h3&gt;

&lt;p&gt;基于两级账户体系，建立云平台付费授权机制，针对用户账户和组织账户进行独立授权。根据产品的商业策略，可执行灵活的付费模式：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;时效限制：年付、季付、月付，不同时效费用不同。&lt;/li&gt;
  &lt;li&gt;功能限制：授权不同的功能，费用不同。&lt;/li&gt;
  &lt;li&gt;数量限制：最大组织数量限制、最大用户数量限制，不同的数量费用不同。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;二组织入驻&quot;&gt;二）组织入驻&lt;/h3&gt;

&lt;p&gt;UIMS 应提供一个组织实体注册登记的流程，允许组织主动提交基本信息，开户入驻平台。此外，应提供在管理后台手工录入组织开户的功能。&lt;/p&gt;

&lt;h3 id=&quot;三实名认证&quot;&gt;三）实名认证&lt;/h3&gt;

&lt;p&gt;分为个人账户实名认证和组织账户实名认证，尽量通过技术手段自动执行实名认证的审核过程，减少甚至取消人工干预。UIMS 应提供实名认证的功能和流程。&lt;/p&gt;

&lt;h3 id=&quot;四资质审核&quot;&gt;四）资质审核&lt;/h3&gt;

&lt;p&gt;资质审核分为两部分：一是部分实体实名认证过程中的人工核查；二是对实体提交的额外资质进行技术或人工审核。&lt;/p&gt;

&lt;h3 id=&quot;五组织绑定&quot;&gt;五）组织绑定&lt;/h3&gt;

&lt;p&gt;基于『从属关系隔离原则』，个人账户应在具体的业务系统中绑定组织账户，绑定过程分为两种类型：一是由组织管理员手工创建的从属个人账户，另一个是个人账户申请加入某个组织。业务系统应该提供此功能和流程。例如，个人注册帐号后，可主动登记绑定组织，对已注册登记的组织则要该组织管理员审核，未在系统中注册登记的组织，则始终处于待审核状态。&lt;/p&gt;

&lt;h3 id=&quot;六组织解绑&quot;&gt;六）组织解绑&lt;/h3&gt;

&lt;p&gt;允许个人账户解除与组织之间的从属关系。解绑分为两种情况：一是个人账户主动解除关系，二是组织管理员解绑、解雇或清除雇员（个人账户）。其中第一种个人解绑的，应当由组织进行审核批准，个人申请解除绑定关系，组织进行审核，但是是否需要审核，应交由具体的业务系统自行决定。&lt;/p&gt;

&lt;h3 id=&quot;七间接雇佣从属关系&quot;&gt;七）间接雇佣（从属）关系&lt;/h3&gt;

&lt;p&gt;雇佣（从属）关系分为直接雇佣与间接雇佣关系。例如保安员在某保安公司入职（直接雇佣），在某物业作保安（间接雇佣）。考虑两种办法标识间接雇佣关系：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;增加服务单位（项目点、物业社区）的实体概念&lt;/li&gt;
  &lt;li&gt;利用组织内部的组织机构体系，将间接雇佣单位作为当前组织的分支机构进行处理。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;八账户注销&quot;&gt;八）账户注销&lt;/h3&gt;

&lt;p&gt;分为个人账户的注销和组织账户的注销。UIMS 应提供相应的页面完成账户注销的操作。&lt;/p&gt;

&lt;h3 id=&quot;九私有化部署&quot;&gt;九）私有化部署&lt;/h3&gt;

&lt;p&gt;原则上拒绝私有化部署，但对于特定的客户，考虑私有化部署。私有化部署须考虑版本升级问题，在软件架构设计时，尽量遵循业务系统和技术系统分离的原则，并抽离公共模块，最大限度为私有部署的版本提供升级服务。&lt;/p&gt;

&lt;h2 id=&quot;五总结&quot;&gt;五、总结&lt;/h2&gt;

&lt;p&gt;总体来说，统一身份管理系统要做的事情有这么几件：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;定义实体&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;业务系统实体&lt;/li&gt;
  &lt;li&gt;服务账户实体（客户端）&lt;/li&gt;
  &lt;li&gt;组织实体&lt;/li&gt;
  &lt;li&gt;组织架构&lt;/li&gt;
  &lt;li&gt;个人实体&lt;/li&gt;
  &lt;li&gt;角色实体&lt;/li&gt;
  &lt;li&gt;权限标识&lt;/li&gt;
  &lt;li&gt;资源标识&lt;/li&gt;
  &lt;li&gt;条件标识&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
  &lt;li&gt;处理上述各实体之间的关系，并提供数据结构&lt;/li&gt;
  &lt;li&gt;提供 IDP APIs 和业务 APIs&lt;/li&gt;
  &lt;li&gt;提供其他功能：统一注册功能（页面和流程）、统一登录功能、软件授权、组织入驻、组织绑定/解绑、资质审查。&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Thu, 04 Oct 2018 00:00:00 +0000</pubDate>
        <link>https://mtide.net/%E5%B9%B3%E5%8F%B0%E7%BA%A7SAAS%E6%9E%B6%E6%9E%84%E7%9A%84%E5%9F%BA%E7%A1%80-%E7%BB%9F%E4%B8%80%E8%BA%AB%E4%BB%BD%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F.html</link>
        <guid isPermaLink="true">https://mtide.net/%E5%B9%B3%E5%8F%B0%E7%BA%A7SAAS%E6%9E%B6%E6%9E%84%E7%9A%84%E5%9F%BA%E7%A1%80-%E7%BB%9F%E4%B8%80%E8%BA%AB%E4%BB%BD%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F.html</guid>
        
        <category>SaaS,身份治理</category>
        
        
        <category>ARCHITECTURE</category>
        
      </item>
    
      <item>
        <title>mysql 主从复制配置实例</title>
        <description>&lt;blockquote&gt;
  &lt;p&gt;mysql 主从复制配置，主要用于双（多）机热备份，所有读写操作都在主库上执行，从库只对主库的数据进行复制。而读写分离配置，除了具备主从备份的功用，更重要的是读和写操作的分离，即主库只写，从库只读，从而减轻了单机的I/O压力。值得注意的是，主从备份不能保证历史数据的备份，因此日常的离线备份还是应该执行。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;基本信息&quot;&gt;基本信息&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;项&lt;/th&gt;
      &lt;th&gt;说明&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;系统版本&lt;/td&gt;
      &lt;td&gt;centos 7 x64&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;mysql版本&lt;/td&gt;
      &lt;td&gt;5.6.35&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;master机&lt;/td&gt;
      &lt;td&gt;192.168.100.201&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;slave机&lt;/td&gt;
      &lt;td&gt;192.168.100.202&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;操作步骤&quot;&gt;操作步骤&lt;/h3&gt;

&lt;h4 id=&quot;1-在master主库上的配置&quot;&gt;1. 在master主库上的配置&lt;/h4&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vim /etc/my.cnf

&lt;span class=&quot;c&quot;&gt;# 在 [mysqld] 下添加如下配置&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 给服务器起一个唯一的id&lt;/span&gt;
server-id&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1

&lt;span class=&quot;c&quot;&gt;# 开启二进制日志&lt;/span&gt;
log-bin&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;mysql-binlog

&lt;span class=&quot;c&quot;&gt;# 指定日志格式&lt;/span&gt;
binlog-format&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;mixed

&lt;span class=&quot;c&quot;&gt;# 重启mysql&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;service mysql restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;2-在slave从库上的配置&quot;&gt;2. 在slave从库上的配置&lt;/h4&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vim /etc/my.cnf

&lt;span class=&quot;c&quot;&gt;# 在 [mysqld] 下添加如下配置&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 给服务器起一个唯一的id&lt;/span&gt;
server-id&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;2

&lt;span class=&quot;c&quot;&gt;# 从服务器中继日志&lt;/span&gt;
relay-log&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;mysql-relaylog

&lt;span class=&quot;c&quot;&gt;# 重启mysql&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;service mysql restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;3-在master主机上分配从库复制的帐号权限&quot;&gt;3. 在master主机上分配从库复制的帐号权限&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&quot;language-mysql&quot;&gt;GRANT REPLICATION CLIENT, REPLICATION SLAVE on *.* to USERNAME@&apos;192.168.100.202&apos; IDENTIFIED BY &apos;PASSWORD&apos;;

# 其中是 USERNAME 是帐号名，PASSWORD 是密码
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&quot;4-在master主机检查master状态&quot;&gt;4. 在master主机检查master状态&lt;/h4&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 登录mysql后执行&lt;/span&gt;

show master status&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-binlog.000001 |      354 |              |                  |
+------------------+----------+--------------+------------------+
1 row &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0.00 sec&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;记住 File 和 Position 两项值，此处分别为 mysql-binlog.000001 和 354。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;注意，此命令执行后，不要再操作master主机，防止状态值变化。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;5-在slave从主机上配置&quot;&gt;5. 在slave从主机上配置&lt;/h4&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 登录mysql后执行&lt;/span&gt;

change master to 
&lt;span class=&quot;nv&quot;&gt;master_host&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;’192.168.100.201’, 
&lt;span class=&quot;nv&quot;&gt;master_user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;’USERNAME’, 
&lt;span class=&quot;nv&quot;&gt;master_password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;’PASSWORD’, 
&lt;span class=&quot;nv&quot;&gt;master_log_file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;’mysql-binlog.000001’, 
&lt;span class=&quot;nv&quot;&gt;master_log_pos&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;354&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 启动slave主库&lt;/span&gt;
slave start

&lt;span class=&quot;c&quot;&gt;# 此处 master_log_file 和 master_log_pos 即上文中的 File 和 Position值。&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;6-在slave从库上进行状态检查&quot;&gt;6. 在slave从库上进行状态检查&lt;/h4&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 登录mysql后执行&lt;/span&gt;

show slave status&lt;span class=&quot;se&quot;&gt;\G&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;***************************&lt;/span&gt; 1. row &lt;span class=&quot;k&quot;&gt;***************************&lt;/span&gt;
               Slave_IO_State: Waiting &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;master to send event
                  Master_Host: 192.168.100.201
                  Master_User: backup
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-binlog.000001
          Read_Master_Log_Pos: 354
               Relay_Log_File: mysql-relaylog.000002
                Relay_Log_Pos: 286
        Relay_Master_Log_File: mysql-binlog.000001
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB:
          Replicate_Ignore_DB:
           Replicate_Do_Table:
       Replicate_Ignore_Table:
      Replicate_Wild_Do_Table:
  Replicate_Wild_Ignore_Table:
                   Last_Errno: 0
                   Last_Error:
                 
                          ...
                          ...
                 
1 row &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;0.00 sec&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;c&quot;&gt;# 其中，重点看以下两项配置：&lt;/span&gt;

 Slave_IO_Running: Yes
Slave_SQL_Running: Yes

此两项必须都为YES，才说明主从配置成功。

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Fri, 19 May 2017 00:00:00 +0000</pubDate>
        <link>https://mtide.net/mysql-%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E9%85%8D%E7%BD%AE%E5%AE%9E%E4%BE%8B.html</link>
        <guid isPermaLink="true">https://mtide.net/mysql-%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E9%85%8D%E7%BD%AE%E5%AE%9E%E4%BE%8B.html</guid>
        
        
        <category>ARCHITECTURE</category>
        
      </item>
    
      <item>
        <title>分布式系统概念介绍</title>
        <description>&lt;h3 id=&quot;1cap&quot;&gt;1、CAP&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Consistency 一致性&lt;/li&gt;
  &lt;li&gt;Availability 可用性&lt;/li&gt;
  &lt;li&gt;Partition tolerance 分区容错性&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CA  单点集群 PostgreSQL MySql&lt;br /&gt;
CP  MongoDB redis  HBase MemcacheDB&lt;br /&gt;
AP  CouchDB&lt;br /&gt;
CAP 木有&lt;/p&gt;

&lt;h3 id=&quot;2base&quot;&gt;2、BASE&lt;/h3&gt;
&lt;p&gt;满足 CAP 的最低要求&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Basically Available      基本可用。支持分区失败&lt;/li&gt;
  &lt;li&gt;Soft state               软状态 状态可以有一段时间不同步，异步。&lt;/li&gt;
  &lt;li&gt;Eventually consistent    最终一致，最终数据是一致的就可以了，而不是时时高一致。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;3acid&quot;&gt;3、ACID&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Atomicity原子性       操作要么全部完成，要么全部不完成。&lt;/li&gt;
  &lt;li&gt;Consistency一致性     把系统从一个有效状态带入另一个有效状态的操作属性。如果某个操作使系统出现不一致，则操作不会执行或操作被回退。即操作前后系统状态必须一致。&lt;/li&gt;
  &lt;li&gt;Isolation隔离层       两个操作的执行互不干扰。同时在一个对象上不会出现两个写动作，写动作会一个接一个地发生，即串行化。&lt;/li&gt;
  &lt;li&gt;Durability           持久性，数据一旦写入，确保可以读回并且不会在系统正常操作一段时间后丢失。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;4quorum-nrw-模型&quot;&gt;4、Quorum NRW 模型&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;N 存储备份的节点数&lt;/li&gt;
  &lt;li&gt;R 读取最新数据所需要操作的最小节点数&lt;/li&gt;
  &lt;li&gt;W 写操作成功所需要操作的最小节点数&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;强一致性： R+W&amp;gt;N&lt;br /&gt;
弱一致性： R+W&amp;lt;=N&lt;/p&gt;
</description>
        <pubDate>Wed, 14 Sep 2016 00:00:00 +0000</pubDate>
        <link>https://mtide.net/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%E6%A6%82%E5%BF%B5%E4%BB%8B%E7%BB%8D.html</link>
        <guid isPermaLink="true">https://mtide.net/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%E6%A6%82%E5%BF%B5%E4%BB%8B%E7%BB%8D.html</guid>
        
        
        <category>BIGDATA</category>
        
      </item>
    
      <item>
        <title>hadoop 生态圈介绍</title>
        <description>&lt;h3 id=&quot;简介&quot;&gt;简介&lt;/h3&gt;
&lt;p&gt;Hadoop 是一个能够处理海量数据的分布式系统基础软件框架，理论上能够通过增加计算节点以处理无限增长的数据，由java写成。其作者是 Doug Cutting，得益于谷歌的Map/Reduce计算模型和GFS分布式文件系统，Hadoop实现了其核心组件HDFS和MapReducce。Hadoop 是目前世界上大数据行业的主流软件框架。其生态圈非常庞大，并且社区很活跃。Hadoop本身仅有hadoop-common + hdfs + mapreduce 组成，hadoop2.x以后增加了yarn体系。&lt;/p&gt;

&lt;h3 id=&quot;hadoop-的集群模式&quot;&gt;hadoop 的集群模式&lt;/h3&gt;
&lt;p&gt;hadoop 集群，从细的方面来说，总共分为四种模式：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Single Node Cluster，即伪分布式模式（单机模式）&lt;/li&gt;
  &lt;li&gt;Full Distributed Cluster，即完全分布式集群模式&lt;/li&gt;
  &lt;li&gt;HA Cluster，即高可用集群模式&lt;/li&gt;
  &lt;li&gt;HA + Federation Cluster，即高可用联邦集群模式&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;每一种方式都有其特定的使用场景，但一般数据规模很大的企业，都会选择HA+Federation模式，下面简要地介绍一下每一种模式的情况：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;单机模式，将hadoop安装在一台机器上，通过进程来模拟各主机节点的协作和运行，其可靠性、稳定性都是非常差的，并且具备糟糕的性能效率，没有团队会在生产环境使用它。那么它是否就没有用呢？也不是的，通常使用这种模式进行开发和调试工作。&lt;/li&gt;
  &lt;li&gt;完全分布式模式，将hadoop部署在至少两台机子上，数据块副本的数量通常也设置为2以上。该模式的集群，无论规模多大，只拥有1台Namenode节点，且也是唯一Active的工作节点。Namenode（简称NN）相当于hadoop文件系统的管家，对集群的所有文件访问和操作都经由NN统一协调管理。可想，当集群规模越来越庞大时，仅有一台NN，必定是不堪重负，那么它很容易就会挂掉，一旦挂掉，不仅集群立即瘫痪，还很容易造成数据丢失。另外，该模式通常ResourceManager（RM）也仅部署1台，ResourceManager是yarn的管家，主要管理任务的执行，例如MapReduce任务。与NN类似，当集群提交的作业过于繁重时，其同样面临超负载的问题。那么此模式是否也无用武之地呢？也不是的，视业务、资金等情况而定，因为该模式日后也可以安全升级成高可用模式。&lt;/li&gt;
  &lt;li&gt;高可用模式，分为NN的高可用和RM的高可用。在完全分布式的基础上，增加备用NN和RM节点。NN高可用，也就是集群里面会部署两台NN（最多也只能两台），以形成主备NN节点，达到高可用的目的。RM高可用与NN高可用类似，也是在集群里部署备用RM节点。不过此种模式下集群里面依然只有一台NN/RM处于Active工作状态，另一台则处于Standby的等待状态。当Active的NN/RM出现问题无法工作时，Standby的那台则立即无缝切入，继续保障集群正常运转。这种模式是很多企业都使用的，但是依然有缺陷。什么缺陷呢？虽然集群的可用性问题解决了，但是性能瓶颈依然存在——仅有一台NN/RM，由于无法横向扩展，其很可能会超负载运行。&lt;/li&gt;
  &lt;li&gt;高可用联邦模式，解决了单纯HA模式的性能瓶颈。单纯的HA模式NN和RM之间虽然配置了HA，但是依旧仅有一台NN或RM同时运行，这可能会导致了NN或RM的负载过重，从而造成整个集群的性能瓶颈。而联邦模式将整个HA集群再划分为两个以上的集群，不同的集群之间通过Federation进行连接，不同集群间可以共享数据节点，也可以不共享，可以互相访问和操作数据，也可以不。这样便做到了HA集群的横向扩展，从而移除了单纯HA模式同时仅有1台NN/RM工作所带来的性能瓶颈。Federation模式，相当于在多个集群之上又构建了一个集群层次，从数据访问的角度看，也可以简单的将其理解为一台路由器，而每一个HA集群则是单独的网络，不同网络间通过Federation路由器进行沟通。此模式是目前hadoop生态中最高的一种模式，适用于规模较大的企业。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;组件体系&quot;&gt;组件体系&lt;/h3&gt;

&lt;p&gt;hadoop
hdfs
mapreduce
yarn
zookeeper
hive
hbase
sqoop
pig
Impala
dubbo
kudu
storm
kafka
flume
elasticsearch elk
mahout
ambari
avro
cassandra
chukwa
tez&lt;/p&gt;

&lt;p&gt;spark
spark-streaming
spark-sql
spark-MLlib&lt;/p&gt;

&lt;h3 id=&quot;概念&quot;&gt;概念&lt;/h3&gt;

&lt;p&gt;hadoop hdfs mapreduce fsimage edits namenode-metadata
yarn zookeeper
Namenode Secondary-Namenode Datanode
ResourceManager(JobTracker)
NodeManager(TaskTracker)
ApplicationManager
ApplicationMaster
Container
JobHistoryServer
JobClient
JournalNode&lt;/p&gt;

&lt;p&gt;hadoop1.x hadoop2.x  HA federation&lt;/p&gt;
</description>
        <pubDate>Wed, 14 Sep 2016 00:00:00 +0000</pubDate>
        <link>https://mtide.net/hadoop-%E7%94%9F%E6%80%81%E5%9C%88%E4%BB%8B%E7%BB%8D.html</link>
        <guid isPermaLink="true">https://mtide.net/hadoop-%E7%94%9F%E6%80%81%E5%9C%88%E4%BB%8B%E7%BB%8D.html</guid>
        
        
        <category>BIGDATA</category>
        
      </item>
    
      <item>
        <title>hadoop HA高可用集群模式搭建指南</title>
        <description>&lt;h3 id=&quot;简述&quot;&gt;简述&lt;/h3&gt;
&lt;hr /&gt;
&lt;p&gt;hadoop 集群一共有4种部署模式，详见&lt;a href=&quot;http://www.jianshu.com/p/c3a834e45ae3&quot;&gt;《hadoop 生态圈介绍》&lt;/a&gt;。HA模式的集群里面会部署两台NN（最多也只能两台），以形成主备NN节点，达到高可用的目的。两台NN之间同步数据有两种方法：QJM和NFC。本文选择QJM方式，下文中出现的 journalnode 即为QJM模式下的进程。另外还可以配置两台ResourceManager，形成主备RM节点，从而达到yarn集群的高可用。该模式下的集群配置是在完全分布式模式的基础上做了部分调整。&lt;/p&gt;

&lt;p&gt;所有四种模式的部署指南见：  &lt;br /&gt;
&lt;a href=&quot;http://www.jianshu.com/p/38a94bade2b4&quot;&gt;hadoop 伪分布式搭建指南&lt;/a&gt;   &lt;br /&gt;
&lt;a href=&quot;http://www.jianshu.com/p/3a16f8ecf883&quot;&gt;hadoop 完全分布式搭建指南&lt;/a&gt;   &lt;br /&gt;
&lt;a href=&quot;http://www.jianshu.com/p/8a8fb958f11f&quot;&gt;hadoop HA高可用集群模式搭建指南&lt;/a&gt;  &lt;br /&gt;
&lt;a href=&quot;http://www.jianshu.com/p/ccee07a31ca9&quot;&gt;hadoop HA+Federation（联邦）模式搭建指南&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;搭建过程&quot;&gt;搭建过程&lt;/h3&gt;
&lt;hr /&gt;

&lt;h5 id=&quot;系统环境&quot;&gt;系统环境&lt;/h5&gt;

&lt;p&gt;Ubuntu 14.04 x64 Server LTS  &lt;br /&gt;
Hadoop 2.7.2  &lt;br /&gt;
vagrant 模拟三台主机，内存都为2G&lt;/p&gt;

&lt;h5 id=&quot;节点角色&quot;&gt;节点角色&lt;/h5&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;IP&lt;/th&gt;
      &lt;th&gt;主机名&lt;/th&gt;
      &lt;th&gt;角色描述&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;192.168.100.201&lt;/td&gt;
      &lt;td&gt;h01.vm.com&lt;/td&gt;
      &lt;td&gt;主节点 NameNode, job-history-server&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;192.168.100.202&lt;/td&gt;
      &lt;td&gt;h02.vm.com&lt;/td&gt;
      &lt;td&gt;主节点  NameNode, (yarn)ResourceManager&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;192.168.100.203&lt;/td&gt;
      &lt;td&gt;h03.vm.com&lt;/td&gt;
      &lt;td&gt;-&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;另，以上所有节点都同时是 zookeeper、zkfc、journalnode 和 datanode。运行Namenode和ResourceManager的节点即为主节点。&lt;/p&gt;

&lt;h6 id=&quot;更新软件源索引&quot;&gt;更新软件源索引&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;分别在 h01 h02 h03 操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get update  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;安装基础软件&quot;&gt;安装基础软件&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;分别在 h01 h02 h03 操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;ssh  
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;rsync  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;配置主机域名&quot;&gt;配置主机域名&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;分别在 h01 h02 h03 操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;vim /etc/hostname &lt;span class=&quot;c&quot;&gt;# centos系统可能没有该文件，创建即可  &lt;/span&gt;
h01.vm.com &lt;span class=&quot;c&quot;&gt;# 该节点主机名  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;将该文件内容修改为对应的主机名，例如 h01.vm.com&lt;/p&gt;

&lt;h6 id=&quot;域名解析&quot;&gt;域名解析&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;搭建内网DNS服务器（可选，但推荐），可阅读vincent的博文&lt;br /&gt;
http://blog.kissdata.com/2014/07/10/ubuntu-dns-bind.html&lt;/li&gt;
  &lt;li&gt;配置 /etc/hosts，将以下代码追加到文件末尾即可（如搭建了DNS服务器，则跳过此步骤）&lt;/li&gt;
  &lt;li&gt;分别在 h01 h02 h03 操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;vim /etc/hosts  
192.168.100.201 h01.vm.com h01  
192.168.100.202 h02.vm.com h02  
192.168.100.203 h03.vm.com h03  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;!!! Ubuntu系统，须删掉 /etc/hosts 映射 127.0.1.1/127.0.0.1 !!!  &lt;br /&gt;
Check that there isn’t an entry for your hostname mapped to 127.0.0.1 or 127.0.1.1 in /etc/hosts (Ubuntu is notorious for this).  &lt;br /&gt;
127.0.1.1 h01.vm.com # must remove  &lt;br /&gt;
不然可能会引起 hadoop、zookeeper 节点间通信的问题&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h6 id=&quot;时间同步可选&quot;&gt;时间同步（可选）&lt;/h6&gt;
&lt;p&gt;在内网中搭建 ntp 服务器，可阅读vincent的博文  &lt;br /&gt;
http://blog.kissdata.com/2014/10/28/ubuntu-ntp.html&lt;/p&gt;

&lt;h6 id=&quot;准备jdkhadoop和zookeeper软件包&quot;&gt;准备jdk、hadoop和zookeeper软件包&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;须到官方网站下载stable版本  &lt;br /&gt;
jdk-7u79-linux-x64.tar.gz  &lt;br /&gt;
hadoop-2.7.2.tar.gz  &lt;br /&gt;
zookeeper-3.4.8.tar.gz&lt;/li&gt;
  &lt;li&gt;所有的软件包都统一解压到 /home/vagrant/VMBigData 目录下，其中 vagrant 是linux系统的用户名，由于我是使用 vagrant 虚拟的主机，所以默认是 vagrant&lt;/li&gt;
  &lt;li&gt;在 h01 操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 先在其中一台机子操作，后面会使用 scp 命令或者其他方法同步到其他主机  &lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /home/vagrant/VMBigData/hadoop /home/vagrant/VMBigData/java /home/vagrant/VMBigData/zookeeper  
&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;zxf jdk-7u79-linux-x64.tar.gz &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; /home/vagrant/VMBigData/java  
&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;zxf hadoop-2.7.2.tar.gz &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; /home/vagrant/VMBigData/hadoop  
&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;zxf zookeeper-3.4.8.tar.gz &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; /home/vagrant/VMBigData/zookeeper  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;配置软连接方便以后升级版本&quot;&gt;配置软连接，方便以后升级版本&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;在 h01 操作，后面通过 scp 同步到其他主机&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; /home/vagrant/VMBigData/java/jdk1.7.0_79/  /home/vagrant/VMBigData/java/default  
&lt;span class=&quot;nb&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; /home/vagrant/VMBigData/hadoop/hadoop-2.7.2/  /home/vagrant/VMBigData/hadoop/default  
&lt;span class=&quot;nb&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; /home/vagrant/VMBigData/zookeeper/zookeeper-3.4.8/ /home/vagrant/VMBigData/zookeeper/default  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;配置环境变量&quot;&gt;配置环境变量&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;分别在 h01 h02 h03 操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;vim /etc/profile  
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;HADOOP_HOME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/home/vagrant/VMBigData/hadoop/default  
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;JAVA_HOME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/home/vagrant/VMBigData/java/default  
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$JAVA_HOME&lt;/span&gt;/bin:&lt;span class=&quot;nv&quot;&gt;$HADOOP_HOME&lt;/span&gt;/bin:&lt;span class=&quot;nv&quot;&gt;$PATH&lt;/span&gt;  
&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; /etc/profile  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;配置免密码ssh登录&quot;&gt;配置免密码ssh登录&lt;/h6&gt;
&lt;p&gt;hadoop主节点需要能远程登陆集群内的所有节点（包括自己），以执行命令。所以需要配置免密码的ssh登陆。可选的ssh秘钥对生成方式有rsa和dsa两种，这里选择rsa。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;分别在 h01 h02 ，即两个主节点上操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh-keygen &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; rsa &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;youremail@xx.com&quot;&lt;/span&gt;  
&lt;span class=&quot;c&quot;&gt;# 注意在接下来的命令行交互中，直接按回车跳过输入密码  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;以下命令将本节点的公钥 id_rsa.pub 文件的内容追加到远程主机的 authorized_keys 文件中（默认位于 ~/.ssh/）&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh-copy-id vagrant@h01.vm.com &lt;span class=&quot;c&quot;&gt;# vagrant是远程主机用户名  &lt;/span&gt;
ssh-copy-id vagrant@h02.vm.com &lt;span class=&quot;c&quot;&gt;# vagrant是远程主机用户名  &lt;/span&gt;
ssh-copy-id vagrant@h03.vm.com  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;在 h01 h02 上测试无密码 ssh 登录到 h01 h02 h03&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh h01.vm.com  
ssh h02.vm.com  
ssh h03.vm.com  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;!!! 注意使用rsa模式生成密钥对时，不要轻易覆盖原来已有的，确定无影响时方可覆盖 !!!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h6 id=&quot;配置从节点&quot;&gt;配置从节点&lt;/h6&gt;
&lt;p&gt;在 slaves 文件中配置的主机即为从节点，将自动运行datanode服务&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;在 h01 操作，后面通过 scp 同步到其他主机&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vim /home/vagrant/VMBigData/hadoop/default/etc/hadoop/slaves  
h01.vm.com   
h02.vm.com  
h03.vm.com  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;建立存储数据的相应目录&quot;&gt;建立存储数据的相应目录&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;在 h01 操作，后面通过 scp 同步到其他主机&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /home/vagrant/VMBigData/hadoop/data/hdfs/tmp  
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /home/vagrant/VMBigData/hadoop/data/pid  
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /home/vagrant/VMBigData/hadoop/data/namenode1  
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /home/vagrant/VMBigData/hadoop/data/namenode2  
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /home/vagrant/VMBigData/hadoop/data/datanode1  
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /home/vagrant/VMBigData/hadoop/data/datanode2  
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /home/vagrant/VMBigData/hadoop/data/local-dirs  
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /home/vagrant/VMBigData/hadoop/data/log-dirs      
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;配置hadoop参数&quot;&gt;配置hadoop参数&lt;/h6&gt;
&lt;p&gt;在 h01 操作，后面通过 scp 同步到其他主机&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;vim /home/vagrant/VMBigData/hadoop/default/etc/hadoop/hadoop-env.sh&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# export JAVA_HOME=${JAVA_HOME} # 注意注释掉原来的这行  &lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;JAVA_HOME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/home/vagrant/VMBigData/java/default  
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;HADOOP_PREFIX&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/home/vagrant/VMBigData/hadoop/default  
&lt;span class=&quot;c&quot;&gt;# export HADOOP_PID_DIR=${HADOOP_PID_DIR} # 注意注释掉原来的这行  &lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;HADOOP_PID_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/home/vagrant/VMBigData/hadoop/data/hdfs/pid  
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;YARN_PID_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/home/vagrant/VMBigData/hadoop/data/hdfs/pid  
&lt;span class=&quot;c&quot;&gt;# export HADOOP_SECURE_DN_PID_DIR=${HADOOP_PID_DIR} # 注意注释掉原来的这行  &lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;HADOOP_SECURE_DN_PID_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;HADOOP_PID_DIR&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;vim /home/vagrant/VMBigData/hadoop/default/etc/hadoop/mapred-env.sh&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;HADOOP_MAPRED_PID_DIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/home/vagrant/VMBigData/hadoop/data/hdfs/pid  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;vim /home/vagrant/VMBigData/hadoop/default/etc/hadoop/core-site.xml&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;nt&quot;&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 指定hdfs的nameservice为h01，需与dfs.nameservices一致 --&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;fs.defaultFS&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;hdfs://ns1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 指定hadoop数据存储目录 --&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;hadoop.tmp.dir&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;/home/vagrant/VMBigData/hadoop/data/hdfs/tmp&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 指定zookeeper地址 --&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;ha.zookeeper.quorum&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;h01.vm.com:2181,h02.vm.com:2181,h03.vm.com:2181&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;   
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;vim /home/vagrant/VMBigData/hadoop/default/etc/hadoop/hdfs-site.xml&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;nt&quot;&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.replication&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 单机版的一般设为1，若是集群，一般设为3 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 以下3个 property 的配置，是非HA模式下的，即一个集群只有一个 namenode，在这里不可使用 --&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 配置Secondary NameNode在另外一个节点上，该节点也将作为主节点之一 --&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- &amp;lt;property&amp;gt;  
&amp;lt;name&amp;gt;dfs.http.address&amp;lt;/name&amp;gt;  
&amp;lt;value&amp;gt;h01.vm.com:50070&amp;lt;/value&amp;gt;  
&amp;lt;description&amp;gt;Secondary get fsimage and edits via dfs.http.address&amp;lt;/description&amp;gt;  
&amp;lt;/property&amp;gt;  
&amp;lt;property&amp;gt;  
&amp;lt;name&amp;gt;dfs.secondary.http.address&amp;lt;/name&amp;gt;  
&amp;lt;value&amp;gt;h02.vm.com:50090&amp;lt;/value&amp;gt;  
&amp;lt;/property&amp;gt;  
&amp;lt;property&amp;gt;  
&amp;lt;name&amp;gt;dfs.namenode.checkpoint.dir&amp;lt;/name&amp;gt;  
&amp;lt;value&amp;gt;/home/vagrant/VMBigData/hadoop/data/namesecondary&amp;lt;/value&amp;gt;  
&amp;lt;/property&amp;gt; --&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 以下 property 配置，是haddop的HA即高可用模式，一个集群可以配置最多2个namenode --&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 命名空间的逻辑名称 --&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.nameservices&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;ns1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 命名空间中所有NameNode的唯一标示。该标识指示DataNode集群中有哪些NameNode。目前最多只能配置两个NameNode --&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.ha.namenodes.ns1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;nn1,nn2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.namenode.rpc-address.ns1.nn1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;h01.vm.com:9000&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.namenode.http-address.ns1.nn1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;h01.vm.com:50070&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.namenode.rpc-address.ns1.nn2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;h02.vm.com:9000&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.namenode.http-address.ns1.nn2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;h02.vm.com:50070&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- JournalNode URLs，ActiveNameNode 会将 Edit Log 写入这些 JournalNode 所配置的本地目录即 dfs.journalnode.edits.dir --&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.namenode.shared.edits.dir&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;qjournal://h01.vm.com:8485;h02.vm.com:8485;h03.vm.com:8485/ns1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- JournalNode 用于存放 editlog 和其他状态信息的目录 --&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.journalnode.edits.dir&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;/home/vagrant/VMBigData/hadoop/data/journal&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.ha.automatic-failover.enabled&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;true&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.client.failover.proxy.provider.ns1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 一种关于 NameNode 的隔离机制(fencing) --&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.ha.fencing.methods&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;sshfence shell(/bin/true)&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.ha.fencing.ssh.private-key-files&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;/home/vagrant/.ssh/id_rsa&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.ha.fencing.ssh.connect-timeout&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;30000&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- HA模式配置结束 --&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.namenode.name.dir&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 创建的namenode文件夹位置，如有多个用逗号隔开。配置多个的话，每一个目录下数据都是相同的，达到数据冗余备份的目的 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;file:///home/vagrant/VMBigData/hadoop/data/namenode1,file:///home/vagrant/VMBigData/hadoop/data/namenode2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;dfs.datanode.data.dir&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 创建的datanode文件夹位置，多个用逗号隔开，实际不存在的目录会被忽略 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;file:///home/vagrant/VMBigData/hadoop/data/datanode1,file:///home/vagrant/VMBigData/hadoop/data/datanode2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;   
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;vim /home/vagrant/VMBigData/hadoop/default/etc/hadoop/yarn-site.xml&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;nt&quot;&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;yarn.resourcemanager.hostname&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;h02.vm.com&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;yarn.log-aggregation-enable&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 打开日志聚合功能，这样才能从web界面查看日志 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;true&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;yarn.log-aggregation.retain-seconds&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 聚合日志最长保留时间 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;86400&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;yarn.nodemanager.resource.memory-mb&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- NodeManager总的可用内存，这个要根据实际情况合理配置 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;1024&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;yarn.scheduler.minimum-allocation-mb&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- MapReduce作业时，每个task最少可申请内存 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;256&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;yarn.scheduler.maximum-allocation-mb&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- MapReduce作业时，每个task最多可申请内存 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;512&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;yarn.nodemanager.vmem-pmem-ratio&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 可申请使用的虚拟内存，相对于实际使用内存大小的倍数。实际生产环境中可设置的大一些，如4.2 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;2.1&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;yarn.nodemanager.vmem-check-enabled&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;false&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;yarn.nodemanager.local-dirs&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 中间结果存放位置。注意，这个参数通常会配置多个目录，已分摊磁盘IO负载。 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;/home/vagrant/VMBigData/hadoop/data/localdir1,/home/vagrant/VMBigData/hadoop/data/localdir2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;yarn.nodemanager.log-dirs&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 日志存放位置。注意，这个参数通常会配置多个目录，已分摊磁盘IO负载。 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;/home/vagrant/VMBigData/hadoop/data/logdir1,/home/vagrant/VMBigData/hadoop/data/logdir2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;yarn.nodemanager.aux-services&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;mapreduce_shuffle&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;yarn.nodemanager.aux-services.mapreduce.shuffle.class&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;org.apache.hadoop.mapred.ShuffleHandler&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;   
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;vim /home/vagrant/VMBigData/hadoop/default/etc/hadoop/mapred-site.xml&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;&lt;/span&gt;  
&lt;span class=&quot;nt&quot;&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;mapreduce.framework.name&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;yarn&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;yarn.app.mapreduce.am.resource.mb&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 默认值为 1536,可根据需要调整，调小一些也是可接受的 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;512&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;mapreduce.map.memory.mb&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 每个map task申请的内存，每一次都会实际申请这么多 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;384&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;mapreduce.map.java.opts&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 每个map task中的child jvm启动时参数，需要比 mapreduce.map.memory.mb 设置的小一些 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 注意：map任务里不一定跑java，可能跑非java（如streaming） --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;-Xmx256m&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;mapreduce.reduce.memory.mb&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;384&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;mapreduce.reduce.java.opts&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;-Xmx256m&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;mapreduce.tasktracker.map.tasks.maximum&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;mapreduce.tasktracker.reduce.tasks.maximum&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;mapred.child.java.opts&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 默认值为 -Xmx200m，生产环境可以设大一些 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;-Xmx384m&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;mapreduce.task.io.sort.mb&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 任务内部排序缓冲区大小 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;128&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;mapreduce.task.io.sort.factor&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- map计算完全后的merge阶段，一次merge时最多可有多少个输入流 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;100&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;mapreduce.reduce.shuffle.parallelcopies&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- reuduce shuffle阶段并行传输数据的数量 --&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;50&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;mapreduce.jobhistory.address&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;h01.vm.com:10020&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;    
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&amp;gt;&lt;/span&gt;   
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;mapreduce.jobhistory.webapp.address&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;    
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;value&amp;gt;&lt;/span&gt;h01.vm.com:19888&lt;span class=&quot;nt&quot;&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;   
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;   
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;安装配置zookeeper&quot;&gt;安装配置zookeeper&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;在 h01 操作，后面通过 scp 同步到其他主机&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /home/vagrant/VMBigData/zookeeper/default/conf/  
&lt;span class=&quot;nb&quot;&gt;cp &lt;/span&gt;zoo_sample.cfg zoo.cfg  
vim zoo.cfg  
&lt;span class=&quot;c&quot;&gt;# 对该文件做出以下修改  &lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;dataDir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/home/vagrant/VMBigData/zookeeper/data/tmp  
&lt;span class=&quot;c&quot;&gt;# 如果无法启动zookeeper，可将以下代码对应的行改为 0.0.0.0:2888:3888  &lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 注意zookeeper解析该文件很死板，不要输入多余的空格和空行  &lt;/span&gt;
server.1&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;h01.vm.com:2888:3888  
server.2&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;h02.vm.com:2888:3888  
server.3&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;h03.vm.com:2888:3888  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /home/vagrant/VMBigData/zookeeper/data/tmp  
vim /home/vagrant/VMBigData/zookeeper/data/tmp/myid  
&lt;span class=&quot;c&quot;&gt;# 在此文件中输入节点编号，比如h01节点就输入1，h02节点就输入2  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;将hadoop所需文件同步到其他主机&quot;&gt;将hadoop所需文件同步到其他主机&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;在 h01 上操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;scp &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; /home/vagrant/VMBigData vagrant@h02.vm.com:/home/vagrant  
scp &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; /home/vagrant/VMBigData vagrant@h03.vm.com:/home/vagrant  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;!!! 注意：default 软连接需要重建 !!!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;修改各节点的 zookeeper 的 /home/vagrant/VMBigData/zookeeper/data/tmp/myid 文件，内容为各节点编号，本例中为 1,2,3&lt;/li&gt;
&lt;/ul&gt;

&lt;h6 id=&quot;启动zookeeper&quot;&gt;启动zookeeper&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;在 h01 h02 h03 操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /home/vagrant/VMBigData/zookeeper/default  
bin/zkServer.sh start  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;启动journalnode&quot;&gt;启动JournalNode&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;在任一配置了journalnode的节点操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /home/vagrant/VMBigData/hadoop/default  
sbin/hadoop-daemons.sh start journalnode  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;格式化namenode&quot;&gt;格式化namenode&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;在 h01 namenode 上执行&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hdfs namenode &lt;span class=&quot;nt&quot;&gt;-format&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;!!! 注意仅在首次启动时执行，因为此命令会删除hadoop集群所有的数据 !!!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h6 id=&quot;同步两个namenode数据方法一推荐&quot;&gt;同步两个namenode数据（方法一：推荐）&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;在 h01 启动 namenode&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /home/vagrant/VMBigData/hadoop/default  
sbin/hadoop-daemon.sh start namenode  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;在 h02 执行同步&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /home/vagrant/VMBigData/hadoop/default  
bin/hdfs namenode &lt;span class=&quot;nt&quot;&gt;-bootstrapStandby&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;在 h02 启动 namenode&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /home/vagrant/VMBigData/hadoop/default  
sbin/hadoop-daemon.sh start namenode  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;同步两个namenode数据方法二不推荐可能引起复制后的文件的权限问题&quot;&gt;同步两个namenode数据（方法二：不推荐，可能引起复制后的文件的权限问题）&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;在 h01 操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;scp &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; /home/vagrant/VMBigData/hadoop/data/namenode1/ vagrant@h02.vm.com:/home/vagrant/VMBigData/hadoop/data/  
scp &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; /home/vagrant/VMBigData/hadoop/data/namenode2/ vagrant@h02.vm.com:/home/vagrant/VMBigData/hadoop/data/  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;格式化zkfc&quot;&gt;格式化zkfc&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;在 h01 或 h02 (任一namenode) 上操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hdfs zkfc &lt;span class=&quot;nt&quot;&gt;-formatZK&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;!!! 注意仅在首次启动时执行 !!!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h6 id=&quot;启动zkfc&quot;&gt;启动zkfc&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;在 h01 h02 h03 等计划运行zkfc的节点上操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /home/vagrant/VMBigData/hadoop/default  
sbin/hadoop-daemon.sh start zkfc  
&lt;span class=&quot;c&quot;&gt;# sbin/hadoop-daemon.sh stop zkfc #  停止  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;启动hadoop集群&quot;&gt;启动hadoop集群：&lt;/h6&gt;

&lt;p&gt;&lt;em&gt;启动hdfs&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;可在任意主节点执行&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /home/vagrant/VMBigData/hadoop/default  
sbin/start-dfs.sh  
&lt;span class=&quot;c&quot;&gt;# sbin/stop-dfs.sh # 停止  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;启动Yarn&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;在 h02 ResourceManager 上操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /home/vagrant/VMBigData/hadoop/default  
sbin/start-yarn.sh  
&lt;span class=&quot;c&quot;&gt;# sbin/stop-yarn.sh# 停止  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;启动 job history server（可选）&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;在 h01 上操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /home/vagrant/VMBigData/hadoop/default  
sbin/mr-jobhistory-daemon.sh start historyserver  
&lt;span class=&quot;c&quot;&gt;# sbin/mr-jobhistory-daemon.sh stop historyserver # 停止  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;浏览服务启动情况&quot;&gt;浏览服务启动情况&lt;/h6&gt;
&lt;p&gt;NameNode1&lt;br /&gt;
http://192.168.100.201:50070&lt;/p&gt;

&lt;p&gt;NameNode2&lt;br /&gt;
http://192.168.100.202:50070&lt;/p&gt;

&lt;p&gt;ResourceManager&lt;br /&gt;
http://192.168.100.202:8088&lt;/p&gt;

&lt;p&gt;MapReduce JobHistory Server&lt;br /&gt;
http://192.168.100.201:19888&lt;/p&gt;

&lt;p&gt;Datanode&lt;br /&gt;
http://192.168.100.201:50075&lt;br /&gt;
http://192.168.100.202:50075&lt;br /&gt;
http://192.168.100.203:50075&lt;/p&gt;

&lt;p&gt;zookeeper&lt;br /&gt;
bin/zkServer.sh status&lt;/p&gt;

&lt;p&gt;zookeeper命令行&lt;br /&gt;
zkCli.sh -server 127.0.0.1:2181&lt;/p&gt;

&lt;p&gt;集群状态&lt;br /&gt;
bin/hdfs dfsadmin -report&lt;/p&gt;

&lt;p&gt;hadoop进程&lt;br /&gt;
jps&lt;/p&gt;
</description>
        <pubDate>Wed, 14 Sep 2016 00:00:00 +0000</pubDate>
        <link>https://mtide.net/hadoop-HA%E9%AB%98%E5%8F%AF%E7%94%A8%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F%E6%90%AD%E5%BB%BA%E6%8C%87%E5%8D%97.html</link>
        <guid isPermaLink="true">https://mtide.net/hadoop-HA%E9%AB%98%E5%8F%AF%E7%94%A8%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F%E6%90%AD%E5%BB%BA%E6%8C%87%E5%8D%97.html</guid>
        
        
        <category>BIGDATA</category>
        
      </item>
    
      <item>
        <title>Mac OS X 10.11+ Rootless 介绍</title>
        <description>&lt;h3 id=&quot;rootless&quot;&gt;Rootless&lt;/h3&gt;
&lt;p&gt;Mac OS X 10.11+ (El Capitan) 以后，引入了 Rootless 安全机制，该机制限制了 root 用户的权限，有部分操作即使是 root 也无法执行。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;该机制的详细介绍，请参考：&lt;br /&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/System_Integrity_Protection&quot;&gt;Wikipedia: System Integrity Protection&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://developer.apple.com/library/content/documentation/Security/Conceptual/System_Integrity_Protection_Guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40016462-CH1-DontLinkElementID_15&quot;&gt;Apple: System Integrity Protection Guide&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;主要地限制有：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;以下目录无法修改：&lt;br /&gt;
/System, /bin, /sbin, 或者 /usr (/usr/local 除外)，以及内置 App 和系统工具 Utilities。&lt;br /&gt;
具体的限制名单在 /System/Library/Sandbox/rootless.conf 文件中有定义，该文件行首的 * 表示该行记录不在限制之列&lt;/li&gt;
  &lt;li&gt;无法追踪调试系统进程&lt;/li&gt;
  &lt;li&gt;无法加载未经验证的内核扩展&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;可能引起的问题和解决方案&quot;&gt;可能引起的问题和解决方案&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;修改受限制的文件或文件夹&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; FILE /usr/bin/

&lt;span class=&quot;c&quot;&gt;# 报错： cp: /usr/bin/FILE: Operation not permitted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;解决方案：关闭 rootless，见下文&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;执行部分命令受挫&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;posix-spawn &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;0.3.11&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 报错：&lt;/span&gt;
Building native extensions.  This could take a &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;...
ERROR:  While executing gem ... &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Errno::EPERM&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    Operation not permitted - /usr/bin/posix-spawn-benchmark
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;解决方案：尝试执行 /usr/local/bin 里命令，或将命令文件复制到 /usr/local/bin 后再执行，例如&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;gem &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; /usr/local/bin GEM-NAME
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;关闭-rootless&quot;&gt;关闭 rootless&lt;/h3&gt;
&lt;p&gt;如果遇到的问题难以解决，也可以关闭 rootless 功能，彻底解决引起的权限问题，但是关闭 rootless 将会严重降低系统安全性，必须尽快重新开启。&lt;/p&gt;

&lt;p&gt;关闭的步骤如下：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;重启 Mac 并按住 Command+R，进入恢复模式&lt;/li&gt;
  &lt;li&gt;打开终端 Terminal&lt;/li&gt;
  &lt;li&gt;csrutil disable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;开启的步骤：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;重启 Mac 并按住 Command+R，进入恢复模式&lt;/li&gt;
  &lt;li&gt;打开终端 Terminal&lt;/li&gt;
  &lt;li&gt;csrutil enable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在 mac 终端直接键入 csrutil 可查看该命令的使用说明。&lt;/p&gt;
</description>
        <pubDate>Wed, 14 Sep 2016 00:00:00 +0000</pubDate>
        <link>https://mtide.net/Mac-OS-X-10.11+-Rootless-%E4%BB%8B%E7%BB%8D.html</link>
        <guid isPermaLink="true">https://mtide.net/Mac-OS-X-10.11+-Rootless-%E4%BB%8B%E7%BB%8D.html</guid>
        
        
        <category>NOTE</category>
        
      </item>
    
      <item>
        <title>CDH 5.x 离线安装指南</title>
        <description>&lt;h3 id=&quot;简述&quot;&gt;简述&lt;/h3&gt;
&lt;hr /&gt;

&lt;p&gt;hadoop 体系衍生了许多商业发行版，其中国内最常用的是 &lt;a href=&quot;http://www.cloudera.com/&quot;&gt;CDH&lt;/a&gt;。CDH是在原生 apache hadoop 的基础上封装的发行版，拥有 Cloudera Manager (简称CM，是一个管理、监控CDH运行的软件)，将 hadoop 集群很好的监控和管理了起来，目前 CDH 最高版本是 5.7.x，对应 apache hadoop 2.7.x，这里我们采用 CDH 5.7.0。其安装的简要说明及系统要求见以下链接：&lt;br /&gt;
&lt;a href=&quot;http://www.cloudera.com/downloads/manager/5-7-1.html&quot;&gt;Download Cloudera Manager&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CDH 的安装相比原生 hadoop 安装，将会少敲很多命令，少写很多配置文件，但是在过程中许多步骤都需要用到 root 权限。整个安装过程，大致上可以分成两个步骤：首先是先安装 CM 控制台（即一个web管理界面），然后通过 CM 来安装 CDH。也就是说，整个CDH集群的安装，是可以在CM界面上进行操作的，包括后面添加节点，也是在CM界面上进行操作。&lt;/p&gt;

&lt;h3 id=&quot;搭建过程&quot;&gt;搭建过程&lt;/h3&gt;
&lt;hr /&gt;

&lt;h5 id=&quot;系统环境&quot;&gt;系统环境&lt;/h5&gt;
&lt;p&gt;CentOS release 6.5 x64&lt;br /&gt;
CDH 5.7.0&lt;br /&gt;
三台主机节点，内存都为10G&lt;/p&gt;

&lt;h5 id=&quot;节点角色说明&quot;&gt;节点角色说明&lt;/h5&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;IP&lt;/th&gt;
      &lt;th&gt;主机名&lt;/th&gt;
      &lt;th&gt;角色描述&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;10.1.2.126&lt;/td&gt;
      &lt;td&gt;hadoop01&lt;/td&gt;
      &lt;td&gt;CM, Agent&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10.1.2.142&lt;/td&gt;
      &lt;td&gt;hadoop02&lt;/td&gt;
      &lt;td&gt;Agent&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;10.1.2.144&lt;/td&gt;
      &lt;td&gt;hadoop03&lt;/td&gt;
      &lt;td&gt;Agent&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;一开始可能只有一台机器，那么本文的的所有安装步骤可以先只在其中一台机器上操作，那么这台机器也就是主控节点了，因为这台机器上将会安装CM，那么以后集群添加其他机器便可以在CM上操作了。例如，选择 hadoop01 作为主控节点，hadoop02和hadoop03 当做受控节点，那么仅需要在 hadoop01 节点上执行本文的所有安装步骤。等安装完成，CM界面可以正常打开运行，再添加 hadoop02和hadoop03 节点。&lt;/p&gt;

&lt;h6 id=&quot;配置主机域名&quot;&gt;配置主机域名&lt;/h6&gt;

&lt;ul&gt;
  &lt;li&gt;分别在每一台主机上操作&lt;/li&gt;
  &lt;li&gt;方法1：&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;vim /etc/sysconfig/network
&lt;span class=&quot;nv&quot;&gt;HOSTNAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;hadoop01
&lt;span class=&quot;c&quot;&gt;# 修改 HOSTNAME 为该节点主机名，例如 hadoop01&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 然后重新启动即可&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;方法2：&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;vim /etc/hostname &lt;span class=&quot;c&quot;&gt;# centos系统可能没有该文件，创建即可&lt;/span&gt;
hadoop01 &lt;span class=&quot;c&quot;&gt;# 该节点主机名&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;将该文件内容修改为对应的主机名，例如 hadoop02&lt;/p&gt;

&lt;h6 id=&quot;域名解析&quot;&gt;域名解析&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;配置 /etc/hosts，将以下代码追加到文件末尾即可&lt;/li&gt;
  &lt;li&gt;分别在所有三台主机上操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;vim /etc/hosts
10.1.2.126 hadoop01 
10.1.2.142 hadoop02 
10.1.2.144 hadoop03
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;!!! Ubuntu系统，须删掉 /etc/hosts 映射 127.0.1.1/127.0.0.1 !!!&lt;br /&gt;
Check that there isn’t an entry for your hostname mapped to 127.0.0.1 or 127.0.1.1 in /etc/hosts (Ubuntu is notorious for this).&lt;br /&gt;
127.0.1.1 h01.vm.com # must remove&lt;br /&gt;
不然可能会引起 hadoop、zookeeper 节点间通信的问题&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h6 id=&quot;服务器安全配置&quot;&gt;服务器安全配置&lt;/h6&gt;

&lt;ul&gt;
  &lt;li&gt;分别在所有三台主机上操作&lt;/li&gt;
  &lt;li&gt;关闭 iptables （如果不允许关闭，则采用配置 iptables 策略的方法）&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;service iptables stop    &lt;span class=&quot;c&quot;&gt;# 临时关闭&lt;/span&gt;
chkconfig iptables off  &lt;span class=&quot;c&quot;&gt;# 永久关闭 重启后生效&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;或 配置 iptables 策略
附CDH、CM及其上各组件的端口列表&lt;br /&gt;
&lt;a href=&quot;http://www.cloudera.com/documentation/manager/5-0-x/Cloudera-Manager-Installation-Guide/cm5ig_ports_cm.html&quot;&gt;CM5 PORTS&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;http://www.cloudera.com/documentation/cdh/5-0-x/CDH5-Installation-Guide/cdh5ig_ports_cdh5.html&quot;&gt;CDH5 PORTS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# ACCEPT=允许  DROP=拒绝&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 443&lt;/span&gt;
iptables &lt;span class=&quot;nt&quot;&gt;-A&lt;/span&gt; INPUT &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; tcp &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; 10.1.2.0/24 &lt;span class=&quot;nt&quot;&gt;--dport&lt;/span&gt; 443 &lt;span class=&quot;nt&quot;&gt;-j&lt;/span&gt; ACCEPT &lt;span class=&quot;c&quot;&gt;# 允许10.1.2.0的IP访问本机443端口&lt;/span&gt;
iptables &lt;span class=&quot;nt&quot;&gt;-A&lt;/span&gt; OUTPUT &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; tcp &lt;span class=&quot;nt&quot;&gt;--sport&lt;/span&gt; 443 &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; state &lt;span class=&quot;nt&quot;&gt;--state&lt;/span&gt; ESTABLISHED &lt;span class=&quot;nt&quot;&gt;-j&lt;/span&gt; ACCEPT &lt;span class=&quot;c&quot;&gt;# 允许443端口出&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# zookeeper&lt;/span&gt;
iptables &lt;span class=&quot;nt&quot;&gt;-A&lt;/span&gt; INPUT &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; tcp &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; 10.1.2.0/24 &lt;span class=&quot;nt&quot;&gt;--dport&lt;/span&gt; 2181 &lt;span class=&quot;nt&quot;&gt;-j&lt;/span&gt; ACCEPT
iptables &lt;span class=&quot;nt&quot;&gt;-A&lt;/span&gt; OUTPUT &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; tcp &lt;span class=&quot;nt&quot;&gt;--sport&lt;/span&gt; 2181 &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; state &lt;span class=&quot;nt&quot;&gt;--state&lt;/span&gt; ESTABLISHED &lt;span class=&quot;nt&quot;&gt;-j&lt;/span&gt; ACCEPT
iptables &lt;span class=&quot;nt&quot;&gt;-A&lt;/span&gt; INPUT &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; tcp &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; 10.1.2.0/24 &lt;span class=&quot;nt&quot;&gt;--dport&lt;/span&gt; 2888 &lt;span class=&quot;nt&quot;&gt;-j&lt;/span&gt; ACCEPT
iptables &lt;span class=&quot;nt&quot;&gt;-A&lt;/span&gt; OUTPUT &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; tcp &lt;span class=&quot;nt&quot;&gt;--sport&lt;/span&gt; 2888 &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; state &lt;span class=&quot;nt&quot;&gt;--state&lt;/span&gt; ESTABLISHED &lt;span class=&quot;nt&quot;&gt;-j&lt;/span&gt; ACCEPT
iptables &lt;span class=&quot;nt&quot;&gt;-A&lt;/span&gt; INPUT &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; tcp &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; 10.1.2.0/24 &lt;span class=&quot;nt&quot;&gt;--dport&lt;/span&gt; 3888 &lt;span class=&quot;nt&quot;&gt;-j&lt;/span&gt; ACCEPT
iptables &lt;span class=&quot;nt&quot;&gt;-A&lt;/span&gt; OUTPUT &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; tcp &lt;span class=&quot;nt&quot;&gt;--sport&lt;/span&gt; 3888 &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; state &lt;span class=&quot;nt&quot;&gt;--state&lt;/span&gt; ESTABLISHED &lt;span class=&quot;nt&quot;&gt;-j&lt;/span&gt; ACCEPT

&lt;span class=&quot;c&quot;&gt;# yarn&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# 重启 iptables&lt;/span&gt;
service iptables save
service iptables restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;关闭 SELinux&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;setenforce 0 &lt;span class=&quot;c&quot;&gt;# 临时生效&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;vim /etc/selinux/config
&lt;span class=&quot;nv&quot;&gt;SELINUX&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;disabled
&lt;span class=&quot;c&quot;&gt;# 将 SELINUX 设置为 disabled 即永久关闭了 SELinux&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;配置免密码ssh登录&quot;&gt;配置免密码ssh登录&lt;/h6&gt;
&lt;p&gt;hadoop 主节点需要能远程登陆集群内的所有节点（包括自己），以执行命令。所以需要配置免密码的ssh登陆。可选的ssh秘钥对生成方式有rsa和dsa两种，这里选择rsa。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;分别所有节点操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh-keygen &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; rsa
&lt;span class=&quot;c&quot;&gt;# 注意在接下来的命令行交互中，直接按回车跳过输入密码&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;分别在所有节点操作：以下命令将本节点的公钥 id_rsa.pub 文件的内容追加到远程主机的 authorized_keys 文件中（默认位于 ~/.ssh/）&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh-copy-id root@hadoop01 &lt;span class=&quot;c&quot;&gt;# root是远程主机用户名&lt;/span&gt;
ssh-copy-id root@hadoop02
ssh-copy-id root@hadoop03
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;在每个节点上测试无密码 ssh 登录到 hadoop01 hadoop02 hadoop03&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh hadoop01
ssh hadoop02
ssh hadoop03
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;!!! 注意使用rsa模式生成密钥对时，不要轻易覆盖原来已有的，确定无影响时方可覆盖 !!!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h6 id=&quot;安装jdk在所有节点操作&quot;&gt;安装jdk（在所有节点操作）&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;先查询看系统是否已安装 openjdk&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;rpm &lt;span class=&quot;nt&quot;&gt;-qa&lt;/span&gt; openjdk
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;卸载 openjdk&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;rpm &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--nodeps&lt;/span&gt; 上面步骤找到的包名
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;安装 jdk&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# jdk-7u67-linux-x64.rpm 需要事先下载好&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;rpm &lt;span class=&quot;nt&quot;&gt;-ivh&lt;/span&gt; jdk-7u67-linux-x64.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;配置环境变量&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;vim /etc/profile
&lt;span class=&quot;c&quot;&gt;# 在该文件末尾添加以下行&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;JAVA_HOME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/usr/java/jdk1.7.0_67
&lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PATH&lt;/span&gt;:&lt;span class=&quot;nv&quot;&gt;$JAVA_HOME&lt;/span&gt;/bin
&lt;span class=&quot;nv&quot;&gt;CLASSPATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;.:&lt;span class=&quot;nv&quot;&gt;$JAVA_HOME&lt;/span&gt;/lib/jt.jar:&lt;span class=&quot;nv&quot;&gt;$JAVA_HOME&lt;/span&gt;/lib/tools.jar
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;JAVA_HOME PATH CLASSPATH
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;使环境变量立即生效&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; /etc/profile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;测试jdk安装是否成功&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;java &lt;span class=&quot;nt&quot;&gt;-version&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 如显示了jdk的版本，则安装成功&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;时间同步在内网中搭建-ntp-服务器&quot;&gt;时间同步：在内网中搭建 ntp 服务器&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;安装 ntp（在所有节点操作）&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;ntp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;设置NTP服务开机启动（在所有节点操作）&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;chkconfig ntpd on
chkconfig --list ntpd
&lt;span class=&quot;c&quot;&gt;# 2-5为on即成功&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;配置主节点（在 hadoop01 操作）&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vim /etc/ntp.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;该配置文件全部内容如下&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;driftfile /var/lib/ntp/ntp.drift #草稿文件

# Hosts on local network are less restricted.
# 允许内网其他机器同步时间
restrict 10.1.2.0 mask 255.255.255.0 nomodify notrap
 
# Use public servers from the pool.ntp.org project.
# Please consider joining the pool (http://www.pool.ntp.org/join.html).
# 中国这边最活跃的时间服务器 : [http://www.pool.ntp.org/zone/cn](http://www.pool.ntp.org/zone/cn)
server 210.72.145.44 perfer   # 中国国家受时中心
server 202.112.10.36             # 1.cn.pool.ntp.org
server 59.124.196.83             # 0.asia.pool.ntp.org
 
#broadcast 192.168.1.255 autokey        # broadcast server
#broadcastclient                        # broadcast client
#broadcast 224.0.1.1 autokey            # multicast server
#multicastclient 224.0.1.1              # multicast client
#manycastserver 239.255.254.254         # manycast server
#manycastclient 239.255.254.254 autokey # manycast client
 
# allow update time by the upper server 
# 允许上层时间服务器主动修改本机时间
restrict 210.72.145.44 nomodify notrap noquery
restrict 202.112.10.36 nomodify notrap noquery
restrict 59.124.196.83 nomodify notrap noquery
 
# Undisciplined Local Clock. This is a fake driver intended for backup
# and when no outside source of synchronized time is available. 
# 外部时间服务器不可用时，以本地时间作为时间服务
server  127.127.1.0     # local clock
fudge   127.127.1.0 stratum 10
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;保存后重启服务&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;service ntpd restart

&lt;span class=&quot;c&quot;&gt;# 查看同步状态&lt;/span&gt;
netstat &lt;span class=&quot;nt&quot;&gt;-tlunp&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;ntp
watch ntpd –p

&lt;span class=&quot;c&quot;&gt;# 手动同步 &lt;/span&gt;
ntpdate –u ip
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;配置子节点（在所有非主节点操作）&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vim /etc/ntp.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;该配置文件全部内容如下&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;driftfile /var/lib/ntp/ntp.drift # 草稿文件

# 日志文件
statsdir /var/log/ntpstats/

statistics loopstats peerstats clockstats
filegen loopstats file loopstats type day enable
filegen peerstats file peerstats type day enable
filegen clockstats file clockstats type day enable

# 让NTP Server为内网的ntp服务器
server 10.1.2.126
fudge 10.1.2.126 stratum 5

# 不允许来自公网上ipv4和ipv6客户端的访问
restrict -4 default kod notrap nomodify nopeer noquery 
restrict -6 default kod notrap nomodify nopeer noquery

# Local users may interrogate the ntp server more closely.
restrict 127.0.0.1
restrict ::1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;保存后重启服务（在每一台子节点操作）&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;service ntpd restart

&lt;span class=&quot;c&quot;&gt;# 手工同步&lt;/span&gt;
netdate &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; 10.1.2.126
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;准备包用parcel-方式安装&quot;&gt;准备包（用parcel 方式安装）&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;CM包下载地址：
http://archive.cloudera.com/cm5/redhat/5/x86_64/cm/5.7.0/RPMS/x86_64/&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;cloudera-manager-agent-5.7.0-1.cm570.p0.76.el6.x86_64.rpm&lt;/li&gt;
  &lt;li&gt;cloudera-manager-daemons-5.7.0-1.cm570.p0.76.el6.x86_64.rpm&lt;/li&gt;
  &lt;li&gt;cloudera-manager-server-5.7.0-1.cm570.p0.76.el6.x86_64.rpm&lt;/li&gt;
  &lt;li&gt;cloudera-manager-server-db-2-5.7.0-1.cm570.p0.76.el6.x86_64.rpm&lt;/li&gt;
  &lt;li&gt;enterprise-debuginfo-5.7.0-1.cm570.p0.76.el6.x86_64.rpm&lt;/li&gt;
  &lt;li&gt;oracle-j2sdk1.7-1.7.0+update67-1.x86_64.rpm&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
  &lt;li&gt;CDH包下载地址
http://archive.cloudera.com/cdh5/parcels/5.7.0&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;CDH-5.7.0-1.cdh5.7.0.p0.45-el6.parcel&lt;/li&gt;
  &lt;li&gt;CDH-5.7.0-1.cdh5.7.0.p0.45-el6.parcel.sha1&lt;/li&gt;
  &lt;li&gt;cloudera-manager-installer.bin&lt;/li&gt;
  &lt;li&gt;manifest.json&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
  &lt;li&gt;CM 安装文件下载地址
http://archive.cloudera.com/cm5/installer/5.7.0/&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;cloudera-manager-installer.bin&lt;/li&gt;
&lt;/ol&gt;

&lt;h6 id=&quot;配置-cloudera-yum-源在-hadoop01-操作&quot;&gt;配置 cloudera yum 源（在 hadoop01 操作）&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;添加 cloudera-manager 源&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /etc/yum.repos.d
wget http://archive.cloudera.com/cm5/redhat/5/x86_64/cm/cloudera-manager.repo
vim cloudera-manager.repo
&lt;span class=&quot;c&quot;&gt;# 修改该文件以下内容&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;baseurl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;http://archive.cloudera.com/cm5/redhat/6/x86_64/cm/5.7.0/
gpgcheck &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0
enabled  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;添加 cloudera-cdh5 源&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /etc/yum.repos.d
wget http://archive.cloudera.com/cdh5/redhat/6/x86_64/cdh/cloudera-cdh5.repo
vim cloudera-cdh5.repo
&lt;span class=&quot;c&quot;&gt;# 修改该文件以下内容&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;baseurl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;http://archive.cloudera.com/cdh5/redhat/6/x86_64/cdh/5.7.0/
gpgcheck &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 0
enabled  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;更新源&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum update 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;安装-mysql&quot;&gt;安装 mysql&lt;/h6&gt;
&lt;p&gt;（略）&lt;/p&gt;

&lt;h6 id=&quot;安装-cm注意安装顺序&quot;&gt;安装 CM（注意安装顺序）&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;只需要在 hadoop01 节点安装&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;CM包的下载目录
rpm &lt;span class=&quot;nt&quot;&gt;-ivh&lt;/span&gt; cloudera-manager-daemons-5.7.0-1.cm570.p0.76.el6.x86_64.rpm
rpm &lt;span class=&quot;nt&quot;&gt;-ivh&lt;/span&gt; cloudera-manager-agent-5.7.0-1.cm570.p0.76.el6.x86_64.rpm
rpm &lt;span class=&quot;nt&quot;&gt;-ivh&lt;/span&gt; cloudera-manager-server-5.7.0-1.cm570.p0.76.el6.x86_64.rpm
rpm &lt;span class=&quot;nt&quot;&gt;-ivh&lt;/span&gt; cloudera-manager-server-db-2-5.7.0-1.cm570.p0.76.el6.x86_64.rpm
rpm &lt;span class=&quot;nt&quot;&gt;-ivh&lt;/span&gt; enterprise-debuginfo-5.7.0-1.cm570.p0.76.el6.x86_64.rpm
rpm &lt;span class=&quot;nt&quot;&gt;-ivh&lt;/span&gt; oracle-j2sdk1.7-1.7.0+update67-1.x86_64.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;执行安装&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; /etc/cloudera-scm-server/db.properties
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;CM包的下载目录
./cloudera-manager-installer.bin &lt;span class=&quot;nt&quot;&gt;--skip_repo_package&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;后面按照提示操作&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;创建 mysql 数据库&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mysql &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt;用户名 &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 然后在mysql控制台执行以下语句&lt;/span&gt;
CREATE DATABASE IF NOT EXISTS cmf DEFAULT CHARSET utf8 COLLATE utf8_general_ci&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;修改 CM 配置文件使支持mysql&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vim /etc/cloudera-scm-server/db.properties
&lt;span class=&quot;c&quot;&gt;# 修改以下配置&lt;/span&gt;
com.cloudera.cmf.db.type&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;mysql
com.cloudera.cmf.db.host&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;localhost:3306
com.cloudera.cmf.db.name&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;cmf
com.cloudera.cmf.db.user&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;USER
com.cloudera.cmf.db.password&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;PASSWORD
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;添加 com.mysql.jdbc.Driver 到 /usr/share/cmf/lib&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wget &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; http://cdn.mysql.com//Downloads/Connector-J/mysql-connector-java-5.1.39.tar.gz
&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;zxf mysql-connector-java-5.1.39.tar.gz
&lt;span class=&quot;nb&quot;&gt;cp &lt;/span&gt;mysql-connector-java-5.1.39/mysql-connector-java-5.1.39-bin.jar /usr/share/cmf/lib/
&lt;span class=&quot;nb&quot;&gt;rm &lt;/span&gt;mysql-connector-java-5.1.39 &lt;span class=&quot;nt&quot;&gt;-Rf&lt;/span&gt; mysql-connector-java-5.1.39.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;安装-cloudera-manager-agent-在所有节点操作&quot;&gt;安装 cloudera-manager-agent （在所有节点操作）&lt;/h6&gt;
&lt;ol&gt;
  &lt;li&gt;将所需软件从 hadoop01 复制到其他节点&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;scp &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; /data/soft/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; hadoop02:/data/soft
scp &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; /data/soft/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; hadoop03:/data/soft
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;执行安装&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rpm &lt;span class=&quot;nt&quot;&gt;-ivh&lt;/span&gt; cloudera-manager-daemons-5.7.0-1.cm570.p0.76.el6.x86_64.rpm
rpm &lt;span class=&quot;nt&quot;&gt;-ivh&lt;/span&gt; cloudera-manager-agent-5.7.0-1.cm570.p0.76.el6.x86_64.rpm
rpm &lt;span class=&quot;nt&quot;&gt;-ivh&lt;/span&gt; enterprise-debuginfo-5.7.0-1.cm570.p0.76.el6.x86_64.rpm
rpm &lt;span class=&quot;nt&quot;&gt;-ivh&lt;/span&gt; oracle-j2sdk1.7-1.7.0+update67-1.x86_64.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;将-cdh-复制到optclouderaparcel-repo&quot;&gt;将 cdh 复制到/opt/cloudera/parcel-repo&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;在 hadoop01 操作&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;CDH包的下载目录
&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; ./&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; /opt/cloudera/parcel-repo
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /opt/cloudera/parcel-repo
&lt;span class=&quot;nb&quot;&gt;mv &lt;/span&gt;CDH-5.7.0-1.cdh5.7.0.p0.45-el6.parcel.sha1 CDH-5.7.0-1.cdh5.7.0.p0.45-el6.parcel.sha
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;检验哈希码&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /opt/cloudera/parcel-repo
vim CDH-5.7.0-1.cdh5.7.0.p0.45-el6.parcel.sha
&lt;span class=&quot;c&quot;&gt;# 查看该文件内容是否与以下链接的文件内容一致&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# http://archive.cloudera.com/cdh5/parcels/5.7.0/manifest.json&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# parcelName: &quot;CDH-5.7.0-1.cdh5.7.0.p0.45-el5.parcel&quot; 所对应的 hash 值&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 如不一致，则将线上的hash值替换到 CDH-5.7.0-1.cdh5.7.0.p0.45-el6.parcel.sha 文件中&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;重启-cm-服务&quot;&gt;重启 CM 服务&lt;/h6&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;service cloudera-scm-server-db restart
service cloudera-scm-server restart
service cloudera-scm-agent restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;启动-cm-web-界面安装-cdh&quot;&gt;启动 CM Web 界面安装 CDH&lt;/h6&gt;
&lt;ul&gt;
  &lt;li&gt;如果访问 refused to connect，请耐心等待片刻，服务启动需要时间&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
  &lt;li&gt;访问 http://hadoop01:7180/&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;默认帐号密码是 admin / admin&lt;br /&gt;
强烈要求登录后修改密码&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;后面的步骤都在 web 界面操作，这里暂不说明&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;这里展示一张安装好后，并添加了些许服务的效果图&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/post/20160914/1.png&quot; alt=&quot;Paste_Image.png&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 14 Sep 2016 00:00:00 +0000</pubDate>
        <link>https://mtide.net/CDH-5.x-%E7%A6%BB%E7%BA%BF%E5%AE%89%E8%A3%85%E6%8C%87%E5%8D%97.html</link>
        <guid isPermaLink="true">https://mtide.net/CDH-5.x-%E7%A6%BB%E7%BA%BF%E5%AE%89%E8%A3%85%E6%8C%87%E5%8D%97.html</guid>
        
        
        <category>BIGDATA</category>
        
      </item>
    
  </channel>
</rss>
