OAuth2.0深入理解

OAuth2.0是什么

用于REST/APIS的代理授权框架(delegatedauthorizetion framework),基于令牌token的授权,在无需暴露用户密码的情况下,使应用能够对用户数据有效访问权限,充分解耦认证和授权,实际上是标准的安全架构,支持多种应用场景,服务器端WebApp,浏览器单页面SPA,无线原生App,服务器与服务器之间访问。像仆从钥匙,给应用授权优先的访问权限,代表用户访问用户数据。OAoth是系统之间代理授权协议

优点

  1. 易实现
  2. 安全,服务端不接触用户密码,服务单更容易集中保护。
  3. 广泛传播被持续采用
  4. 短寿命和封装的token
  5. 资源服务器和授权服务器解耦
  6. 集中授权简化客户端
  7. HTTP/JSON友好易于请求和传递token
  8. 考虑多种客户端架构场景
  9. 客户可以具有不同的信任级别

缺点

  1. 协议框架太宽泛,造成各种实现的兼容性和互操作性
  2. 与OAuth1.0不兼容
  3. OAuth 2.0 不是一个认证协议,OAuth2.0本身并不能告诉你任何用户信息

架构角色

1.授权服务 Authorization Service
客户应用成功认证并获得授权之后,向客户应用颁发访问令牌。

2.资源服务 Resource Service
一个web服务或者web应用,保存用户受保护的数据

3.客户端应用 Client Application
通常是一个浏览器或者手机app,它需要用户受保护的数据

4.资源拥有者 owner
数据拥有者,想把数据分享给他人使用

客户端应用需要访问资源服务,但是没有认证,此时客户端去授权服务获取认证令牌,拿到令牌后交给资源服务器,资源服务器拿到令牌后也去授权服务比较一次,如果是对的,就认证通过.

OAuth2.0语术概念

1.客户凭证 Client Credentials
客户的clientId和密码用户认证客户

2.令牌 Tokens
授权服务器在接收到客户请求后颁发的资源服务器
令牌类型

  • 授权码 (Authorization Code Token) 仅用于授权码类型,用于交换获取访问令牌和刷新令牌
  • 刷新令牌 (Refresh Token) 用于去授权服务器获取一个新的token
  • 访问令牌 (Access Token) 代表用户直接访问受保护的资源服务器
  • Bearer Token 不管谁拿到都可以访问资源
  • Proof of Prosession Token 可以校验Client是否对Token有明确的权限

3.作用域
客户请求访问令牌时,有资源拥有者额外指定的细分权限

应用场景,解决方案

一、开放间系统授权

1.社交联合登陆

2.开放API平台

二、现代微服务安全

1.单页面浏览器APP

2.无线原生APP

3.服务端WebApp

4.微服务和原生API调用

三、企业内部认证授权(IAM,SSO)

AOuth Flow

推荐两篇文章

https://tools.ietf.org/html/rfc6749

理解OAuth 2.0 - 阮一峰的网络日志

客户端的授权模式

1. 授权码模式

授权步骤

  • A.用户访问客户端,客户端重定向到认证服务器.
  • B.用户选择授权客户端
  • C.如果用户授权,授权服务器重定向到客户端带来的url并附加一个授权码
  • D.客户端附带重定向url和授权码后台请求授权服务器申请令牌
  • E.授权服务器校对授权码和重定向url,确认无误,向客户端发送访问令牌(Access Token)和刷新令牌(Refresh Token)
基于spring security oauth2.0搭建最简授权码模式服务器
一、 源码

资源服务器和授权服务器一起案例

  1. 引入jar包
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- OAuth 2.0 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
        </dependency>

  1. 添加授权服务器配置

//授权服务器配置
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends
        AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.inMemory()
                .withClient("clientapp")
                .secret("123456")
                .redirectUris("http://localhost:9001/callback")
                // 授权码模式
                .authorizedGrantTypes("authorization_code")
                .scopes("read_userinfo", "read_contacts");
    }

}
  1. 添加资源服务器配置
//资源服务配置
@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .requestMatchers()
                .antMatchers("/api/**");
    }

}
  1. 配置添加授权用户
# Spring Security Setting
security.user.name=user1
security.user.password=passwd1
  1. 添加资源服务器api入口

@Controller
public class UserController {

