彻底理解浏览器同源策略SOP

本文来自于公众号链接: 彻底理解浏览器同源策略SOP

)

  1. 多种认证方式的优先级问题,如何杜绝冲突的问题
  2. 两个示例的描述不清晰的问题

这是一篇理论和实战相结合的干货文章,建议手机阅读者收藏本文,然后使用电脑下载源码进行实战

大纲:

  1. 一.概述
  2. 二.源码
  3. 三.示例1:接口分多组,每组认证方式不同
  4. 四.示例2:同组接口同时支持多种认证
  5. 五.总结
  6. 六.参考

一.概述

很多朋友虽然使用Spring Security做过很多项目,但是总是感觉没有理解和掌握Spring Security的核心思想。Spring Security比Shiro更强大更灵活的同时,确实带来了一定的复杂性。然而这种复杂性并不是因为Spring Security设计得不好造成的,而是因为安全需求本身就像一团乱麻一样错综复杂,Spring Security成功地将这团乱麻梳理柔顺后呈现给代价。在解决复杂安全问题场景下,Spring Security已经足够简洁了,我们更应该关注Spring Security的灵活性。

认证、鉴权和漏洞防御是安全框架的核心功能。Spring Security的认证功能强大并且非常灵活,支持的认证方式也在不断地水平扩展,已经预置了非常多的认证方式,如:

  1. Form认证
    最常用的(用户名/密码)认证方式及其变体,如(手机号/验证码)
    插图:用户名密码
  2. HTTP BASIC认证
    插图:浏览器弹出框
  3. LDAP认证
    大型环境如企业用户经常采用LDAP
  4. CAS认证
    单点登录场景下常用解决方案
  5. HTTP Digest 认证头 ( IETF RFC-based 标准)
  6. HTTP X.509 客户端证书交换 ( IETF RFC-based 标准)
  7. OpenID 认证
  8. ......

如果预置的认证仍然不满足需求,Spring Security支持用户自定义认证方式。

除了认证方式的丰富性,Spring Security对认证的配置也非常灵活。Spring Security支持多种认证方式自由组合

本文主要通过两个代码示例来展示Spring Security认证的灵活性:

  • 第一个配置示例: 接口分多组,每组认证方式不同
  • 第二个配置示例: 同组接口同时支持多种认证

二.源码

本文源码维护在Github,非常具有参考价值,建议下载源码:

有两个模块:

插图: twomodule

  • spring-security-url-separate-sample: 本文展示Spring Security认证的灵活性的配置示例。
  • authorizationserver:自建的OAuth2认证服务器。

三.示例1:接口分多组,每组认证方式不同

假设一个web应用的一部分接口是暴露在互联网上提供服务API接口,而另一部分接口是只需内部访问的内部接口。我们常常把这个web应用配置为OAuth2资源服务器,其中API接口采用OAuth2资源服务欧认证,内部接口则采用传统的认证方式如Form认证。
Spring Security支持这种“接口分多组,每组认证方式不同”类型的需求。

1.新建Spring Boot工程

新建Spring Boot工程,命名为“spring-security-url-separate-sample”,依赖:

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

spring-boot-starter-oauth2-resource-server是OAuth2资源服务器功能包。

2.增加两个Controller

FooController:

@RestController
public class FooController {

    @GetMapping("/foo")
    public String getSample() {
        return "get foo";
    }
}

和BarController:

@RestController
public class BarController {

    @GetMapping("/bar")
    public String getSample() {
        return "get bar";
    }
}

两个Controller用来模拟两组API接口。

3.两个Spring Security配置类

GroupOneSecurityConfig配置所有以“/foo”开头的接口使用OAuth2资源服务进行认证和鉴权:

@Configuration
//此处配置Order=1,因而比 GroupTwoSecurityConfig 配置类先执行,配置优先级高,因为Spring Security的配置规则是"先执行的优先级高"。
@Order(1)
public class GroupOneSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.requestMatchers()
                .antMatchers("/foo/**")
                .and().authorizeRequests().anyRequest().fullyAuthenticated()
                .and().oauth2ResourceServer().jwt();
    }
}

GroupTwoSecurityConfig配置除了以“/foo”开头的接口外剩余的其他接口使用From认证:

@EnableWebSecurity(debug = true)
public class GroupTwoSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/error", "/login**").permitAll()
                .anyRequest().fullyAuthenticated()
                .and().formLogin();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password(passwordEncoder().encode("123456"))
                .roles("ADMIN", "USER");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

GroupTwoSecurityConfig类配置的接口选择范围为“所有接口”,包含了GroupOneSecurityConfig选择的以“/foo”开头的接口,但是不用担心两个配置类的范围冲突问题,因为我们在 GroupOneSecurityConfig上增加了@Order(1)注解。@Order表示执行优先级,WebSecurityConfigurerAdapter默认的优先级是100:
插图:order

Spring Security的@Order规则是:“数字越小,越先执行,并且先执行的配置优先级高”
@Order(1)小于默认的@Order(100),所以GroupOneSecurityConfig的接口选择“/foo”的优先级高、先生效,而GroupTwoSecurityConfig只能选择到除“/foo”以外剩余的接口。

工程的接口分为了两组:

  1. 以“/foo”开头的接口,使用OAuth2资源服务认证
  2. 除“/foo”开头接口外剩余的接口,使用From认证。

注意GroupTwoSecurityConfig要配置“/error”和“/login**”为permitAll()

