Spring Security Oauth2 从零到一完整实践(二)自动配置实现

本贴最后更新于 1797 天前,其中的信息可能已经时过境迁

前面我们学习了四种授权模式的两种,因为那两种分别满足了方便和安全,已经能够胜任大多数情况,我们从简开始,先来用最简单的方式开始。

github 地址:spring-security-oauth2-demo

博客地址:echocow.cn

[TOC]

系列文章

  1. 较为详细的学习 oauth2 的四种模式其中的两种授权模式
  2. spring boot oauth2 自动配置实现
  3. spring security oauth2 授权服务器配置
  4. spring security oauth2 资源服务器配置
  5. spring security oauth2 自定义授权模式(手机、邮箱等)
  6. spring security oauth2 踩坑记录

spring boot oauth2 自动配置实现

spring boot 最大一个特点就是 约定大于配置,去繁就简 。既然如此,他自然也提供了一套 oauth2 的自动化配置,我们先来实验他完成的自动化配置看看效果。

首先创建我们的 module 如下:

1

2

3

注意:这个过程以后不再截图演示。

pom.xml 中添加如下依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        <version>${spring.boot.version}</version>
    </dependency>
</dependencies>

spring security 保护的资源

默认情况下,我们加入了 spring security 的依赖,他会保护我们的资源。现在添加启动类如下:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

直接启动,控制台如下

然后访问 http://127.0.0.1:8080 ,如下

使用用户名 user 密码为控制台打印的那一串登录即可,成功后如下:

但是现在默认的是 spring security,我们接下来来实验一下 oauth2 保护我们的资源

spring security oauth2 保护资源

我们在前面提到过 oauth2 的几种角色,我们现在一步一步的来。在下面的授权服务器与资源服务器,我们将他们存在同一个应用之中使用,先以最快速的方式学习与了解,后面再来考虑分离的问题。

授权服务器

首先第一步是授权服务器,因为它是我们获取与请求凭证的地方,我们需要他来给我们下发令牌凭证,如何开启呢?需要一个启动注解 @EnableAuthorizationServer 添加在启动类上即可。

同时为了方便测试,我们添加一个 ResourceController 来设置一个资源访问路径如下:

这个时候我们再启动,然后去访问就会发现不需要登录了

测试

因为现在 spring security 已经不再去管理你的应用了,然而现在你只配置了授权服务器,他不会保护你的应用程序的,所以不需要登录了。我们暂时不管,现在授权服务器的任务是验证身份并下发令牌,我们来测试一下。

为了方便查看路径,我们开启 debug 日志,以便更好的理解整个过程;添加 application.yml 以及内容如下:

logging:
  level:
    org:
      springframework:
        security: DEBUG

然后运行,你会看到如下画面:

run

在我们启动的时候为我们自动生成的了一些东西:

  • 用户密码
  • 客户端 id
  • 客户端密码
  • 添加了七个路径
  • 使用权限表达式设置访问权限

授权码模式

spring security oauth 授权服务器默认开启授权码模式。那么按照我们前面说的,授权码模式是在授权端点 /oauth/authorize 请求授权码,路径应该如下:

http://localhost:8080/oauth/authorize?response_type=code&client_id=73ec1533-f25a-4fb0-9332-552d864bebbc&redirect_uri=http://example.com&scope=all

为什么回调地址是 http://example.com?因为我们现在没有任何应用,需要一个页面来接收回调厚的授权码,所以随便找了一个。

访问后会出现如下错误

error

这是因为我们没有配置 spring security 造成的,所以需要回去配置一下,使用默认配置即可,添加一个配置类如下:

spring security config

重启启动后,你会发现,现在的网页又不能访问了,全都提示需要登录了,暂时不管。

我们使用新的 client id 去请求授权,他会自动跳转到登录页面了,如下:

login

这里的步骤是在授权服务器上面的,就像我们点击第三方登录的 qq 的时候,是跳转到 腾讯 自己的登录页面的。用户名 user ,密码为控制台生成的,登录后如下:

error

