Oauth2认证模式之授权码模式实现

  • 2019 年 10 月 3 日
  • 筆記

Oauth2认证模式之授权码模式(authorization code)

本示例实现了Oauth2之授权码模式,授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。

阅读本示例之前,你需要先有以下两点基础:

  • 需要对spring security有一定的配置使用经验,用户认证这一块,spring security oauth2建立在spring security的基础之上
  • oauth2开放授权标准基础,可以稳步到OAuth2 详解,浏览下授权码模式,理解下基本概念

概述

实现 oauth2,可以简易的分为三个步骤

  • 配置资源服务器
  • 配置认证服务器
  • 配置spring security

代码实现

1.pom.xml添加maven依赖

    <dependencies>          <dependency>              <groupId>org.springframework.security.oauth</groupId>              <artifactId>spring-security-oauth2</artifactId>              <version>2.3.6.RELEASE</version>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-web</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-thymeleaf</artifactId>          </dependency>      </dependencies>

2.配置资源服务器

public class ResourceServerConfig {        private static final String RESOURCE_ID = "account";        @Configuration      @EnableResourceServer()      protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {            @Override          public void configure(ResourceServerSecurityConfigurer resources) {              resources.resourceId(RESOURCE_ID).stateless(true);          }            @Override          public void configure(HttpSecurity httpSecurity) throws Exception {              httpSecurity                      .requestMatchers()                      // 保险起见,防止被主过滤器链路拦截                      .antMatchers("/account/**").and()                      .authorizeRequests().anyRequest().authenticated()                      .and()                      .authorizeRequests()                      .antMatchers("/account/info/**").access("#oauth2.hasScope('get_user_info')")                      .antMatchers("/account/child/**").access("#oauth2.hasScope('get_childlist')");          }      }  }

3.配置认证服务器

    @Override      public void configure(ClientDetailsServiceConfigurer clients) throws Exception {          clients.inMemory()                  .withClient("client1")                  .resourceIds(RESOURCE_ID)                  .authorizedGrantTypes("authorization_code", "refresh_token", "implicit")                  .authorities("ROLE_CLIENT")                  .scopes("get_user_info", "get_childlist")                  .secret("secret")                  .redirectUris("http://localhost:8081/client/account/redirect")                  .autoApprove(true)                  .autoApprove("get_user_info");      }

4.配置spring security

@Configuration  @EnableWebSecurity  public class SecurityConfig extends WebSecurityConfigurerAdapter {        @Bean      @Override      protected UserDetailsService userDetailsService() {          InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();          // 创建两个内存用户          manager.createUser(User.withUsername("admin").password("123456").authorities("USER").build());          manager.createUser(User.withUsername("lin").password("123456").authorities("USER").build());          return manager;      }        @Override      @Bean      public AuthenticationManager authenticationManagerBean() throws Exception {          return super.authenticationManagerBean();      }        @Bean      PasswordEncoder passwordEncoder(){          return NoOpPasswordEncoder.getInstance();      }        /**       * 密码生成器(默认为bcrypt模式)       *       * @return       */  //    @Bean  //    PasswordEncoder passwordEncoder() {  //        return PasswordEncoderFactories.createDelegatingPasswordEncoder();  //    }        @Override      protected void configure(HttpSecurity httpSecurity) throws Exception {            httpSecurity.                  requestMatchers()                  // 必须登录过的用户才可以进行 oauth2 的授权码申请                  .antMatchers("/", "/home", "/login", "/oauth/authorize")                  .and()                  .authorizeRequests()                  .anyRequest().permitAll()                  .and()                  .formLogin()                  .loginPage("/login")                  .and()                  .httpBasic()                  .disable()                  .exceptionHandling()                  .accessDeniedPage("/login?authorization_error=true")                  .and()                  .csrf()                  .requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))                  .disable();      }  }

使用介绍

  • 找到AuthResServerApplication.java运行server服务,默认端口:8080
  • 找到ClientApplication.java运行client客户端,端口:8081

1.尝试直接访问用户信息

http://localhost:8080/account/info/testAccount1/

返回未授权错误

<oauth>  <error_description>  Full authentication is required to access this resource  </error_description>  <error>unauthorized</error>  </oauth>

2.尝试获取授权码

http://localhost:8080/oauth/authorize?client_id=client1&response_type=code&redirect_uri=http://localhost:8081/client/account/redirect

结果被主过滤器拦截,302 跳转到登录页,因为 /oauth/authorize 端点是受保护的端点,必须登录的用户才能申请 code。

3.输入用户名和密码

输入用户名和密码 admin 123456
如上用户名密码是交给 SpringSecurity 的主过滤器用来认证的

4.登录成功后,真正进行授权码的申请

oauth/authorize 认证成功,会根据 redirect_uri 执行 302 重定向,并且带上生成的 code,注意重定向到的是 8001 端口,这个时候已经是另外一个应用了。

localhost:8081/client/account/redirect?code=xxxx
代码中封装了一个 http 请求,使得 client1 使用 restTemplate 向 server 发送 token 的申请,当然是使用 code 来申请的,并最终成功获取到 access_token

{  access_token: "59a25558-f714-4ca8-aa87-c36f93c120bf",  token_type: "bearer",  refresh_token: "92436849-7ef7-4923-8270-5a2c9b464556",  expires_in: 43199,  scope: "get_user_info get_childlist"  }

5.携带 access_token 访问account信息

http://localhost:8080/account/info/testAccount1?access_token=59a25558-f714-4ca8-aa87-c36f93c120bf

6.正常返回信息

{  name: "testAccount1",  nickName: "测试用户1",  remark: "备注1",  childAccount: [  {  name: "testChild1_0",  nickName: "测试子用户1_0",  remark: "0",  childAccount: null  },  {  name: "testChild1_1",  nickName: "测试子用户1_1",  remark: "1",  childAccount: null  },  {  name: "testChild1_2",  nickName: "测试子用户1_2",  remark: "2",  childAccount: null  },  {  name: "testChild1_3",  nickName: "测试子用户1_3",  remark: "3",  childAccount: null  },  {  name: "testChild1_4",  nickName: "测试子用户1_4",  remark: "4",  childAccount: null  },  {  name: "testChild1_5",  nickName: "测试子用户1_5",  remark: "5",  childAccount: null  },  {  name: "testChild1_6",  nickName: "测试子用户1_6",  remark: "6",  childAccount: null  },  {  name: "testChild1_7",  nickName: "测试子用户1_7",  remark: "7",  childAccount: null  },  {  name: "testChild1_8",  nickName: "测试子用户1_8",  remark: "8",  childAccount: null  },  {  name: "testChild1_9",  nickName: "测试子用户1_9",  remark: "9",  childAccount: null  }  ]  }

资料