    // 资源API
    @RequestMapping("/api/userinfo")
    public ResponseEntity<UserInfo> getUserInfo() {
        User user = (User) SecurityContextHolder.getContext()
                .getAuthentication().getPrincipal();
        String email = user.getUsername() + "@hello.com";

        UserInfo userInfo = new UserInfo();
        userInfo.setName(user.getUsername());
        userInfo.setEmail(email);

        return ResponseEntity.ok(userInfo);
    }

}
  1. 启动服务器
二、实践
  1. 获取授权码

请求:
打开浏览器 输入地址:

http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=code&scope=read_userinfo

输入用户名和密码,选择授权。

请求:
页面会跳转一个callback地址加授权码

http://localhost:9001/callback?code=4PFZ2w
  1. 获取令牌

请求:

curl -X POST --user clientapp:123456 http://localhost:8080/oauth/token -H
"content-type: application/x-www-form-urlencoded" -d
"code=4PFZ2w&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalh
ost%3A9001%2Fcallback&scope=read_userinfo"

响应:

{"access_token":"44a572ca-3a05-40ba-a557-35955179e143","token_type":"bearer","expires_in":43199,"scope":"read_userinfo"}
  1. 访问资源,调用API
    请求:
curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 44a572ca-3a05-40ba-a557-35955179e143"

响应:

{"name":"user1","email":"user1@hello.com"}

特点

  • 通过前端渠道客户获取授权码
  • 通过后端渠道,客户使用authorization code 交换access token 或refresh token
  • 假定资源拥有者和客户在不同的设备上
  • 最安全的流程,因为令牌不会传递经过User-Agent

总结: 授权码模式本质上是客户端通过用户名密码发起获取授权码请求,服务端根据回调地址返回授权码,客户端根据授权码访问资源服务器,资源服务器根据授权码拿到授权服务器给的access token返回给客户端,客户端就可以带着这个access token访问资源服务器上的有效资源.

2. 简化模式

授权步骤

  • 客户端重定向用户到认证服务器
  • 用户选择是否授权给客户端
  • 用户授权,授权服务器根据客户端请求参数uri重定向到客户端,并将token放入url的hash部分
  • 资源服务返回一个页面,可以获取hash值
  • 浏览器执行脚本获取令牌
  • 浏览器叫令牌发送给客户端
基于spring security oauth2.0搭建简化模式服务器
一、 源码

资源服务器和授权服务器一起案例

  1. 引入spring security 和 oauth jar包
  2. 授权服务器配置
//简化服务器配置
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends
        AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.inMemory()
                .withClient("clientapp")
                .secret("123456")
                .redirectUris("http://localhost:9001/callback")
                // 授权码模式
                //.authorizedGrantTypes("authorization_code")
                // 简化模式
                .authorizedGrantTypes("implicit")
                .accessTokenValiditySeconds(120)
                .scopes("read_userinfo", "read_contacts");
    }

}
二、操作
  1. 获取令牌
    访问浏览器
http://localhost:8080/oauth/authorize?client_id=clientapp&redirect_uri=http://localhost:9001/callback&response_type=token&scope=read_userinfo&state=abc

输入配置的用户和密码,选择授权。
响应:

http://localhost:9001/callback#access_token=d678754d-752c-46cc-9a9b-d59ae830cddb&token_type=bearer&state=abc&expires_in=119
  1. 访问资源,调用API
curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer d678754d-752c-46cc-9a9b-d59ae830cddb"

响应:

{"name":"user1","email":"user1@hello.com"}

特点

  • 适用于公开的浏览器单页面应用
  • Access Token 直接从授权服务器返回
  • 不支持Refresh Token
  • 假定资源拥有者和客户在同一设备上
  • 最容易受安全攻击

总结:简化模式,就是没有授权码的授权模式,去掉了授权码这个步骤。

3. 密码模式 Resource Owner Password Credential Grant

授权步骤

  • 用户向客户端提供用户名和密码
  • 客户端将用户名和密码发送给认证服务器去认证授权
  • 认证服务器验证无误,返回给客户端令牌
基于spring security oauth2.0搭建密码模式服务器
一、代码

授权服务器配置

