Spring Security 集成OAth2.0

467 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

简介

OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。

基本流程

图片.png

A. 客户端请求第三方授权 B. 资源拥有者同意给客户端授权 C. 客户端获取到授权码,请求认证服务器申请令牌 D. 认证服务器向客户端响应令牌 E. 客户端请求资源服务器的资源 F. 资源服务器返回受保护资源

基本角色

  • 客户端 客户端通常可以为Web客户端、微信客户端等,需要通过资源拥有者的授权去请求资源服务器的资源。

  • 资源拥有者  通常为用户,也可以是应用程序,即该资源的拥有者。

  • 授权服务器 用于服务提供商对资源拥有的身份进行认证、对访问资源进行授权,认证成功后会给客户端发放令牌(access_token)作为客户端访问资源服务器的凭据。

  • 资源服务器 存储相关资源的服务器.

Spring Security 集成OAth2.0

前期准备

由于oauth2涉及了多个角色,所以本文中创建了三个工程来表示oauth2的三个角色,分别为:

  • fw-security-oauth2-auth-client(客户端)-9092
  • fw-security-oauth2-auth-server(授权服务) -9090
  • fw-security-oauth2-resource-server(资源服务) -9091

授权服务器

pom文件添加相关依赖

 <parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.4.RELEASE</version>
	</parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <spring-cloud.version>Hoxton.SR3</spring-cloud.version>
  </properties>



  <dependencies>
   	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</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>
  </dependencies>
  
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

授权服务Spring Securiy认证

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter 
{
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("admin")
                .and()
                .withUser("john")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("user");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().formLogin();
    }
}

说明:为了简化操作,认证服务器采用的时用户名和密码内存方式的验证,如果需要采用其他方式的认证方式,可以参考之前的问题,这里不在赘述。

授权服务器核心配置

@EnableAuthorizationServer
@Configuration
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter 
{
    
   @Autowired
   private ClientDetailsService clientDetailsService;
   
   /**
    *  令牌管理服务
    * @return
    */
   @Bean
   AuthorizationServerTokenServices tokenServices() {
       DefaultTokenServices services = new DefaultTokenServices();
       services.setClientDetailsService(clientDetailsService);
       //支持刷新令牌
       services.setSupportRefreshToken(true);
       //配置令牌的存储方式,此时采用内存方式存储
       services.setTokenStore(new InMemoryTokenStore());
       // 访问令牌有效时间2小时
       services.setAccessTokenValiditySeconds(7200);
       // 刷新令牌的有效时间3天
       services.setRefreshTokenValiditySeconds(259200);
       return services;
   }

   @Override
   public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
       security.checkTokenAccess("permitAll()")
               .allowFormAuthenticationForClients();
   }

   /**
    * 客户端的详细信息
    */
   @Override
   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    // 基于内存方式存储客户信息
       clients.inMemory()
               .withClient("john")
               //密码加密
               .secret(new BCryptPasswordEncoder().encode("123456"))
                // 资源服务器的ID配置
               .resourceIds("res01")
               // 授权模式
               .authorizedGrantTypes("authorization_code","refresh_token")
               //作用范围
               .scopes("all")
               //重定到第三方客户端的地址,用来接收授权服务器返回的授权码
               .redirectUris("http://localhost:9092/login.html");
   }

   /**
    * 授权码存储配置
    */
   @Override
   public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
       endpoints.authorizationCodeServices(authorizationCodeServices())
               .tokenServices(tokenServices());
   }
   
   /**
    * 配置授权码模式下授权码的存取方式,此时采用内存模式
    * @return
    */
   @Bean
   AuthorizationCodeServices authorizationCodeServices() {
       return new InMemoryAuthorizationCodeServices();
   }
}

说明:

资源服务器

pom文件添加相关依赖

  <parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.4.RELEASE</version>
	</parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>


  <dependencies>
   	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	
	<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>2.1.8.RELEASE</version>
    </dependency>

资源服务核心配置

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter
{
    @Bean
    RemoteTokenServices tokenServices() {
        RemoteTokenServices services = new RemoteTokenServices();
        //access_token认证服务器地址
        services.setCheckTokenEndpointUrl("http://localhost:9090/oauth/check_token");
        //客户端id
        services.setClientId("john");
        //客户端密码
        services.setClientSecret("123456");
        return services;
    }
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
      //资源服务器id与认证服务器配置保持一致
        resources.resourceId("res01").tokenServices(tokenServices());
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //配置为admin角色
                .antMatchers("/admin/**").hasRole("user")
                .anyRequest().authenticated();
    }
}

第三方客户端

pom文件添加相关依赖

 <parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.4.RELEASE</version>
	</parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>


  <dependencies>
    <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>

	 <dependency>
				<groupId>org.apache.httpcomponents</groupId>
				<artifactId>httpclient</artifactId>
			</dependency>
	  </dependencies>

核心代码

@Controller
public class LoginController 
{
   @Autowired
   private RestTemplate restTemplate;

    @GetMapping("/login")
    public String login(String code, Model model) {
        if (code != null) {
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            map.add("code", code);
            map.add("client_id", "john");
            map.add("client_secret", "123456");
            map.add("redirect_uri", "http://localhost:9092/login.html");
            map.add("grant_type", "authorization_code");
            Map<String,String> resp = restTemplate.postForObject("http://localhost:9090/oauth/token", map, Map.class);
            String access_token = resp.get("access_token");
            HttpHeaders headers = new HttpHeaders();
            headers.add("Authorization", "Bearer " + access_token);
            HttpEntity<Object> httpEntity = new HttpEntity<>(headers);
            //认证成功,去资源服务器获取资源信息
            ResponseEntity<String> entity = restTemplate.exchange("http://localhost:9091/admin/hello", HttpMethod.GET, httpEntity, String.class);
            model.addAttribute("msg", entity.getBody());
        }
        return "login.html";
    }
}

说明:

  • /oauth/authorize:授权服务器对外暴露的,用于给第三方客户端生成授权码的接口。
  • client_id:就是授权服务器分配给第三方客户端的标识。
  • response_type:code表示采用授权码。
  • scope,值为all表示需要申请all这个域的资源访问权限,必须和上面配置中的一致。
  • redirect_uri,即第三方客户端的回调地址,用来获取授权服务器返回的授权码。

登录页面

 <!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>剑圣无痕</title>
</head>
<body>
你好,剑圣无痕!

<a href="http://localhost:9090/oauth/authorize?client_id=john&response_type=code&scope=all&redirect_uri=http://localhost:9092/index.html">第三方登录</a>

<h1 th:text="${msg}"></h1>
</body>
</html>

测试

启动所有工程,访问客户端登录页面

图片.png

点击第三方登录,进入认证页面,输入用户名和密码(john/123456)

图片.png

授权页面,选择Approve认证

图片.png

认证成功

图片.png

其中的admin信息是从资源服务器获取的信息。

总结

本文讲解了Spring Security 集成OAth2.0认证,采用的授权模式,其实OAuth2还提供了其他的认证模式,例如密码模式、简化模式、客户端模式等。我们需要针对不同的场景,选择不同的认证模式。