正文也私有 OAuth2
学问总结,转载请注明出处:https://www.jianshu.com/u/50d83346eaff

摘要:使用OAuth2
认证的裨益就是是你独自需要一个账号密码,就能于各个网站进行访问,而给去矣以每个网站都进展注册的累赘过程,如:很多网站都可行使微信登录,网站作为第三正在服务、微信作为服务提供商

OAuth2 角色

  • resource owner:资源所有者(指用户)
  • resource
    server
    :资源服务器存放于保障资源,要访问这些资源,需要取访问令牌(下面例子中的
    Twitter 资源服务器)
  • client:客户端代表呼吁资源服务器资源的老三在程序(下面例子中之
    Quora)
  • authrization
    server
    :授权服务器用于发放访问使牌被客户端(下面例子中之 Twitter
    授权服务器)

OAuth2 工作流程例子

  • 客户端 Quora 将团结注册到授权服务器上
  • 用户访问 Quora 主页,它显示了各种登陆选项
  • 当用户点击使用 Twitter 登陆时,Quora 打开一个初窗口,将用户重定向到
    Twitter 的登陆页面上
  • 每当是新窗口被,用户以他的账号密码登陆了 Twitter
  • 只要用户之前未授权 Quora 应用程序使用他们的多寡,则 Twitter
    要求用户授权 Quora 来访问用户信息权限,如果用户就授权
    Quora,此步骤则叫超了
  • 透过是的身份验证,Twitter 以用户与一个身份验证代码重定向到 Quora
    的重定向 URI
  • Quora 发送客户端 ID、客户端令牌和身份验证代码到 Twitter
  • Twitter 验证这些参数后,用拜访令牌发送至 Quora
  • 成赢得访问令牌后用户给登陆到 Quora 上,用户登录 Quora
    后点击他们之个人资料页面
  • Quora 从 Twitter 资源服务器请求用户之资源,并发送访问令牌
  • Twitter 资源服务器使用 Twitter 授权服务器验证访问令牌
  • 遂验证访问令牌后,Twitter 资源服务器向 Quora 提供所用之资源
  • Quora 使用这些资源,并最终显示用户之个人资料页面
dtcoz.png

OAuth2 授权模式

授权码模式

  • 授权码模式是职能最完好、流程太紧密的授权模式,它的风味是由此客户端的后台服务器,与“服务器提供”的征服务器进行互动
dR6SO.png
  • 其的步子如下:

    • (A)用户访问客户端,后者以前者导向认证服务器
    • (B)用户挑选是否授予客户端授权
    • (C)假设用户与授权,认证服务器将用户导向客户端事先指定的“重定向
      URI”,同时附上一个授权码
    • (D)客户端收到授权码,附上早先的“重定向
      URI”向认证服务器申请令牌,这同样步是在客户端的后台服务器上就的,对用户不可见
    • (E)认证服务器对了授权码和重定向URI,确认无误后朝客户端发送令牌和翻新令牌
  • 上述手续中所用之参数:

    • A步骤中,客户端报名认证的 URI,包含以下参数:

      • repsone_type:授权类型,必选,此处固定值“code”
      • client_id:客户端的ID,必选
      • client_secret:客户端的密码,可摘
      • redirect_uri:重定向URI,可选
      • scope:申请的权杖限制,可挑选
      • state:客户端当前的状态,可以指定任意值,认证服务器会原本封不动的返这个价值

      GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
      &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
      Host: server.example.com
      
    • C步骤中,服务器对客户端的URI,包含以下参数:

      • code:表示授权码,必须遵,该码有效期应该怪紧缺,通常10分钟,客户端只能以相同次于,否则会给授权服务器拒绝,该码与客户端
        ID 和 重定向 URI 是各个对许涉及
      • state:如果客户端请求中包含在唱歌参数,认证服务器的应对也非得同型一样包含这个参数

      HTTP/1.1 302 Found
      Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
      &state=xyz
      
    • D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:

      • grant_type:表示用的授权模式,必选,此处固定值为“authorization_code”
      • code:表示达成同一步获得的授权也,必选
      • redirect_uri:重定向URI,必选,与步骤 A 中保持一致
      • client_id:表示客户端ID,必选

      POST /token HTTP/1.1
      Host: server.example.com
      Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
      Content-Type: application/x-www-form-urlencoded
      
      grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
      &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
      
    • E步骤中,认证服务器发送的HTTP回复,包含以下参数:

      • access_token:表示令牌,必选
      • token_type:表示让牌类,该值大小写不灵动,必选,可以是
        bearer 类型或 mac 类型
      • expires_in:表示过时,单位为秒,若省略该参数,必须装任何过期时
      • refresh_token:表示更新令牌,用来赢得下一样不好的拜会令牌,可卜
      • scope:表示权限限制

      HTTP/1.1 200 OK
           Content-Type: application/json;charset=UTF-8
           Cache-Control: no-store
           Pragma: no-cache
      
           {
             "access_token":"2YotnFZFEjr1zCsicMWpAA",
             "token_type":"example",
             "expires_in":3600,
             "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
             "example_parameter":"example_value"
           }
      
    • 起地方代码可以看来,参数使用 JSON 格式发送(Content-Type:
      application/json),才外,HTTP头信息遭到显然指定不得缓存

