(二十二)Spring Boot 安全管理【OAuth 2】

1、简介

       OAuth 是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(如头像、照片、视频等〉,而在这个过程中无须将用户名和密码提供给第三方应用。实现这一功能是通过提供一个令牌( token ),而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的网站在特定的时段内访问特定的资源。这样, OAuth 让用户可以授权第三方网站灵活地访问存储在另外一些资源服务器的特定信息,而非所有内容。例如,用户想通过 QQ 登录知乎,这时知乎就是一个第三方应用,知乎要访问用户的一些基本信息就需要得到用户的授权,如果用户把自己的 QQ 用户名和密码告诉知乎,那么知乎就能访问用户的所有数据,并且只有用户修改密码才能收回授权,这种授权方式安全隐患很大,如果使用 OAuth ,就能很好地解决这一问题。
       采用令牌的方式可以让用户灵活地对第三方应用授权或者收回权限。OAuth 2 是 OAuth 协议的下一版本,但不向下兼容 OAuth 1.0。OAuth 2 关注客户端开发者的简易性,同时为 Web 应用、桌面应用、移动设备、起居室设备提供专门的认证流程。传统的 Web 开发登录认证一般都是基于Session 的,但是在前后端分离的架构中继续使用 Session 会有许多不便,因为移动端( Android iOS 微信小程序等)要么不支持 Cookie (微信小程序〉,要么使用非常不便,对于这些问题,使用 OAuth 2 认证都能解决。

2、OAuth 2 角色

  • 资源所有者:资源所有者即用户,具有头像、照片、视频等资源。

  • 客户端:客户端即第三方应用,例如上文提到的知乎。

  • 授权服务器:投权服务器用来验证用户提供的信息是否正确,并返回一个令牌给第三方应用。

  • 资源服务器:资源服务器是提供给用户资源的服务器,例如头像、照片、视频等。

    一般来说,授权服务器和资源服务器可以是同一台服务器

3、OAuth 2 授权流程

  1. 步骤01:客户端(第三方应用)向用户请求授权。
  2. 步骤02:用户单击客户端所呈现的服务授校页面上的同意授权按钮后 ,服务端返回一个授权许可凭证给客户端。
  3. 步骤03:客户端拿着授权许可凭证去授权服务器申请令牌。
  4. 步骤04:授权服务器验证信息无误后,发放令牌给客户端。
  5. 步骤05:客户端拿着令牌去资源服务器访问资源。
  6. 步骤06:资源服务器验证令牌无误后开放资源。
    在这里插入图片描述

4、授权模式

• 授权码模式:授权码模式( authorization code )是功能最完整、流程最严谨的技权模式。它的特点就是通过客户端的服务器与授权服务器进行交互,国内常见的第三方平台登录功能基本都是使用这种模式。

• 简化模式:简化模式不需要客户端服务器参与,直接在浏览器中向授权服务器申请令牌,一般若网站是纯静态页面,则可以采用这种方式。

• 密码模式:密码模式是用户把用户名密码直接告诉客户端,客户端使用这些信息向授权服务器申请令牌,这需要用户对客户端高度信任,例如客户端应用和服务提供商是同一家公司。

• 客户端模式:客户端模式是指客户端使用自己的名义而不是用户的名义向服务提供者申请授权。严格来说,客户端模式并不能算作 OAuth 协议要解决的问题的一种解决方案,但是,对于开发者而言,在一些前后端分离应用或者为移动端提供的认证授权服务器上使用这种模还是非常方便的

4、项目中运用

       在前后端分离应用(或者为移动端、微信小程序等〉提供的认证服务器中,如何搭 OAuth 服务,因此主要介绍密码模式。搭建步骤如下。

4.1 创建项目,添加依赖

<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.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.4.RELEASE</version>
</dependency>

        由于Spring Boot 中的 OAuth 协议是在 Spring Security 基础上完成的 ,因此首先要添 Spring Security 依赖, 要用到 OAuth 2,因此添加 OAuth 2相关依赖,令牌可以存储在 Redis 缓存服务器上,同时 Redis 具有过期等功能,很适合令牌的存储,因此也加 redis 依赖。
项目创建成功后,接下来在 application.properties 中配置一 Redis 服务器的连接信息,代码如下:

# Redis数据库索引(默认为0)
spring.redis.database=0
#Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=200
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0

4.2 配置授权服务器

授权服务器和资源服务器可以是同一台服务器, 也可以是不同服务器,本案例中假设是同一台服务器,通过不同的配置分别开启授权服务器和资源服务器,首先是授权服务器:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

@Configuration
// 开启授权服务器
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

	// 注入该对象用来支持password模式
    @Autowired
    private AuthenticationManager authenticationManager;
    //该对象将用来完成 Redis 缓存,将令牌信息存储到 Redis 缓存中
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    //该对象将为刷新 token 提供支持
    @Autowired
    private UserDetailsService userDetailsService;

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

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    	// 配置password授权模式
        clients.inMemory()
                .withClient("password") 
                .secret("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq")
                //authorizedGrantTypes 表示 OAuth 2 中的授权模式为 “password” 和“refresh_token”两种,在标准的 OAuth 2 协议中, 授权模式并不包括“refresh_token”,但是在 Spring Security 的实现中将其归为一种 ,因此如果要实现 access_token 的刷新,就需要添加这样一种授权模式; accessTokenValiditySeconds 方法配置了access_token 的过期时间;resourceIds 配置了资源id; secret 方法配置了加密后的密码,明文是 123。
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(1800)
                .resourceIds("rid")
                .scopes("all");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    	//AuthenticationManager UserDetailsService 主要用于支持 password 模式以及令牌的刷新
        endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security){
    	//支持 client id client secret 做登录认证
        security.allowFormAuthenticationForClients();
    }
}

4.3 配置资源服务器

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {


    @Override
    public void configure(ResourceServerSecurityConfigurer resourceServerSecurityConfigurer){
    	//配置资源 id ,这里的资源 id 和授权服务器中的资源 id 一致,然后设置这些资源仅基于令牌认证。
        resourceServerSecurityConfigurer.resourceId("rid").stateless(true);
    }

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

4.4 配置 Security

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq")
                .roles("admin")
                .and()
                .withUser("user")
                .password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq")
                .roles("user");
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        return super.userDetailsService();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/oauth/**")
                .authorizeRequests()
                .antMatchers("/oauth/**")
                .permitAll()
                .and()
                .csrf()
                .disable();
    }
}

这里 Spring Security 的配置基本上和前文一致,唯一不同的是多了两个 Bean ,这里两个 Bean 将注入授权服务器配置类中使用。另外,这里的 HttpSecurity 配置主要是配置/oauth/**模式的 URL ,这一类的请求直接放行。在 Spring Security 配置和资源服务器配置中, 一共涉及两个 HttpSecurity ,其中 Spring Security 中的配置优先级高于资源服务器中的配置,即请求地址先经过 Spring Security 的 HttpSecurity ,再经过资源服务器的HttpSecurity。

4.5 测试验证

@RestController
public class HelloController {

    @GetMapping("/admin/hello")
    public String admin(){
        return "admin";
    }

    @GetMapping("/user/hello")
    public String user(){
        return "user";
    }

    @GetMapping("/hello")
    public String hello(){
        return "hello";
    }
}

根据前文的配置,要请求这三个地址,分别需要 admin 角色、 user 角色以及登录后访问。
所有都配置完成后,启动 Redis 务器,再启动 Spring Boot 项目 先发送 POST 请求获取 token ,请求地址如下(注意这里是 POST 请求): http://localhost:8080/oauth/token
请求地址中包含的参数有用户名、密码、授权模式、客户端 id scope 及客户端密码,基本就是授权服务器中所配置的数据
在这里插入图片描述
返回结果有 access_token、token_type、refresh_token、expires_in 以及 scope ,其中 access_token
是获取其他资源时要用的令牌, refresh_token 用来刷新令牌, expires_in 表示 access_token 的过期时间,当 access_token 过期后,使用 refresh_token 重新获取新的 access_token (前提是 refresh_token未过期〉

接下来访问所有资源,携带上 access_token 参数即可, 例如“user/hello” 接口:
http://localhost:8080/user/hello?access_token=a9e8078c-4acc-4770-bc54-65cb2fc0af7d

在这里插入图片描述
如果非法访问一个资源,例如 user 用户访 “/admin/hello ”接 口, 结果如下:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值