4.配置文件

server:
  servlet:
    session:
      cookie:
        name: UISESSIONCOUPON

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: http://localhost:9999/.well-known/jwks.json

自建OAuth2认证服务器和本工程都以localhost启动,配置不同的cookie名称,防止冲突报错,并且使用JWK接口验证 JWT Token 的有效性。

5.运行与演示

假设spring-security-url-separate-sample是一个在互联网提供API接口的服务器,其中“/foo”接口暴露在互联网提供服务的API接口,而 “/bar”接口为内部接口,则有如下访问场景:

  1. 内部接口可以直接使用Form认证
    1. 启动“spring-security-url-separate-sample”
    2. 隐身模式打开浏览器,输入:http://localhost:8080/bar
      插图:formlogin
    3. 输入用户名/密码:admin/123456,可以登录成功
    4. 浏览器再输入:http://localhost:8080/foo ,也可以成功返回字符串“get foo”。
      用户使用Form登录后既可以访问API接口也可以访问内部接口
  2. 暴露在互联网的API接口只能使用OAuth2资源服务认证:随便来自于互联网的用户不能获得内部的用户名和密码
    1. 启动“spring-security-url-separate-sample”

    2. 启动“sauthorizationserver”

    3. 隐身模式打开浏览器,输入:http://localhost:8080/foo
      插图:foo401
      未认证时,OAuth2资源服务认证不会自动重定向到认证中心,而是显示要401错误。调用
      “/foo”接口是需要携带OAuth2的access token的。

    4. 使用curl命令行工具模拟OAuth2请求,使用密码模式获取一个access token:

      curl -i -X POST -d "username=admin&password=admin&grant_type=password&client_id=client-for-server&client_secret=client-for-server" http://localhost:9999/oauth/token
      

      插图:gettoken

    5. 携带access_token请求“/foo”接口:

      curl -i -H "Authorization:Bearer {此处粘贴上一步骤返回的access_token}"  http://localhost:8080/foo
      

      插图:foosuccess
      外部用户成功调用“/foo”接口,但是仍然无法调用到内部“/bar”接口。内部接口”被成功地保护起来了

此示例将应用接口分为两组,分别使用不同的认证方式。理论上Spring Security可以将接口分为任意多组,使用任意多种认证方式,非常灵活。

四.示例2:同组接口同时支持多种认证

除了多组接口使用不同的认证方式外。对于同一组接口,可以同时支持多种认证方式。

我们继续对“spring-security-url-separate-sample”进行扩展。

1.增加OAuth2 Client依赖

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

2.在GroupTwoSecurityConfig中增加配置

@Override
public void configure(HttpSecurity http) throws Exception {
   ...
   .and().formLogin()
   .and().oauth2Login();
}

对于除“/foo"以外的其他接口同时支持Form认证和OAuth2客户端认证。

3.配置文件增加配置

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: b7fb29a538bb19b09365
            client-secret: 2fbfd22e69e61d873bad55143538770748a76d3a
          custom:
            client-id: client-for-server
            client-secret: client-for-server
            provider: custom
            client-name: 自建OAuth2认证服务
            authorization-grant-type: authorization_code
            redirect-uri: "http://localhost:8080/login/oauth2/code/custom"
        provider:
          custom:
            authorization-uri: http://localhost:9999/oauth/authorize
            token-uri: http://localhost:9999/oauth/token
            user-info-uri: http://localhost:9999/me
            user-name-attribute: "name"
            jwk-set-uri: http://localhost:9999/.well-known/jwks.json

配置两个OAuth2客户端,一个是Github的OAuth2的客户端,另一是自建OAuth2认证服务器的客户端。

4.运行与演示

对于“/bar”接口,同时支持Form认证和OAuth2客户端认证。

  1. 启动“spring-security-url-separate-sample”
  2. 启动“sauthorizationserver”
  3. 隐身模式打开浏览器,输入:http://localhost:8080/bar
    插图:oauth2login
    此时我们有三种选择:
    1. 输入用户名/密码:admin/123456登录成功。
    2. 点击“Github”蓝色按钮,使用Github账号登录成功。
    3. 点击“自建OAuth2认证服务”蓝色按钮,使用自建OAuth2认证服务,输入用户名/密码;admin/admin,登录成功。
      插图: customas

此示例演示了Spring Security支持同组接口同时支持多种认证

五.总结

spring-security-url-separate-sample整体来看同时支持了Form、GitHub认证、自建OAuth2认证服务客户端认证和自建OAuth2认证服务资源服务认证四种认证方式,并且不互相冲突。每种认证方式都有各自的应用场景。

Spring Security实现之所以可以实现如此灵活的认证配置,正是因为“按需装配”这一核心思想。

首先无论Spring Security应用在Servlet还是响应式(Reactive)场景,都是一种AOP切面原理。
插图:authenticaiton

其次一个web应用根据需求,需要使用哪种认证方式就装配哪种。
插图;asneed

总之,Spring Security除了支持漏洞防御,针对认证和鉴权的需求做了的非常强大且灵活的设计。

本文的相关源码上传到了Github,地址:https://github.com/andyzhaozhao/spring-security-url-separate

如果有任何问题和建议,可以右下角点赞后评论,我们会第一时间回复。

六.参考

更多干货都在《spring security实战》

本为官方出处微信公众号: 码闻


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

推荐阅读更多精彩内容