简化模式

  • 简化模式不通过第三着应用程序的服务器,直接当浏览器中为认证服务器申请令牌,跳了了“授权码”这个手续

bg2014051205.png

  • 她的步调如下:
    • (A)客户端将用户导向认证服务器
    • (B)用户决定是否给客户端授权
    • (C)假设用户给授权,认证服务器将用户导向客户端指定的“重定向URI”,并在URI的Hash部分包含了看令牌
    • (D)浏览器为资源服务器发出请求,其中未包上亦然步收到的Hash值
    • (E)资源服务器返回一个网页,其中涵盖了代码可以得到Hash值中之令牌
    • (F)浏览器执行上一致步获得的台本,取出令牌
    • (G)浏览器将令牌发给客户端
  • 上述手续中所欲的参数:

    • A步骤中,客户端有HTTP请求,包含以下参数:

      • response_type:表示授权类型,此处固定值为”token”,必选
      • client_id:表示客户端ID,必选
      • redirect_uri:表示重定向URI,可选
      • scope:表示权限限制,可卜
      • state:表示客户端当前状态,可指定任意值,认证服务器会原本封不动返回这个价

      GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
      &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
      Host: server.example.com
      
    • C步骤中,认证服务器对客户端的URI,包含以下参数:

      • access_token:表示访问令牌,必选
      • token_type:表示让牌类,该值大小写不灵动,必选
      • expires_in:表示过时,单位为秒
      • scope:表示权限限制,如果与客户端申请的限制一致,可忽略
      • state:如果客户端请求被富含这个参数,认证服务器也只要回一模一样的参数

      HTTP/1.1 302 Found
      Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
      &state=xyz&token_type=example&expires_in=3600
      
  • 地方例子中,认证服务器用HTTP头信息之Location栏,指定浏览器还定向的网址,注意,这个网址的Hash部分包含了令牌

  • 冲D步骤,下一致步浏览器会访问Location指定的网址,但是Hash部分不见面为发送,接下的E步骤,服务提供商的资源服务器发送过来的代码,提取出Hash令牌

密码模式

  • 密码模式中,用户为客户端提供好的用户称和密码,客户端采用这些信息为“服务提供商”索要授权

  • 每当这种模式面临,用户要把密码被客户端,但客户端不可存储密码,这通常以用户对客户端高端信任的动静下,比如客户端是操作系统的同局部,由一个知名的公司出品,而认证服务器只有以旁授权模式无法尽之情形下,才考虑该模式

bg2014051206.png
  • 她的步调如下:

    • (A)用户向客户端提供用户称以及密码
    • (B)客户端将用户称密码发送认证为服务器,向后者要令牌
    • (C)认证服务器确认无误后,向客户端提供访问令牌
  • 上述手续中所要的参数:

    • B步骤中,客户端有HTTP请求,包含以下参数:

      • grant_type:授权类型,必选,此处固定值“password”
      • username:表示用户称,必选
      • password:表示用户密码,必选
      • scope:权限限制,可选取

      POST /token HTTP/1.1
       Host: server.example.com
       Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
       Content-Type: application/x-www-form-urlencoded
      
       grant_type=password&username=johndoe&password=A3ddj3w
      
    • C步骤中,认证服务器向客户端发送访问令牌,下面是一个例证:

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

