SpringSecurity OAuth2 入门(待续)

OAuth是一种用来规范令牌(Token)发放的授权机制,主要包含了四种授权模式:授权码模式、简化模式、密码模式和客户端模式。Spring Security OAuth2对这四种授权模式进行了实现。这节主要记录下什么是OAuth2以及Spring Security OAuth2的基本使用。

下面分为二部分讲解:

  1. 四种授权模式
    1.1 授权码模式
    1.2 密码模式
  2. SpringSecurity OAuth2 Demo
    2.1 配置认证服务器
    2.2 授权码模式获取令牌
    2.3 密码模式获取令牌
    2.4 配置资源服务器

四种授权模式

在了解这四种授权模式之前,我们需要先学习一些和OAuth相关的名词。举个社交登录的例子吧,比如在浏览器上使用微信账号登录简书,这个过程可以提取出以下几个名词:

  • Third-party application 第三方应用程序,比如这里的简书

  • HTTP service HTTP服务提供商,比如这里的微信

  • Resource Owner 资源所有者,就是微信用户,你

  • User Agent 用户代理,这里指浏览器

  • Authorization server 认证服务器,这里指微信提供的第三方登录服务

  • Resource server 资源服务器

授权模式

授权码模式是最能体现OAuth2协议,最严格,流程最完整的授权模式,流程如下所示:


image.png

A. 客户端将用户导向认证服务器;
B. 用户决定是否给客户端授权;
C. 同意授权后,认证服务器将用户导向客户端提供的URL,并附上授权码;
D. 客户端通过重定向URL和授权码到认证服务器换取令牌;
E. 校验无误后发放令牌。

其中A步骤,客户端申请认证的URI,包含以下参数:

  1. response_type:表示授权类型,必选项,此处的值固定为”code”,标识授权码模式

  2. client_id:表示客户端的ID,必选项

  3. redirect_uri:表示重定向URI,可选项

  4. scope:表示申请的权限范围,可选项

  5. state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

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

  1. grant_type:表示使用的授权模式,必选项,此处的值固定为”authorization_code”。

  2. code:表示上一步获得的授权码,必选项。

  3. redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。

  4. client_id:表示客户端ID,必选项。

密码模式

在密码模式中,用户向客户端提供用户名和密码,客户端通过用户名和密码到认证服务器获取令牌。流程如下所示:


image.png

A. 用户向客户端提供用户名和密码;

B. 客户端向认证服务器换取令牌;

C. 发放令牌。

B步骤中,客户端发出的HTTP请求,包含以下参数:

  1. grant_type:表示授权类型,此处的值固定为”password”,必选项。

  2. username:表示用户名,必选项。

  3. password:表示用户的密码,必选项。

  4. scope:表示权限范围,可选项。

SpringSecurity OAuth2 Demo

Spring框架对OAuth2协议进行了实现,下面学习下上面两种模式在Spring Security OAuth2相关框架的使用。

Spring Security OAuth2主要包含认证服务器和资源服务器这两大块的实现:


image.png

认证服务器主要包含了四种授权模式的实现和Token的生成与存储,我们也可以在认证服务器中自定义获取Token的方式(后面会介绍到);资源服务器主要是在Spring Security的过滤器链上加了OAuth2AuthenticationProcessingFilter过滤器,即使用OAuth2协议发放令牌认证的方式来保护我们的资源。

配置认证服务器

新建一个SpringBoot 项目,版本为2.1.6.RELEASE并引入相关依赖,pom如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>top.lconcise</groupId>
    <artifactId>Spring-Security-OAuth2-Guide</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Spring-Security-OAuth2-Guide</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

</project>

在创建认证服务器前,我们先定义一个MyUser对象:

@Data
public class MyUser implements Serializable {
    private static final long serialVersionUID = 3497935890426858541L;

    private String userName;

    private String password;

    private boolean accountNonExpired = true;

    private boolean accountNonLocked= true;

    private boolean credentialsNonExpired= true;

    private boolean enabled= true;
}

接着定义UserDetailService实现org.springframework.security.core.userdetails.UserDetailsService接口:

@Service
public class UserDetailService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        MyUser user = new MyUser();
        user.setUserName(username);
        user.setPassword(this.passwordEncoder.encode("123456"));
        return new User(username, user.getPassword(), user.isEnabled(),
                user.isAccountNonExpired(), user.isCredentialsNonExpired(),
                user.isAccountNonLocked(), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

这里的逻辑是用什么账号登录都可以,但是密码必须为123456,并且拥有”admin”权限。

接下来开始创建一个认证服务器,并且在里面定义UserDetailService需要用到的PasswordEncoder。

创建认证服务器很简单,只需要在Spring Security的配置类上使用@EnableAuthorizationServer注解标注即可。创建AuthorizationServerConfig,代码如下所示:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends WebSecurityConfigurerAdapter {

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

这时候启动项目,会发现控制台打印出了随机分配的client-id和client-secret:

security.oauth2.client.client-id = 4afe5cf8-23e0-406f-8ed4-e80db6ef63f1
security.oauth2.client.client-secret = f7e63ad5-327c-4b97-b280-27432bfd0383

为了方便后面的测试,我们可以手动指定这两个值。在Spring Boot配置文件application.yml中添加如下配置:

security:
  oauth2:
    client:
      client-id: test
      client-secret: test1234

重启项目,发现控制台输出:

security.oauth2.client.client-id = test
security.oauth2.client.client-secret = ****

说明替换成功。

授权模式获取令牌

接下来开始往认证服务器请求授权码。打开浏览器,访问http://localhost:9090/oauth/authorize?response_type=code&client_id=test&redirect_uri=https://www.baidu.com&scope=all&state=hello

URL中的几个参数在上面的授权码模式的A步骤里都有详细说明。这里response_type必须为code,表示授权码模式,client_id就是刚刚在配置文件中手动指定的test,redirect_uri这里随便指定一个地址即可,主要是用来重定向获取授权码的,scope指定为all,表示所有权限。

访问这个链接后,页面如下所示:

image.png

需要登录认证,根据我们前面定义的UserDetailService逻辑,这里用户名随便输,密码为123456即可。输入后,页面跳转如下所示:


image.png

原因是上面指定的redirect_uri必须同时在配置文件中指定,我们往application.yml添加配置:

server:
  port: 9090
security:
  oauth2:
    client:
      client-id: test
      client-secret: test1234
      registered-redirect-uri: https://www.baidu.com

重启项目,重新执行上面的步骤,登录成功后页面成功跳转到了授权页面:


image.png

选择同意Approve,然后点击Authorize按钮后,页面跳转到了我们指定的redirect_uri,并且带上了授权码信息:


image.png

到这里我们就可以用这个授权码从认证服务器获取令牌Token了。


image.png

image.png

key为Authentication,value为Basic加上client_id:client_secret经过base64加密后的值(可以使用http://tool.chinaz.com/Tools/Base64.aspx):

源码地址:https://github.com/lbshold/springboot/tree/master/Spring-Security-OAuth2-Guide

参考文章:
https://juejin.im/post/5a3cbce05188252582279467
http://www.ruanyifeng.com/blog/2019/04/oauth_design.html
https://mrbird.cc/Spring-Security-OAuth2-Guide.html

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

推荐阅读更多精彩内容