它提示我们 至少为客户端注册一个回调地址 ,我们请求授权的时候传递了一个回调地址了,这里为什么还需要一个呢?这个很容易理解,**因为你传递过来的回调地址授权服务器不知道是否合法,可能会在传输的中途被篡改,所以在授权服务器里面需要你注册一个回调地址,与你传递过来的进行对比,如果匹配才会携带授权码进行回调。**这样就有效避免中途被篡改的问题了,所以现在我们需要去注册一个回调地址,在 application.yml 中配置:

注册一个回调地址

然后重新启动,再次携带新的客户端 id 进行访问:

确认授权当我们确认授权了以后,这个授权流程也就完毕了,也就相当于前面 角色中的抽象流图的 AB 完成了 ,我们看看得到的授权码

授权授权码

接下来我们需要使用此授权码去完成请求令牌的操作也就是前面说到的第二个请求,我们需要 postman 接口测试工具:

postman

当我们设置好授权信息以后他会为我们自动添加一个请求头

请求头

请求头的添加方式就是前面提到的 客户端加密 的那一部分,不再赘述。然后我们设置 第二个请求的请求参数 如下:

设置参数

请求令牌

这样我们就获取到令牌了,到这一步,也就相当于前面 角色中的抽象流图的 CD 也完成了 。这就是授权码模式获取令牌的两个请求的过程。

密码模式

接下来我们来试一下 密码模式 来获取令牌,就像前面所说,他只有一个请求即可,所以我们只要用 postman 携带参数请求一下就好了。

密码模式

相比起来,密码模式就简单太多啦!但是用户名密码是在客户端那里的,而不是在授权服务器这边的,所以只能是完全信得过的应用才能够使用!

快速自定义

**所谓快速自定义,就是我们不需要写代码,通过配置文件即可完成自定义。**对于 oauth2 客户端,提供了如下配置让我们快速自定义:

oauth

当然,只能提供一个客户端使用。我们后面再来详细学习如何自定义

配置用户

其实用户就是用的 spring security 的用户,但是由于不能够直接在配置文件中指定用户的密码了,所以我们需要建一个 UserService 的实现类。不过在那之前,我们需要配置一个密码加密器,让我们的密码得到保障,而不是明文传输,spring 5 以后这个是必须指定的。

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

BCrypt 不可逆的加密算法,无法通过解密密文得到明文,和其他对称或非对称加密方式不同的是,不是直接解密得到明文,也不是二次加密比较密文,而是把明文和存储的密文一块运算得到另一个密文,如果这两个密文相同则验证成功。对于同一个密码,每次加密出来是完全不同的,所以安全性很可靠。

下面的用户我们用最快捷的方式来进行创建,创建两个内存用户:

@Bean
@Override
protected UserDetailsService userDetailsService() {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(User.withUsername("user")
                   .password(passwordEncoder().encode("123456"))
                   .authorities("ROLE_USER").build());
    manager.createUser(User.withUsername("admin")
                   .password(passwordEncoder().encode("admin"))
                   .authorities("ROLE_ADMIN").build());
    return manager;
}

配置用户

获取令牌看看

token

资源服务器

现在我们取到了 token,我们来尝试访问一下被保护的资源,使用浏览器访问:

资源服务器

你会发现同样需要你登录,因为现在是由 spring security 进行资源保护的。那么我们看看携带 token 使用 postman 测试一下会怎样呢?

请求

会发现是 401,也就是令牌是无效的,原因就是因为现在资源的保护是由传统的 spring security 来进行保护的。接下来我们就要配置我们的资源服务器。

同授权服务器一样,资源服务器的启动也只需要一个注解就可以了:@EnableResourceServer,启动类添加此注解如下

@SpringBootApplication
@EnableAuthorizationServer
@EnableResourceServer
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

启动下应用,通过浏览器看看

浏览器

你会发现已经不能够登录了。现在重新用密码模式请求下 token,截图省略,然后获取 token 后去请求我们受保护的资源试一试:

受保护

现在携带正确的 token 就可以请求到数据了,这就是已经由 spring security oauth 来进行资源保护了。

对于资源服务器的自定义配置,目前只有一个地方,就是资源的 id ,如下:

id

如果两者不相同会抛出如下异常:

{
    "error": "access_denied",
    "error_description": "Invalid token does not contain resource id (resource-id)"
}

其他的配置我们后面再说,因为他主要涉及到与授权服务器的分离的情况。

其他

除了获取 token 和请求以外,她还可以配置一些默认的实现。