客户端模式

  • 客户端模式指客户端以相好名义,而休是用户名义,向“服务提供商”进行说明,严格地说,客户端模式不属于OAuth框架而化解之题目,在这种模式中,用户直接通往客户端注册,客户端以协调名义要求“服务提供商”提供服务

bg2014051207.png

  • 它们的手续如下:
    -(A):客户端向认证服务器进行身份验证,并要求一个造访令牌
    -(B):认证服务器确认无误后,向客户端提供访问令牌

  • 上述手续中所需要的参数:

    • A步骤中,客户端起HTTP请求,包含以下参数:

      • granttype:表示授权类型,此处固定值为“clientcredentials”,必选
      • scope:表示权限限制,可摘

       POST /token HTTP/1.1
       Host: server.example.com
       Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
       Content-Type: application/x-www-form-urlencoded
       grant_type=client_credentials
      
    • B步骤中,认证服务器向客户端发送访问令牌,下面是一个例子

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

更新令牌

  • 如果用户访问的时段,客户端“访问使牌”已经过期,则要采用“更新令牌”申请一个初的令牌
  • 客户端起更新令牌请求,包含以下参数:

    • granttype:表示授权模式,此处固定值为“refreshtoken”,必选
    • refresh_token:表示早前接收的更新令牌,必选
    • scope:表示申请权限限制,不得高于上一样次提请的范围,若省略该参数,则表示与齐等同浅同

     POST /token HTTP/1.1
     Host: server.example.com
     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
     Content-Type: application/x-www-form-urlencoded
     grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
    

Spring Security 与 OAuth2

authrization-server(授权服务器)

授权服务配置

  • 布置一个授权服务,需要考虑
    授权类型(GrantType)、授权类型也客户端(Client)提供了不同的取得令牌(Token)方式,每一个客户端(Client)都能够由此明确的安排和权限来兑现不同之授权访问机制,也就是说要你提供了一个
    “client_credentials”
    授权方式,并无代表任何客户端将动用这种方法来授权
  • 采取 @EnableAuthorizationServer 来配置授权服务机制,编写配置类继承
    AuthorizationServerConfigurerAdapter 该适配器类,并再写 configure
    方法定义授权服务器
  • ClientDetailsServiceConfigurer
    该类用于配置客户端详情信息,可以管客户端详情信息写死于内存中要么通过数据库调取
  • AuthorizationServerSecurityConfigurer:用来布局令牌端点(Token
    Endpoint)的平安约束
  • AuthorizationServerEndpointsConfigurer:用来安排授权(authorization)以及令牌(token)的访问端点和令牌服务(token
    services)
配备客户端详情(Client Details)
  • ClientDetailsServiceConfigurer 能够采取内存还是 JDBC
    方式实现获取已登记的客户端详情,有几只重大的特性:

    • clientId:客户端标识 ID
    • secret:客户端安全码
    • scope:客户端访问范围,默认为空则拥有全方位范围
    • authorizedGrantTypes:客户端应用的授权类型,默认为空
    • authorities:客户端可运的权
管制令牌(Managing Token)
  • AuthorizationServerTokenServices
    接口定义了有操作可以本着令牌进行一些管制,需要注意以下几点:

    • 当一个令牌被创造了,必须对该展开封存,这样当一个客户端应用这个令牌对资源服务要时才会引用这令牌
    • 当一个令牌是实惠之时节,它可用来加载身份信息,里面含了令牌的连带权限
  • 当由定义 AuthorizationServerTokenServices 接口实现时,可考虑以
    DefaultTokenServices
    该类,它含有了一部分落实,可用来修改令牌格式和储存,默认的铮铮尝试创建一个令牌时,是以随机值进行填的,除了持久化令牌是寄托一个
    TokenStore 接口实现以外,这个看似几乎帮你开了富有工作
  • 并且 TokenStore 这个接口有一个默认的落实,它便是
    InMemoryTokenStore,它的令牌被保存在内存中,还时有发生其它几只类似为落实了
    TokenStore 接口:

    • InMemoryTokenStore:默认使用该兑现,将令牌信息保存在内存中,易于调试
    • JdbcTokenStore:令牌会给封存近关系项目数据库,可以以不同服务器之间共享令牌
    • JwtTokenStore:使用 JWT
      方式保存令牌,它不待展开仓储,但是它们撤销一个早已授权令牌会非常不便,所以一般用来处理一个生命周期较短的令牌以及取消刷新令牌