// 授权服务器配置
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends
        AuthorizationServerConfigurerAdapter {

    // 用户认证
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.inMemory()
            .withClient("clientapp")
            .secret("123456")
            // 密码模式
            .authorizedGrantTypes("password")
            .scopes("read_userinfo", "read_contacts");
    }

}

一、操作
  1. 获取令牌
curl -X POST --user clientapp:123456 http://localhost:8080/oauth/token -H "accept: application/json" -H "content-type: application/x-www-form-urlencoded" -d "grant_type=password&username=user1&password=passwd1&scope=read_userinfo"

响应:

{"access_token":"9247e26e-b9d7-488b-88b0-25fb046ac7ce","token_type":"bearer","expires_in":43163,"scope":"read_userinfo"}
  1. 访问资源,调用API
curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer 9247e26e-b9d7-488b-88b0-25fb046ac7ce"

响应:

{"name":"user1","email":"user1@hello.com"}

特点

  • 使用用户名密码登录的应用,比如桌面应用
  • 使用用户名/密码作为授权方式从授权服务器上获取access token
  • 一般不支持refresh token
  • 假定资源拥有者和公开客户在相同设备上

总结:密码授权模式,是对客户端极度信任的情况下,将用户名和密码交给客户端,客户端去授权服务器获取令牌。

4. 客户端模式

授权步骤

  • 客户端直接向授权服务器发起授权认证,获取令牌
  • 授权服务器校验通过,颁发令牌
基于spring security oauth2.0搭建客户端模式服务器
一、代码

授权服务器配置


// 授权服务器配置
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends
        AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.inMemory()
            .withClient("clientdevops")
            // 密码模式
            .secret("123456")
            .authorizedGrantTypes("client_credentials")
            .scopes("devops");
    }

}
二、操作
  1. 获取令牌
curl -X POST "http://localhost:8080/oauth/token" --user clientdevops:123456 -d
"grant_type=client_credentials&scope=devops"

响应:

{"access_token":"ab5e2936-7463-456b-9ded-aac79d9311e1","token_type":"bearer","expires_in":43199,"scope":"devops"}
  1. 访问资源,调用API
curl -X GET http://localhost:8080/api/userinfo -H "authorization: Bearer ab5e2936-7463-456b-9ded-aac79d9311e1"

响应:

{"name":"user1","email":"user1@hello.com"}

特点

  • 适用于服务间通信,机器代表用户或者它自己
  • 只有后端渠道,使用客户凭证获取一个access token
  • 因为客户凭证可以使用对称或者非对称加密,该方式支持共享密码或者证书

刷新令牌

一、代码
//授权服务器配置
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends
        AuthorizationServerConfigurerAdapter {

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.inMemory()
                .withClient("clientapp")
                .secret("123456")
                .redirectUris("http://localhost:9001/callback")
                // 密码模式
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(120)
                .refreshTokenValiditySeconds(60)
                .scopes("read_userinfo", "read_contacts");
    }

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
        endpoints.userDetailsService(userDetailsService);
    }

}

安全配置


@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //启用方法级的权限认证
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("user1").password("passwd1").authorities("USER").build());
        manager.createUser(User.withUsername("root").password("root").authorities("USER").build());
        return manager;
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
二、操作

获取令牌

curl -i -X POST --user clientapp:123456 http://localhost:8080/oauth/token -H "accept: application/json" -H "content-type: application/x-www-form-urlencoded" -d "grant_type=refresh_token&refresh_token=dda1413c-86e8-4612-8e13-e9044e530c66"

响应:

HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
X-Application-Context: application
Cache-Control: no-store
Pragma: no-cache
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 12 Jul 2019 07:43:15 GMT

{"access_token":"052f2239-6231-4537-bc93-33fce4c58d0c","token_type":"bearer","refresh_token":"dda1413c-86e8-4612-8e13-e9044e530c66","expires_in":119,"scope":"read_userinfo"}

认证方式选择

授权服务器端点

  1. Authorize Endpoint (/oauth2/authorize) 认证端点
  2. Token Endpoint (/oauth2/token) 令牌端点
  3. Introspection Endpoint (/oauth2/instrospection) 检查端点
  4. Revocation Endpoint (/aouth2/revoke) 吊销端点

Spring Security OAuth2.0架构

http://terasolunaorg.github.io/guideline/5.3.0.RELEASE/en/Security/OAuth.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348