解析 token

我们需要在配置文件中添加如下配置:

security:
  oauth2:
    authorization:
      # 允许使用 /oauth/check_token 端点
      check-token-access: isAuthenticated()

配置

熟悉 SqEL 表达式的同学应该知道 isAuthenticated() 的意思,它允许此端点的访问,重启后获取新的 token,来访问试一试,参数前面已经说过不再赘述:

解析

这样就成功解析了我们的 token 信息!

使用默认配置的情况下且不增加类的情况下,我们是没有办法刷新 token 的。

总结

这个部分是我们最基础的部分,也是最为简单的部分,使用 spring boot oauth 的自动配置完成了简单的授权服务器和资源服务器的配置, 通过这两个服务器的配置就可以快速搭建起来 oauth2 的授权流程,为我们省掉了很多麻烦事儿,当然,自动配置有好处也有坏处,由于他自动帮我们配置好了很多,能满足很多的小型应用的需求了。但是要求总是在变化的,所以有些不符合我们要求的地方我们需要去自己自定义的,下面我们就要进入 spring security oauth2 完整自定义配置环节,分为两个部分,一个授权服务器,一个资源服务器的配置。

  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    943 引用 • 1460 回帖 • 3 关注
  • OAuth

    OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。oAuth 是 Open Authorization 的简写。

    36 引用 • 103 回帖 • 17 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • someone

    请问怎样改默认的授权地址和默认的获取 token 地址呢,目前默认授权地址为 http://localhost:8000/oauth/token,获取 token 地址:http://localhost:8000/oauth/authorize。我想改成 http://localhost:8000/myoauth2/authorize 和 http://localhost:8000/myoauth/authorize。我在 yml 改了,但无效,不知怎么弄

    1 回复
  • lizhongyue248

    如果你使用的是自动配置的方式来完成,是不能够修改的。也就是说使用配置文件无法修改,至少目前我没有找到修改的方法

    如果你是自己配置授权服务器,继承于 AuthorizationServerConfigurerAdapter,那么你可以通过覆写 configure(AuthorizationServerEndpointsConfigurer endpoints) 来完成,如下:

    public class Oauth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        // ....
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints.authenticationManager(this.authenticationManager);
            endpoints.pathMapping("/oauth/token", "/myoauth2/authorize")
                    .pathMapping("/oauth/authorize", "/myoauth/authorize");
        }
        // ....
    }
    

    如何继承和配置授权服务器可以参考我后面的文章。

  • someone

    图片已失效,博主能补充下么?

    1 回复
  • someone
    作者

    没有失效呀。。。可能是没有加载出来,你刷新试一下下 ~

  • alanfans

    可以楼主的站 echocow.cn

    1 回复
  • someone

    图片都显示不出来,点击图片占位符显示下面信息。应该是图床开启了防盗链检验功能导致的。
    {

    • code: "40310014",

    • msg: "invalid Referer header"

    }

    1 回复
  • lizhongyue248

    是的。开启了。目前只有 github、hacpai 和 我的博客 能够使用。

  • someone

    我的意思是直接由你所指的这些网站访问也报错。
    https://photos.app.goo.gl/52ynDUJyxU2ijd46A

  • someone

    我的意思是直接由你所指的这些网站访问也报错。
    https://photos.app.goo.gl/52ynDUJyxU2ijd46A

    1 回复
  • lizhongyue248

    诶?好奇怪啊。请问你用的什么浏览器啊?我这都没问题诶,可以帮忙 F12 看看 Referer 嘛?

    深度截图选择区域 20191012200037.png

  • someone

    google chrome 浏览器,截个图给你看看。
    https://photos.app.goo.gl/6jsPGs18dP1EiBCH6

    1 回复
  • lizhongyue248

    感谢 ~ 规则已经重新修改,请再试一次。。。

  • someone

    👍 所有图片都出来了!

    2 回复
  • lizhongyue248

    嘿嘿,感谢帮忙排错指出 ~

  • someone

    https://github.com/hansonwang99/Spring-Boot-In-Action/tree/master/springbt_sso_jwt
    请教一个问题,上面的工程里,客户端注册的时候没有指定 redirectUris,但处理时不会报错,spring security 自动跳转到 http://localhost:ip/login?code=xxxxxx,并继而跳转回 controller 请求并返回结果。这是怎么配置的,我在工程里去掉 rediredcturis,会报错 At least one redirect_uri must be registered with the client

    1 回复
  • lizhongyue248

    不好意思,才看到,环境有限没法具体测试。简单给你分析下我理解的把。

    这里最主要的是因为它的客户端开启了 sso 单点登录,请看

    这个注解意味着什么呢?我们去看看它的源码,他会条件装配几个类,分别是

    • OAuth2SsoDefaultConfiguration
    • OAuth2SsoCustomConfiguration
    • ResourceServerTokenServicesConfiguration

    从名字就可看出他们的区别,在你给的 demo 中,没有自定义 sso,所以默认情况下的 sso 配置就是 OAuth2SsoDefaultConfiguration,我们再来看看这个类,注意这个地方,这一行就是重点,他创建了一个 SsoSecurityConfigurer,将它配置进了应用上下文中,并传入 HttpSecurity 进行了配置,再转过来看看这个类。

    你注意 这行代码

    http.apply(new OAuth2ClientAuthenticationConfigurer(oauth2SsoFilter(sso)));
    

    它给我们 apply 了一个新的过滤器,并传入了 OAuth2SsoProperties,那么这里就是重点了。

    不过在看重点之前,先看看 OAuth2SsoProperties 里面的东西

    // 如果配置了就覆盖
    @ConfigurationProperties(prefix = "security.oauth2.sso")
    public class OAuth2SsoProperties {
    
    	public static final String DEFAULT_LOGIN_PATH = "/login";
    
    	/**
    	 * Path to the login page, i.e. the one that triggers the redirect to the OAuth2
    	 * Authorization Server.
    	 */
            // 没有配置就使用默认地址
    	private String loginPath = DEFAULT_LOGIN_PATH;
    	// ...其他方法省略
    }
    

    看到这里你就知道那个地址 login 是怎么来的了把?那么看看怎么配置进去的,看这行代码

    OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
    		sso.getLoginPath());
    

    看到这里其实就知道 sso 配置了 login 了,这个过滤器具体干嘛的,,,emmm,内容就太多了,自己查查看吧。

    而为啥授权服务器不用配置呢?你可以试着去 debug 这一行代码 ,在具体看看这个方法 的注释,可以知道:与服务器预先建立的重定向 URI。如果存在,则在用户授权请求中将省略重定向 URI,因为服务器不需要知道它。那么不存在的情况下就会去获取用户授权请求中的。可以参考同一个文件下的 getRedirectUri 方法。不过,由于我环境限制,没办法做测试,这里具体的情况建议你 debug 自己跟着走一下,我的思路是在你给的那个 demo 中写一个可以拦截所有请求的类,然后获取下请求的信息,看看客户端发出的 sso 相关请求都是些啥。

    最近在忙其他的事,所以只能说到这儿了,sso 还没去好好研究,有空再看看。spring security oauth2 的 sso 好像对前后端分离的应用支持没有默认的,需要自己配置。有空捣鼓下 ~

    1 操作
    lizhongyue248 在 2019-11-09 16:57:33 更新了该回帖
  • someone

    谢谢费心回复。期待博主对前后分体应用的 sso 实现解析文章。

  • lizhongyue248

    注意注意:本文章适用于 5.3 以前的 spring security 以及 spring boot 2.3.x 以前的 oauth,以下内容应该为过时!spring 提供新的 oauth2 授权服务器,目前正在实验性阶段,同时资源服务器由 oauth 模块迁移到 spring security 之内。

    1 回复
  • dodolook8716

    从 roadmap 看当前时间(2020-05-18) 到新的授权服务器 release 之前只有 2.4.x 可用?

    1 回复
  • yuchen001

    最近新项目需要搭一套 oauth server,我还在纠结是要买一套,还是自己写。主要是时间有点点紧张

    1 回复
  • lizhongyue248

    是的,而且好像都已经被标记为了过时状态。

  • lizhongyue248

    时间紧肯定买一套好,遇到问题直接找卖家。自己写要踩很多坑,时间上可能不够。

  • xiaomadidi

    打不开了

  • xiaomadidi

    图片还是显示不出来

请输入回帖内容 ...