JWT 令牌(JWT Tokens)
  • 下 JWT 令牌需要以授权服务被以
    JWTTokenStore,资源服务器也要一个解码 Token 令牌的类
    JwtAccessTokenConverter,JwtTokenStore
    依赖之仿佛进行编码和解码,因此授权服务和资源服务还待安排是转换类
  • Token
    令牌默认是出署名的,并且资源服务器遭到待征这个签名,因此待一个对称的
    Key 值,用来介入签署计算
  • 此 Key 值存在被授权服务及资源服务中,或者用未对如加密算法加密
    Token 进行签字,Public Key 公布在 /oauth/token_key 这个 URL 中
  • 默认 /oauth/token_key 的造访安全规则是 “denyAll()”
    即关闭的,可以注入一个规范的 SpEL 表达式到
    AuthorizationServerSecurityConfigurer 配置类中将它开启,例如
    permitAll()
  • 消引入 spring-security-jwt 库
布授权类型(Grant Types)
  • 授权是行使 AuthorizationEndpoint 这个端点来进展控制的,使用
    AuthorizationServerEndpointsConfigurer
    这个目标的例来开展配置,默认是支持除了密码授权他有标准授权类型,它只是安排以下属性:

    • authenticationManager:认证管理器,当你选了资源所有者密码(password)授权类型的时候,请设置这个特性注入一个
      AuthenticationManager 对象
    • userDetailsService:可定义自己的 UserDetailsService 接口实现
    • authorizationCodeServices:用来设置收取码服务之(即
      AuthorizationCodeServices 的实例对象),主要用来
      “authorization_code” 授权码类型模式
    • implicitGrantService:这个特性用于安装隐式授权模式,用来治本隐式授权模式之状态
    • tokenGranter:完全从定义授权服务实现(TokenGranter
      接口实现),只有当正规的季种授权模式都无力回天满足要求时
安排授权端点 URL(Endpoint URLs)
  • AuthorizationServerEndpointsConfigurer 配置对象有一个 pathMapping()
    方法用来安排端点的 URL,它起星星点点单参数:

    • 参数一:端点 URL 默认链接
    • 参数二:替代的 URL 链接
  • 脚是有的默认的端点 URL:
    • /oauth/authorize:授权端点
    • /oauth/token:令牌端点
    • /oauth/confirm_access:用户确认授权授端点
    • /oauth/error:授权服务错误信息端点
    • /oauth/check_token:用于资源服务看的令牌解析端点
    • /oauth/token_key:提供公有密匙的端点,如果你下JWT令牌的言辞
  • 授权端点的 URL 应该为 Spring Security 保护起来只供应授权用户访问

授权服务器代码例子(基于内存存储令牌)

新建项目,添加依赖

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

布授权服务类,创建一个类继承AuthorizationServerConfigurerAdapter,添加
@EnableAuthorizationServer 注解,添加客户端音

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //添加客户端信息
        clients.inMemory()                  // 使用in-memory存储客户端信息
                .withClient("client")       // client_id
                .secret("secret")                   // client_secret
                .authorizedGrantTypes("authorization_code")     // 该client允许的授权类型
                .scopes("app");                     // 允许的授权范围
    }
}

改配置文件,设置 Security 密码也 password,用户称吧
root,相当给一个资源拥有者(用户)的账号密码

security:
  user:
    name: root
    password: 1234
server:
  port: 8081

依傍客户端访问授权端点 /oauth/authorize
(该手续为授权码模式面临之A),需要附上客户端申请认证的参数(A步骤中所蕴藏的参数

localhost:8081/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com

登用户登陆页面(该步骤为授权码模式遭遇之B

0gXCQ-1.png

输入 root 1234
登陆后会进去下页面,询问用户是否授权客户端(该手续为授权码模式受到的C

0g0kS.png

勾选授权后点击按钮会超过反至百度(A步骤中涵盖的参数定义了重定向URL),并于
URL 中含一个授权码

https://www.baidu.com/?code=mhlA24

客户端将到授权码后,附上先前装的重定向 URL
向服务器申请令牌(该手续为授权码模式遭遇的D),通过令牌端点
/oauth/token

# 使用 CURL 工具发送 POST 命令,授权码模式不需要 client_sercet,因此该值可以为任意值
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=Li4NZo&redirect_uri=http://www.baidu.com' "http://client:secret@localhost:8081/oauth/token"

归来令牌如下

{"access_token":"d0e2f362-3bfd-43bb-a6ca-b6cb1b8ea9ee","token_type":"bearer","expires_in":43199,"scope":"app"}

授权服务器代码例子(基于JDBC存储令牌)

  • 上面例子中,客户端音以及令牌都是保存在内存中之,这明确无法以养环境遭受运用
  • Spring Cloud Security OAuth 已经也咱计划好了平学 Schema 和呼应之
    DAO 对象
连带接口
  • Spring Cloud Security OAuth2 由此 DefaultTokenServices 类来形成
    token 生成、过期等 OAuth2 标准规定的工作逻辑,而
    DefaultTokenServices 又是经过 TokenStore 接口就对转移数据的持久化
  • 每当面的 Demo 中,TokenStore 的默认实现呢 InMemoryTokenStore
    即内存存储,对于 Client 信息,ClientDetailsService
    接口负责从存储仓库中读取数据,在上头的 Demo 中默认使用的呢是
    InMemoryClientDetailsService 实现类似
  • 若想以数据库存储,只要提供这些接口的实现类似即可,而框架已也咱写好
    JdbcTokenStore 和 JdbcClientDetailsService
建表
  • 框架已提前也咱筹好了数量库表,但于 MYSQL
    来说,默认建表语句被主键为
    Varchar(256),这超过了最为特别之主键长度,可改成为 128,并据此 BLOB
    替换语句被之 LONGVARBINARY 类型

https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
配置

编写 @Configuration 类继承 AuthorizationServerConfigurerAdapter

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

   @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Bean // 声明TokenStore实现
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    @Bean // 声明 ClientDetails实现
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Override // 配置框架应用上述实现
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
        endpoints.tokenStore(tokenStore);

        // 配置TokenServices参数
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(false);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds( (int) TimeUnit.DAYS.toSeconds(30)); // 30天
        endpoints.tokenServices(tokenServices);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);
    }
}

修改配置文件,并引入 MYSQL 和 JDBC 依赖库

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/client?useUnicode=yes&characterEncoding=UTF-8
    username: root
    password: 123456ly

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

为数据库 oauth_client_details 表添加客户端信息

QfX0z.png

授权服务器代码例子(基于JWT存储令牌)

本着如加密,对如加密表示认证服务端和客户端的一头用一个密钥
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private TokenStore tokenStore;

    //告诉Spring Security Token的生成方式
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore)
                .accessTokenConverter(jwtAccessTokenConverter())
                .authenticationManager(authenticationManager);
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    //使用同一个密钥来编码 JWT 中的  OAuth2 令牌
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");
        return converter;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()                  // 使用in-memory存储客户端信息
                .withClient("client")       // client_id
                .secret("secret")                   // client_secret
                .authorizedGrantTypes("authorization_code")     // 该client允许的授权类型
                .scopes("app")                     // 允许的授权范围
                .autoApprove(true);         //登录后绕过批准询问(/oauth/confirm_access)

    }

}
运用不对称的密钥来签署令牌

生成 JKS Java KeyStore 文件

keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore mytest.jks -storepass mypass

导出公钥

keytool -list -rfc --keystore mytest.jks | openssl x509 -inform pem -pubkey

拿公钥保存也 pubkey.txt,将 mytest.jks()授权服务器) 和
pubkey.txt(资源服务器) 放到 resource 目录下

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhAF1qpL+8On3rF2M77lR
+l3WXKpGXIc2SwIXHwQvml/4SG7fJcupYVOkiaXj4f8g1e7qQCU4VJGvC/gGJ7sW
fn+L+QKVaRhs9HuLsTzHcTVl2h5BeawzZoOi+bzQncLclhoMYXQJJ5fULnadRbKN
HO7WyvrvYCANhCmdDKsDMDKxHTV9ViCIDpbyvdtjgT1fYLu66xZhubSHPowXXO15
LGDkROF0onqc8j4V29qy5iSnx8I9UIMEgrRpd6raJftlAeLXFa7BYlE2hf7cL+oG
hY+q4S8CjHRuiDfebKFC1FJA3v3G9p9K4slrHlovxoVfe6QdduD8repoH07jWULu
qQIDAQAB
-----END PUBLIC KEY-----

证服务器配置

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private TokenStore tokenStore;

    //告诉Spring Security Token的生成方式
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore)
                .accessTokenConverter(jwtAccessTokenConverter())
                .authenticationManager(authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer
                //允许所有资源服务器访问公钥端点(/oauth/token_key)
                //只允许验证用户访问令牌解析端点(/oauth/check_token)
                .tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()")
                // 允许客户端发送表单来进行权限认证来获取令牌
                .allowFormAuthenticationForClients();
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    //使用私钥编码 JWT 中的  OAuth2 令牌
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
        return converter;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()                  // 测试用,将客户端信息存储在内存中
                .withClient("client")       // client_id
                .secret("secret")                   // client_secret
                .authorizedGrantTypes("authorization_code")     // 该client允许的授权类型
                .scopes("app")                     // 允许的授权范围
                .autoApprove(true);         //登录后绕过批准询问(/oauth/confirm_access)

    }

}

从定义令牌声明,添加额外之性能

累加一个分外的字段 “组织” 到令牌中

public class CustomTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(
     OAuth2AccessToken accessToken, 
     OAuth2Authentication authentication) {
        Map<String, Object> additionalInfo = new HashMap<>();
        additionalInfo.put("organization", authentication.getName() + randomAlphabetic(4));
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
        return accessToken;
    }
}

拿把其并接受我们的授权服务器配置

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    tokenEnhancerChain.setTokenEnhancers(
      Arrays.asList(tokenEnhancer(), accessTokenConverter()));

    endpoints.tokenStore(tokenStore())
             .tokenEnhancer(tokenEnhancerChain)
             .authenticationManager(authenticationManager);
}

@Bean
public TokenEnhancer tokenEnhancer() {
    return new CustomTokenEnhancer();
}

这令牌如下

{
    "user_name": "john",
    "scope": [
        "foo",
        "read",
        "write"
    ],
    "organization": "johnIiCh",
    "exp": 1458126622,
    "authorities": [
        "ROLE_USER"
    ],
    "jti": "e0ad1ef3-a8a5-4eef-998d-00b26bc2c53f",
    "client_id": "fooClientIdPassword"
}

resource-server(资源服务器)

资源服务器

  • 资源服务器是令牌保护之资源。通过 @EnableResourceServer
    注解来拉开一个 OAuth2AuthenticationProcessingFilter
    类型的过滤器,并以 ResourceServerConfigurer(或
    ResourceServerConfigurerAdapter) 来配置资源服务器

ResourceServerProperties

  • OAuth2 乎资源服务器配置提供了 ResourceServerProperties
    类,该类会读取配置文件中针对资源服务器得配置信息(如授权服务器公钥访问地址)

ResourceServerSecurityConfigurer 相关属性

  • tokenServices:ResourceServerTokenServices
    类的实例,用来落实令牌业务逻辑服务
  • resourceId:这个资源服务的ID,这个特性是可选的,但是推荐设置并于授权服务被展开求证
  • tokenExtractor 令牌提取器用来提请求被的令牌
  • 央匹配器,用来安需要进行保护的资源路,默认的景下是让保障资源服务的全路子
  • 吃保障资源的拜会规则,默认的条条框框是简约的身份验证(plain
    authenticated)
  • 另的自定义权限保护规则通过 HttpSecurity 来拓展布置

ResourceServerTokenServices

  • 如若资源服务器和授权服务器在和一个应用,可使用 DefaultTokenServices
  • 设若资源服务器和授权服务器不以跟个下,则 ResourceServerTokenServices
    必须知道令牌如何解码
ResourceServerTokenServices 解析令牌方法:
  • 下 RemoteTokenServices 资源服务器通过 HTTP
    请求来解码令牌,每次都要授权服务器端点 /oauth/check_token
  • 若是 JWT 令牌,则需请授权服务器的 /oauth/token_key 来收获公钥
    key 进行解码

资源服务器代码例子(JWT 对如加密)

资源服务器和授权服务器不以跟一个应用,则用报资源服务器令牌如何存储和分析,并同授权服务器使用相同之密钥进行解密

@Configuration
@EnableResourceServer
public class OAuth2ResourceConfig extends ResourceServerConfigurerAdapter{
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    //与授权服务器使用共同的密钥进行解析
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("123");
        return converter;
    }
}

资源服务器代码例子(非对如加密)

  • 无对如加密亟需公钥,可以自地方得到,也足以由授权服务器提供的公钥端点获取

如果本地得到不交公钥资源文件 pubkey.txt 则由授权服务器端点获取

@Configuration
@EnableResourceServer
public class OAuth2ResourceConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private ResourceServerProperties resourceServerProperties;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //设置用于解码的非对称加密的公钥
        converter.setVerifierKey(getPubKey());
        return converter;
    }

    private String getPubKey() {
        Resource resource = new ClassPathResource("pubkey.txt");
        try (BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
            System.out.println("本地公钥");
            return br.lines().collect(Collectors.joining("\n"));
        } catch (IOException ioe) {
            return getKeyFromAuthorizationServer();
        }
    }

    private String getKeyFromAuthorizationServer() {
        ObjectMapper objectMapper = new ObjectMapper();
        String pubKey = new RestTemplate().getForObject(resourceServerProperties.getJwt().getKeyUri(), String.class);
        try {
            Map map = objectMapper.readValue(pubKey, Map.class);
            System.out.println("联网公钥");
            return map.get("value").toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

}

测试

起先授权服务器、启动资源服务器

看授权服务器 /oauth/authorize 端点获取授权码 code=vT4fY0

localhost:8081/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com

访问授权服务器 /oauth/token 端点获取访问令牌

WX20180112-111436@2x.png

做客资源服务器,请求被保障的资源,并附着令牌在请头,需加上 Bearer
,令牌被包含了资源所有者及客户端的信息

WX20180112-111728@2x.png

client(客户端)

  • OAuth2 客户端的实现方式没有尽多外确定,可活动编排登录逻辑
  • 呢可采取 OAuth2 提供的 @EnableOAuth2Sso
    注解实现单点登录,该注解会加上身份验证过滤器替我们做到有着操作,只待在部署文件里增长授权服务器和资源服务器的布置即可

长配置

server:
  port: 8083
security:
  oauth2:
    sso:
      loginPath: /login   # 登录路径
    client:
      clientId: client
      clientSecret: secret
      userAuthorizationUri: http://localhost:8081/oauth/authorize
      access-token-uri: http://localhost:8081/oauth/token
    resource:
      userInfoUri: http://localhost:8082/user

添加 Security 配置,并启动 @EnableOAuthSso

@Configuration
@EnableOAuth2Sso
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.
                // 禁用 CSRF 跨站伪造请求,便于测试
                csrf().disable()
                // 验证所有请求
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                //允许访问首页
                .antMatchers("/","/login").permitAll()
                .and()
                // 设置登出URL为 /logout
                .logout().logoutUrl("/logout").permitAll()
                .logoutSuccessUrl("/")
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

脚是测试用的控制类

@RestController
public class HelloController {

    @GetMapping("/")
    public String welcome() {
        return "welcome";
    }

}
  • 测试

访问 localhost:9007/login

可这时会晤出现 Authentication Failed: Could not obtain access token

  • 上面问题自己寻找了下,以下是某某网友叫有之对

Centinul as you’ve figured out this happens due to a cookie conflict,
unfortunately cookies don’t respect the port numbers. And so both Apps
interfere with each other since both are setting JSESSIONID. There are
two easy workarounds:

 1. use server.context-path to move each App to different paths, note that you need to do this for both
2. set the server.session.cookie.name for one App to something different, e.g., APPSESSIONID

I would suggest to put this workaround in a profile that you activate
for localhost only.

  • 修改配置文件,添加以下内容

# SESSION COOKIE 冲突 
session:
cookie:
name: APPSESSIONID

相关文章

网站地图xml地图